统一宿主壳目录边界

移动端壳拆分为 host-bridge 与 shell 目录

桌面端 Tauri 拆分为 host_bridge 与 shell Rust 模块

更新三端桥接层结构门禁与配置检查

同步宿主壳方案文档和项目共享决策
This commit is contained in:
2026-06-18 16:02:55 +08:00
parent e92e016549
commit 00b41b6a29
32 changed files with 130 additions and 72 deletions
+18 -7
View File
@@ -732,6 +732,16 @@ function collectRustSourceBasenames(files) {
return files.map((file) => file.pathname.split('/').pop()).sort();
}
function collectRustSourceRelativePaths(files) {
const rootPath = rustSourceDir.pathname.endsWith('/')
? rustSourceDir.pathname
: `${rustSourceDir.pathname}/`;
return files
.map((file) => file.pathname.replace(rootPath, ''))
.sort();
}
function extractNativeAppTauriInvokeCommands(source) {
return [...source.matchAll(/\btauriInvoke[\s\S]*?\(\s*([^,\n]+?)\s*,/g)]
.map((match) => match[1].trim())
@@ -1024,13 +1034,14 @@ const sharedTauriCommand = extractTsStringConst(
);
const allowedTauriCommands = [sharedTauriCommand];
const requiredRustHostModules = [
'desktop_host_bridge.rs',
'desktop_host_bridge_files.rs',
'desktop_host_bridge_protocol.rs',
'desktop_host_bridge_share.rs',
'desktop_shell_tray.rs',
'desktop_shell_webview.rs',
'host_bridge/files.rs',
'host_bridge/mod.rs',
'host_bridge/protocol.rs',
'host_bridge/share.rs',
'main.rs',
'shell/mod.rs',
'shell/tray.rs',
'shell/webview.rs',
];
const requiredRustHostSnippets = [
'tauri_plugin_single_instance::init',
@@ -1138,7 +1149,7 @@ assertSameList(
'shared Tauri HostBridge command',
);
for (const moduleName of requiredRustHostModules) {
if (!collectRustSourceBasenames(rustHostSourceFiles).includes(moduleName)) {
if (!collectRustSourceRelativePaths(rustHostSourceFiles).includes(moduleName)) {
throw new Error(`desktop shell Rust bridge module missing ${moduleName}`);
}
}
@@ -1,4 +1,4 @@
use crate::desktop_host_bridge_protocol::{failed, HostBridgeRequest, HostBridgeResponse};
use crate::host_bridge::protocol::{failed, HostBridgeRequest, HostBridgeResponse};
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _};
use serde_json::{json, Value};
use std::fs;
@@ -431,7 +431,7 @@ pub(crate) fn import_audio_file_payload(path: PathBuf) -> Result<Value, String>
#[cfg(test)]
mod tests {
use super::*;
use crate::desktop_host_bridge_protocol::request;
use crate::host_bridge::protocol::request;
#[test]
fn export_file_name_normalization_rejects_path_like_characters() {
@@ -1,15 +1,22 @@
use crate::desktop_host_bridge_files::{
pub(crate) mod files;
pub(crate) mod protocol;
mod share;
pub(crate) use protocol::HostBridgeReplayState;
pub(crate) use share::DesktopShareState;
use crate::host_bridge::files::{
export_audio_payload, export_image_payload, export_text_payload, import_audio_file_payload,
import_image_file_payload, import_text_file_payload, write_export_bytes_file,
write_export_text_file,
};
use crate::desktop_host_bridge_protocol::{
use crate::host_bridge::protocol::{
capabilities, failed, normalize_request_id, ok, required_string_payload, validate_request,
HostBridgeReplayReservation, HostBridgeReplayState, HostBridgeRequest, HostBridgeResponse,
HostBridgeRuntime, HOST_BRIDGE_VERSION,
HostBridgeReplayReservation, HostBridgeRequest, HostBridgeResponse, HostBridgeRuntime,
HOST_BRIDGE_VERSION,
};
use crate::desktop_host_bridge_share::{share_text_from_request, DesktopShareState};
use crate::desktop_shell_webview::{
use crate::host_bridge::share::share_text_from_request;
use crate::shell::webview::{
color_scheme_from_theme, desktop_platform, normalize_external_url, normalize_native_page_url,
resolve_desktop_network_status,
};
@@ -527,7 +534,7 @@ pub(crate) async fn host_bridge_request(
#[cfg(test)]
mod tests {
use super::*;
use crate::desktop_host_bridge_protocol::request;
use crate::host_bridge::protocol::request;
#[test]
fn runtime_response_reports_tauri_shell() {
@@ -1,5 +1,5 @@
use crate::desktop_host_bridge_protocol::{failed, HostBridgeRequest, HostBridgeResponse};
use crate::desktop_shell_webview::WEB_APP_ORIGIN;
use crate::host_bridge::protocol::{failed, HostBridgeRequest, HostBridgeResponse};
use crate::shell::webview::WEB_APP_ORIGIN;
use serde_json::Value;
use std::sync::Mutex;
@@ -84,7 +84,7 @@ pub(crate) fn share_text_from_request(
#[cfg(test)]
mod tests {
use super::*;
use crate::desktop_host_bridge_protocol::request;
use crate::host_bridge::protocol::request;
use serde_json::json;
#[test]
+5 -11
View File
@@ -1,18 +1,12 @@
mod desktop_host_bridge;
mod desktop_host_bridge_files;
mod desktop_host_bridge_protocol;
mod desktop_host_bridge_share;
mod desktop_shell_tray;
mod desktop_shell_webview;
mod host_bridge;
mod shell;
use desktop_host_bridge::host_bridge_request;
use desktop_host_bridge_protocol::HostBridgeReplayState;
use desktop_host_bridge_share::DesktopShareState;
use desktop_shell_tray::{
use host_bridge::{host_bridge_request, DesktopShareState, HostBridgeReplayState};
use shell::tray::{
register_desktop_tray, register_desktop_window_close_events,
resolve_desktop_single_instance_action, show_main_window, DesktopSingleInstanceAction,
};
use desktop_shell_webview::{
use shell::webview::{
desktop_window_config_with_runtime_platform, emit_desktop_lifecycle_event,
open_desktop_external_navigation, register_desktop_file_drop_events,
register_desktop_lifecycle_events, register_desktop_network_events,
@@ -0,0 +1,2 @@
pub(crate) mod tray;
pub(crate) mod webview;
@@ -1,5 +1,5 @@
use crate::desktop_host_bridge_files::{import_image_file_payload, import_image_mime_type};
use crate::desktop_host_bridge_protocol::{HOST_BRIDGE_PROTOCOL, HOST_BRIDGE_VERSION};
use crate::host_bridge::files::{import_image_file_payload, import_image_mime_type};
use crate::host_bridge::protocol::{HOST_BRIDGE_PROTOCOL, HOST_BRIDGE_VERSION};
use serde_json::{json, Value};
use std::net::{TcpStream, ToSocketAddrs};
use std::path::PathBuf;
+9 -9
View File
@@ -16,25 +16,25 @@ import {
configureMobileHostBridgeNavigation,
handleMobileHostBridgeMessage,
resolveMobileHostCapabilities,
} from './src/mobileHostBridge';
import { buildMobileShellUrlFromDeepLink } from './src/mobileShellDeepLink';
import { lifecyclePayloadFromAppState } from './src/mobileShellLifecycle';
} from './src/host-bridge/mobileHostBridge';
import { buildMobileShellUrlFromDeepLink } from './src/shell/mobileShellDeepLink';
import { lifecyclePayloadFromAppState } from './src/shell/mobileShellLifecycle';
import {
resolveMobileShellExternalUrl,
shouldAcceptMobileShellHostBridgeMessage,
shouldOpenInMobileShellWebView,
} from './src/mobileShellNavigation';
} from './src/shell/mobileShellNavigation';
import {
getMobileNetworkStatus,
subscribeMobileNetworkStatus,
} from './src/mobileShellNetwork';
import { MOBILE_SHELL_HOST_VERSION } from './src/mobileShellRuntime';
import { MOBILE_SHELL_SAFE_AREA_EDGES } from './src/mobileShellSafeArea';
} from './src/shell/mobileShellNetwork';
import { MOBILE_SHELL_HOST_VERSION } from './src/shell/mobileShellRuntime';
import { MOBILE_SHELL_SAFE_AREA_EDGES } from './src/shell/mobileShellSafeArea';
import {
buildMobileShellUrl,
resolveMobileShellBaseWebUrl,
} from './src/mobileShellUrl';
import { BLOCK_WEBVIEW_DOWNLOAD_SCRIPT } from './src/mobileShellWebViewPolicy';
} from './src/shell/mobileShellUrl';
import { BLOCK_WEBVIEW_DOWNLOAD_SCRIPT } from './src/shell/mobileShellWebViewPolicy';
function buildHostBridgeMessageScript(message: unknown) {
return `window.dispatchEvent(new MessageEvent('message', { data: ${JSON.stringify(
+3 -3
View File
@@ -6,11 +6,11 @@ const appConfigPath = new URL('../app.json', import.meta.url);
const appConfig = JSON.parse(fs.readFileSync(appConfigPath, 'utf8')).expo;
const appPath = new URL('../App.tsx', import.meta.url);
const appSource = fs.readFileSync(appPath, 'utf8');
const bridgePath = new URL('../src/mobileHostBridge.ts', import.meta.url);
const bridgePath = new URL('../src/host-bridge/mobileHostBridge.ts', import.meta.url);
const bridgeSource = fs.readFileSync(bridgePath, 'utf8');
const mobileShellUrlPath = new URL('../src/mobileShellUrl.ts', import.meta.url);
const mobileShellUrlPath = new URL('../src/shell/mobileShellUrl.ts', import.meta.url);
const mobileShellUrlSource = fs.readFileSync(mobileShellUrlPath, 'utf8');
const mobileShellRuntimePath = new URL('../src/mobileShellRuntime.ts', import.meta.url);
const mobileShellRuntimePath = new URL('../src/shell/mobileShellRuntime.ts', import.meta.url);
const mobileShellRuntimeSource = fs.readFileSync(mobileShellRuntimePath, 'utf8');
const sharedContractPath = new URL(
'../../../packages/shared/src/contracts/hostBridge.ts',
@@ -20,7 +20,7 @@ import {
type HostBridgeMethod,
type HostBridgeRequest,
type HostBridgeResponse,
} from '../../../packages/shared/src/contracts/hostBridge';
} from '../../../../packages/shared/src/contracts/hostBridge';
import {
configureMobileHostBridgeNavigation,
handleMobileHostBridgeMessage,
@@ -49,10 +49,10 @@ import {
type OpenExternalUrlPayload,
type SetBadgeCountPayload,
type ShareOpenPayload,
} from '../../../packages/shared/src/contracts/hostBridge';
import { resolveMobileShellWebViewUrl } from './mobileShellNavigation';
import { getMobileNetworkStatus } from './mobileShellNetwork';
import { MOBILE_SHELL_HOST_VERSION } from './mobileShellRuntime';
} from '../../../../packages/shared/src/contracts/hostBridge';
import { resolveMobileShellWebViewUrl } from '../shell/mobileShellNavigation';
import { getMobileNetworkStatus } from '../shell/mobileShellNetwork';
import { MOBILE_SHELL_HOST_VERSION } from '../shell/mobileShellRuntime';
const WEB_APP_ORIGIN = 'https://app.genarrative.world';
const EXPORT_TEXT_MAX_BYTES = 5 * 1024 * 1024;
@@ -1,4 +1,4 @@
import { normalizeHostBridgeExternalUrl } from '../../../packages/shared/src/contracts/hostBridge';
import { normalizeHostBridgeExternalUrl } from '../../../../packages/shared/src/contracts/hostBridge';
export function shouldOpenInMobileShellWebView(
rawUrl: string,
@@ -3,7 +3,7 @@ import * as Network from 'expo-network';
import {
type NetworkStatusResult,
normalizeHostBridgeConnectionType,
} from '../../../packages/shared/src/contracts/hostBridge';
} from '../../../../packages/shared/src/contracts/hostBridge';
export function normalizeMobileNetworkStatus(
state: Network.NetworkState,

Some files were not shown because too many files have changed in this diff Show More