接入桌面壳单实例唤醒
Tauri 桌面壳启用 single-instance 插件 重复启动只唤醒并聚焦已有主窗口 禁止把第二实例参数透传给 H5 补充单实例检查测试和架构文档
This commit is contained in:
@@ -134,6 +134,8 @@ const requiredPermissions = [
|
||||
];
|
||||
const requiredBuildCommands = ['host_bridge_request'];
|
||||
const requiredMainSnippets = [
|
||||
'tauri_plugin_single_instance::init',
|
||||
'resolve_desktop_single_instance_action',
|
||||
'tauri_plugin_clipboard_manager::init()',
|
||||
'TrayIconBuilder::with_id',
|
||||
'register_desktop_tray(app)',
|
||||
@@ -216,6 +218,21 @@ if (!cargoManifest.includes('features = ["tray-icon"]')) {
|
||||
throw new Error('desktop shell must enable the Tauri tray-icon feature');
|
||||
}
|
||||
|
||||
if (!cargoManifest.includes('tauri-plugin-single-instance = "2.4.2"')) {
|
||||
throw new Error('desktop shell must depend on tauri-plugin-single-instance');
|
||||
}
|
||||
|
||||
if (
|
||||
main.indexOf('tauri_plugin_single_instance::init') >
|
||||
main.indexOf('tauri_plugin_clipboard_manager::init()')
|
||||
) {
|
||||
throw new Error('desktop shell must register the single-instance plugin first');
|
||||
}
|
||||
|
||||
if (main.includes('single-instance",') || main.includes('"single-instance"')) {
|
||||
throw new Error('desktop shell must not emit secondary-instance argv to H5');
|
||||
}
|
||||
|
||||
const icon = fs.readFileSync(iconPath);
|
||||
if (icon.length < 10000) {
|
||||
throw new Error('desktop shell icon must use a real brand asset');
|
||||
|
||||
16
apps/desktop-shell/src-tauri/Cargo.lock
generated
16
apps/desktop-shell/src-tauri/Cargo.lock
generated
@@ -1286,6 +1286,7 @@ dependencies = [
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-notification",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-single-instance",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3799,6 +3800,21 @@ dependencies = [
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-single-instance"
|
||||
version = "2.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8f29386f5e9fdc699182388a33ee80a56de436d91b67459e86afef426282af"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"thiserror 2.0.18",
|
||||
"tracing",
|
||||
"windows-sys 0.60.2",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.11.2"
|
||||
|
||||
@@ -16,3 +16,4 @@ tauri-plugin-clipboard-manager = "2.3.2"
|
||||
tauri-plugin-dialog = "2.7.1"
|
||||
tauri-plugin-notification = "2.3.3"
|
||||
tauri-plugin-opener = "2.5.4"
|
||||
tauri-plugin-single-instance = "2.4.2"
|
||||
|
||||
@@ -904,6 +904,11 @@ enum DesktopWindowCloseAction {
|
||||
CloseWindow,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum DesktopSingleInstanceAction {
|
||||
ShowMainWindow,
|
||||
}
|
||||
|
||||
fn resolve_desktop_tray_menu_action(menu_id: &str) -> DesktopTrayAction {
|
||||
match menu_id {
|
||||
TRAY_MENU_SHOW => DesktopTrayAction::ShowMainWindow,
|
||||
@@ -932,6 +937,10 @@ fn resolve_desktop_window_close_action(tray_registered: bool) -> DesktopWindowCl
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_desktop_single_instance_action() -> DesktopSingleInstanceAction {
|
||||
DesktopSingleInstanceAction::ShowMainWindow
|
||||
}
|
||||
|
||||
fn show_main_window(app: &tauri::AppHandle) -> tauri::Result<()> {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
window.show()?;
|
||||
@@ -1470,6 +1479,13 @@ async fn host_bridge_request(
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.manage(DesktopShareState::default())
|
||||
.plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| {
|
||||
match resolve_desktop_single_instance_action() {
|
||||
DesktopSingleInstanceAction::ShowMainWindow => {
|
||||
let _ = show_main_window(app);
|
||||
}
|
||||
}
|
||||
}))
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
@@ -1773,6 +1789,14 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn desktop_single_instance_only_restores_existing_window() {
|
||||
assert_eq!(
|
||||
resolve_desktop_single_instance_action(),
|
||||
DesktopSingleInstanceAction::ShowMainWindow
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_url_normalization_allows_only_safe_protocols() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
- 2026-06-18 剪贴板读取能力:新增 `clipboard.readText` HostBridge capability,H5 只能读取纯文本结果,契约限制返回文本最多 100000 字符;Expo 壳通过 `expo-clipboard` 读取系统剪贴板文本,Tauri 壳通过 Rust 侧 `tauri-plugin-clipboard-manager` 读取文本且不开放插件 JS guest API。该能力不读取图片、HTML、文件列表或剪贴板监听事件,宿主未声明或读取失败时由 H5 视作失败并保留原流程。
|
||||
- 2026-06-18 文本文件导入能力:新增 `file.importText` HostBridge capability,H5 统一通过 `importHostTextFile()` 读取宿主返回的纯文本内容;Expo 壳通过 `expo-document-picker` 打开系统文档选择器,Tauri 壳通过系统文件选择框读取真实文本文件。两端只接受 `text/plain`、`text/markdown`、`text/csv`、`application/json` 或对应扩展名,单次不超过 5 MiB,成功只返回清洗后的文件名、MIME、UTF-8 文本内容和字节数,不暴露设备 URI / 本机绝对路径,也不开放通用文件系统。
|
||||
- 2026-06-18 Tauri 系统托盘:桌面壳启用真实 OS 托盘并复用品牌图标,托盘菜单只执行显示主窗口、刷新主窗口和退出应用,左键点击托盘图标恢复并聚焦主窗口;该能力归桌面壳自身,不进入 HostBridge capability,不向 H5 暴露托盘、菜单、shell 或任意窗口控制 API。托盘注册成功时主窗口关闭按钮只隐藏到托盘,必须通过托盘“退出”结束应用;托盘注册失败不得阻断主窗口启动,也不得拦截关闭,避免窗口消失后无法恢复。`check:native-shells` 和 Tauri cargo test 覆盖托盘配置、菜单动作映射和关闭策略。
|
||||
- 2026-06-18 Tauri 单实例:桌面壳启用 `tauri-plugin-single-instance` 并要求该插件最先注册;重复启动 App 时第二实例退出,只唤醒、取消最小化并聚焦已有主窗口,不把第二实例 argv / cwd / 深链内容作为事件透传给 H5。桌面深链后续如需接入,必须先定义受控 URL 归一和宿主边界,不能借单实例回调直接开放任意启动参数。
|
||||
- 影响范围:`src/services/host-bridge/`、未来 `apps/mobile-shell/`、未来 `apps/desktop-shell/`、移动端支付 / 分享 / 深链 / 推送、桌面端系统能力、AI H5 sandbox 的 GameBridge 边界。
|
||||
- 验证方式:普通浏览器、小程序、Expo 壳、Tauri 壳都能返回正确 `getHostRuntime()`;未支持能力能回退 H5;固定玩法在各宿主中读取同一作品数据和运行态 snapshot;AI sandbox 无法直接调用 HostBridge;Tauri release 不允许任意远端页面调用桌面命令。
|
||||
- 关联文档:`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md`、`docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`。
|
||||
|
||||
@@ -295,6 +295,8 @@ GameBridge 禁止:
|
||||
|
||||
2026-06-18 追加:桌面壳启用 Tauri 真实系统托盘,并复用品牌图标。托盘菜单只提供宿主壳级动作:显示主窗口、刷新主窗口和退出应用;左键点击托盘图标恢复并聚焦主窗口。该能力不进入 HostBridge capability 清单,不向 H5 暴露托盘 API、菜单 API、shell API 或任意窗口控制;如果当前桌面环境无法注册托盘,壳会继续启动主窗口。托盘注册成功时,用户点击主窗口关闭按钮会隐藏到托盘,必须通过托盘“退出”动作结束应用;托盘注册失败时不拦截关闭,避免窗口消失后没有恢复入口。
|
||||
|
||||
2026-06-18 追加:桌面壳启用 Tauri 单实例。用户重复启动桌面 App 时,新实例会退出并唤醒已有主窗口;该回调只执行显示、取消最小化和聚焦主窗口,不把第二实例的命令行参数、工作目录或深链内容作为事件透传给 H5。桌面深链若后续需要接入,必须单独定义受控来源、路径归一和 HostBridge / GameBridge 边界。
|
||||
|
||||
### Phase 4:宿主能力扩展
|
||||
|
||||
- 移动端接入系统分享、推送、原生登录和渠道支付。
|
||||
|
||||
Reference in New Issue
Block a user