收口桌面壳网络探测来源
桌面壳网络探测从 WEB_APP_ORIGIN 解析主站 host 和端口 桌面壳配置检查禁止 network.rs 重新写死公开主站域名 同步 HostBridge 协议文档、壳方案文档和共享决策记录
This commit is contained in:
@@ -68,6 +68,14 @@ const desktopShellUrlPath = new URL(
|
||||
import.meta.url,
|
||||
);
|
||||
const desktopShellUrlSource = fs.readFileSync(desktopShellUrlPath, 'utf8');
|
||||
const desktopShellNetworkPath = new URL(
|
||||
'../src-tauri/src/shell/network.rs',
|
||||
import.meta.url,
|
||||
);
|
||||
const desktopShellNetworkSource = fs.readFileSync(
|
||||
desktopShellNetworkPath,
|
||||
'utf8',
|
||||
);
|
||||
const productionSourceRoots = [
|
||||
new URL('../package.json', import.meta.url),
|
||||
new URL('../src-tauri/Cargo.toml', import.meta.url),
|
||||
@@ -1103,6 +1111,23 @@ if (desktopPublicWebOrigin !== sharedPublicWebOrigin) {
|
||||
);
|
||||
}
|
||||
|
||||
for (const expectedNetworkSnippet of [
|
||||
'use crate::shell::url::WEB_APP_ORIGIN',
|
||||
'Url::parse(WEB_APP_ORIGIN)',
|
||||
'url.host_str()',
|
||||
'url.port_or_known_default()',
|
||||
]) {
|
||||
if (!desktopShellNetworkSource.includes(expectedNetworkSnippet)) {
|
||||
throw new Error(
|
||||
`desktop shell network probe must derive its target from WEB_APP_ORIGIN: missing ${expectedNetworkSnippet}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (desktopShellNetworkSource.includes('"app.genarrative.world"')) {
|
||||
throw new Error('desktop shell network probe must not duplicate the public web host');
|
||||
}
|
||||
|
||||
for (const expectedShareSnippet of [
|
||||
'normalize_public_share_url',
|
||||
'raw_url.starts_with("//")',
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
use crate::shell::events::host_bridge_event_script;
|
||||
use crate::shell::url::WEB_APP_ORIGIN;
|
||||
use serde_json::{json, Value};
|
||||
use std::net::{TcpStream, ToSocketAddrs};
|
||||
use std::time::Duration;
|
||||
use tauri::WebviewWindow;
|
||||
use tauri::{Url, WebviewWindow};
|
||||
|
||||
const DESKTOP_NETWORK_CHECK_TIMEOUT_MS: u64 = 1200;
|
||||
|
||||
fn desktop_network_probe_target() -> Option<(String, u16)> {
|
||||
let url = Url::parse(WEB_APP_ORIGIN).ok()?;
|
||||
if url.scheme() != "https" {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((
|
||||
url.host_str()?.to_string(),
|
||||
url.port_or_known_default()?,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn desktop_network_status_payload(is_online: bool) -> Value {
|
||||
json!({
|
||||
"isConnected": is_online,
|
||||
@@ -17,8 +30,8 @@ pub(crate) fn desktop_network_status_payload(is_online: bool) -> Value {
|
||||
|
||||
pub(crate) fn resolve_desktop_network_status() -> Value {
|
||||
let timeout = Duration::from_millis(DESKTOP_NETWORK_CHECK_TIMEOUT_MS);
|
||||
let is_reachable = ("app.genarrative.world", 443)
|
||||
.to_socket_addrs()
|
||||
let is_reachable = desktop_network_probe_target()
|
||||
.and_then(|(host, port)| (host.as_str(), port).to_socket_addrs().ok())
|
||||
.map(|addresses| {
|
||||
addresses.into_iter().any(|address| {
|
||||
TcpStream::connect_timeout(&address, timeout)
|
||||
@@ -89,6 +102,21 @@ mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn desktop_network_probe_target_uses_public_web_origin() {
|
||||
let origin = Url::parse(WEB_APP_ORIGIN).expect("desktop web origin");
|
||||
|
||||
assert_eq!(
|
||||
desktop_network_probe_target(),
|
||||
Some((
|
||||
origin.host_str().expect("desktop web host").to_string(),
|
||||
origin
|
||||
.port_or_known_default()
|
||||
.expect("desktop web default port")
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn desktop_network_status_payload_reports_reachability() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
- 2026-06-18 草稿生成未读角标:平台壳层把“可见作品架里未读的草稿生成完成更新”同步到 `app.setBadgeCount`;同一草稿的 work/profile/session 等多个恢复 ID 只计 1,已读、失败、生成中和不可见草稿不计入。该角标只消费已有 HostBridge 能力,宿主不支持或设置失败不影响 H5 红点、作品架或后端状态。
|
||||
- 2026-06-18 宿主外观只读查询:新增 `appearance.getColorScheme` HostBridge capability,Expo 壳通过 React Native `Appearance.getColorScheme()` 读取系统配色,Tauri 壳通过主窗口 `theme()` 读取窗口主题;该能力只返回 `light` / `dark` / `unknown`,不设置 H5 主题、不覆盖系统主题,也不作为强制 UI 样式入口。
|
||||
- 2026-06-18 原生壳生命周期事件:新增 `app.lifecycle` HostBridge capability,Expo 壳通过 React Native `AppState` 派发 `active` / `inactive` / `background`,Tauri 壳通过主窗口 focus / blur、托盘隐藏 / 恢复和页面加载重放派发统一状态;桌面隐藏到托盘或最小化都归一为 `background`,`hidden`、`minimized`、`focused`、`blurred` 只进入 `nativeState` 便于排障,不扩展共享 `state`。两端都声明 `host.events` 表示事件通过 HostBridge message 注入,但不把它作为 request method,也不开放 Tauri event 插件或 React Native 私有事件 API。H5 只通过 `subscribeHostAppLifecycle()` 订阅统一状态,后续游戏循环、音频和轮询暂停 / 恢复不得直接依赖 Expo / Tauri 平台细节。
|
||||
- 2026-06-18 原生壳网络状态:新增 `network.status` 与 `network.statusChanged` HostBridge capability,Expo 壳通过 `expo-network` 查询和订阅真实系统网络状态,Tauri 壳通过短超时连接 `app.genarrative.world:443` 查询主站可达性,并通过 WebView `online` / `offline` 注入变化事件;H5 统一使用 `getHostNetworkStatus()` / `subscribeHostNetworkStatusChange()`,不得直接读取 Expo / Tauri 私有网络 API。
|
||||
- 2026-06-18 原生壳网络状态:新增 `network.status` 与 `network.statusChanged` HostBridge capability,Expo 壳通过 `expo-network` 查询和订阅真实系统网络状态,Tauri 壳从 `WEB_APP_ORIGIN` 解析主站 host / port 后做短超时 TCP 可达性查询,并通过 WebView `online` / `offline` 注入变化事件;H5 统一使用 `getHostNetworkStatus()` / `subscribeHostNetworkStatusChange()`,不得直接读取 Expo / Tauri 私有网络 API。
|
||||
- 2026-06-18 移动壳 WebView 状态重放:Expo WebView 每次同源主站页面成功加载后都会补发当前 `app.lifecycle` 与 `network.statusChanged` 状态,覆盖首载、受控刷新、H5 刷新和系统回收 WebView 进程后的新 JS 上下文;补发不新增 HostBridge capability,也不向 `about:blank`、外域或错误页注入宿主状态。
|
||||
- 2026-06-18 桌面壳 WebView 状态重放:Tauri 主 WebView 每次页面加载完成后都会回放当前 `app.lifecycle` 并重新安装 `network.statusChanged` 监听脚本,覆盖托盘刷新、`app.reloadWebView` 和 H5 自刷新后的新 JS 上下文;桌面 runtime 同步声明 `host.events` 表示该真实事件通道可用,但仍不开放 Tauri event 插件或额外 command。
|
||||
- 2026-06-18 桌面壳 H5 返回栈事件:Tauri 壳开始声明 `navigation.canGoBack`,但只通过固定注入脚本追踪当前 H5 文档内的 `pushState` / `replaceState` / `popstate` 路由栈并派发 HostBridge event;不把该能力实现为 request method,不开放 H5 到 Tauri 的 event 写入通道,也不声明跨文档 native back-forward list 真相。
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -48,7 +48,7 @@ Tauri 桌面壳启动时必须按 `label="main"` 解析 `tauri.conf.json` 主窗
|
||||
- `getHostRuntime()`:识别 `browser`、`wechat_mini_program`、`native_app`,并解析 `hostCapabilities` 能力声明;进入 `native_app` 后会通过真实 `host.getRuntime` 回读宿主 runtime 并缓存能力清单,未知能力会被丢弃。H5 业务只根据已声明或已回读的能力展示入口、发起宿主请求或走 fallback。
|
||||
- `getHostAppearanceColorScheme()`:原生 App 宿主的受控外观查询入口。H5 可通过 `appearance.getColorScheme` 读取宿主当前 `light` / `dark` / `unknown` 配色模式;Expo 移动壳通过 React Native `Appearance.getColorScheme()` 读取系统偏好,Tauri 桌面壳通过主窗口 `theme()` 读取窗口主题。该能力只读,不改变 H5 主题,也不覆盖用户或系统偏好。
|
||||
- `subscribeHostAppLifecycle()`:原生 App 宿主的受控生命周期事件入口。Expo 移动壳和 Tauri 桌面壳都声明 `host.events`,表示宿主会通过 HostBridge message 派发事件;其中 Expo 移动壳通过 React Native `AppState` 派发 `app.lifecycle`,Tauri 桌面壳通过主窗口 focus / blur、托盘隐藏 / 恢复和页面加载重放派发同名事件。桌面壳不会把 hidden、minimized 或 tray 扩成新的 `state`,而是读取 `is_visible()`、`is_minimized()`、`is_focused()` 后统一归一为 `active` / `inactive` / `background`,并只把 `hidden`、`minimized`、`focused`、`blurred` 放进 `nativeState` 用于排障。`host.events` 不作为 request method,也不开放 Tauri event 插件或 React Native 私有事件 API。H5 只依赖统一的 `active` / `inactive` / `background` 状态和 `focused` 布尔值,原生细分状态只放在 `nativeState` 用于排障,不作为业务分支依据。H5 统一通过 `useHostLifecycleActive()` 把宿主状态折算为运行态可播放状态;WebAudio 背景音乐和固定玩法 `<audio>` 背景音乐都必须按该状态暂停 / 恢复,宿主进入后台、inactive 或窗口失焦时暂停,回到 active 且 focused 后只在原运行态、音源和用户音量仍允许时恢复。
|
||||
- `getHostNetworkStatus()` / `subscribeHostNetworkStatusChange()`:原生 App 宿主的受控网络状态入口。Expo 移动壳通过 `expo-network` 查询并订阅真实系统网络状态;Tauri 桌面壳通过短超时连接 `app.genarrative.world:443` 查询主站可达性,并在主 WebView 内监听 `online` / `offline` 注入变化事件。H5 只依赖统一的 `isConnected`、`isInternetReachable` 和连接类型,不直接读取平台私有网络 API。平台外部生成队列概览通过 `useHostNetworkOnline()` 消费该状态,宿主未声明网络能力时保持原轮询行为,宿主明确离线或不可达时暂停轮询,恢复在线后重新刷新;该状态不替代后端队列事实或生成结果回读。
|
||||
- `getHostNetworkStatus()` / `subscribeHostNetworkStatusChange()`:原生 App 宿主的受控网络状态入口。Expo 移动壳通过 `expo-network` 查询并订阅真实系统网络状态;Tauri 桌面壳从 `WEB_APP_ORIGIN` 解析主站 host / port 后做短超时 TCP 可达性查询,并在主 WebView 内监听 `online` / `offline` 注入变化事件。H5 只依赖统一的 `isConnected`、`isInternetReachable` 和连接类型,不直接读取平台私有网络 API。平台外部生成队列概览通过 `useHostNetworkOnline()` 消费该状态,宿主未声明网络能力时保持原轮询行为,宿主明确离线或不可达时暂停轮询,恢复在线后重新刷新;该状态不替代后端队列事实或生成结果回读。
|
||||
- `requestHostLogin()`:微信小程序跳转原生登录页;浏览器返回 `false`,由 H5 登录弹窗承接。
|
||||
- `requestHostPayment()`:微信小程序支付跳转原生支付页;其它渠道返回 `false`,继续走 H5 / Native 二维码。
|
||||
- `setHostShareTarget()`:把当前公开作品分享目标同步给宿主。
|
||||
|
||||
Reference in New Issue
Block a user