收口桌面壳重放诊断日志
桌面壳 HostBridge replay 内部异常只记录固定阶段标签 扩展桌面壳配置门禁禁止 replay 日志输出 Rust 错误细节 补充宿主壳方案和共享决策中的 replay 诊断边界
This commit is contained in:
@@ -2691,10 +2691,11 @@ const requiredRustHostSnippets = [
|
||||
'HostBridgeReplayReservation',
|
||||
'HOST_BRIDGE_RESPONSE_CACHE_MAX',
|
||||
'Result<HostBridgeReplayReservation, HostBridgeResponse>',
|
||||
'log_desktop_replay_failure("cache.lock", &error.to_string())',
|
||||
'desktop host bridge replay failed for {label}: {error}',
|
||||
'log_desktop_replay_failure("cache.lock")',
|
||||
'desktop host bridge replay failed for {label}',
|
||||
'replay_unavailable_response(request_id)',
|
||||
'host_bridge_replay_state_returns_stable_error_when_cache_lock_is_unavailable',
|
||||
'desktop_host_bridge_replay_logs_stable_label_only',
|
||||
'.manage(HostBridgeReplayState::default())',
|
||||
'fn prepare_host_bridge_request(',
|
||||
'prepare_host_bridge_request(&mut request)',
|
||||
@@ -2727,6 +2728,15 @@ if (
|
||||
throw new Error('desktop WebView download diagnostics must not log URLs');
|
||||
}
|
||||
|
||||
if (
|
||||
desktopHostBridgeProtocolSource.includes(
|
||||
'desktop host bridge replay failed for {label}: {error}',
|
||||
) ||
|
||||
desktopHostBridgeProtocolSource.includes('&error.to_string()')
|
||||
) {
|
||||
throw new Error('desktop HostBridge replay diagnostics must not log Rust error details');
|
||||
}
|
||||
|
||||
assertSameList(
|
||||
allowedTauriCommands,
|
||||
['host_bridge_request'],
|
||||
|
||||
@@ -112,8 +112,8 @@ impl HostBridgeReplayState {
|
||||
&self,
|
||||
request_id: &str,
|
||||
) -> Result<HostBridgeReplayReservation, HostBridgeResponse> {
|
||||
let mut cache = self.cache.lock().map_err(|error| {
|
||||
log_desktop_replay_failure("cache.lock", &error.to_string());
|
||||
let mut cache = self.cache.lock().map_err(|_error| {
|
||||
log_desktop_replay_failure("cache.lock");
|
||||
replay_unavailable_response(request_id)
|
||||
})?;
|
||||
if let Some(slot) = cache.slots.get(request_id) {
|
||||
@@ -140,8 +140,8 @@ impl HostBridgeReplayState {
|
||||
) -> HostBridgeResponse {
|
||||
let mut stored_response = match slot.response.lock() {
|
||||
Ok(response) => response,
|
||||
Err(error) => {
|
||||
log_desktop_replay_failure("slot.complete", &error.to_string());
|
||||
Err(_error) => {
|
||||
log_desktop_replay_failure("slot.complete");
|
||||
return replay_unavailable_response(&response.id);
|
||||
}
|
||||
};
|
||||
@@ -153,16 +153,16 @@ impl HostBridgeReplayState {
|
||||
pub(crate) fn wait_for_response(slot: Arc<HostBridgeReplaySlot>) -> HostBridgeResponse {
|
||||
let mut stored_response = match slot.response.lock() {
|
||||
Ok(response) => response,
|
||||
Err(error) => {
|
||||
log_desktop_replay_failure("slot.wait", &error.to_string());
|
||||
Err(_error) => {
|
||||
log_desktop_replay_failure("slot.wait");
|
||||
return replay_unavailable_response(&slot.request_id);
|
||||
}
|
||||
};
|
||||
while stored_response.is_none() {
|
||||
stored_response = match slot.ready.wait(stored_response) {
|
||||
Ok(response) => response,
|
||||
Err(error) => {
|
||||
log_desktop_replay_failure("slot.ready", &error.to_string());
|
||||
Err(_error) => {
|
||||
log_desktop_replay_failure("slot.ready");
|
||||
return replay_unavailable_response(&slot.request_id);
|
||||
}
|
||||
};
|
||||
@@ -185,11 +185,8 @@ impl HostBridgeReplaySlot {
|
||||
}
|
||||
}
|
||||
|
||||
fn log_desktop_replay_failure(label: &str, error: &str) -> bool {
|
||||
if !error.is_empty() {
|
||||
eprintln!("desktop host bridge replay failed for {label}: {error}");
|
||||
}
|
||||
|
||||
fn log_desktop_replay_failure(label: &str) -> bool {
|
||||
eprintln!("desktop host bridge replay failed for {label}");
|
||||
false
|
||||
}
|
||||
|
||||
@@ -466,6 +463,11 @@ mod tests {
|
||||
assert_eq!(error.message, DESKTOP_HOST_BRIDGE_REQUEST_FAILED);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn desktop_host_bridge_replay_logs_stable_label_only() {
|
||||
assert!(!log_desktop_replay_failure("slot.ready"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_string_payload_is_rejected() {
|
||||
let mut invalid = request("clipboard.writeText");
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
- 2026-06-18 HostBridge request id replay:Expo 和 Tauri 壳都必须按 request id 回放首次完成结果;同 id 进行中的请求共享同一执行结果,已完成请求直接回放缓存响应,避免系统分享、外链、剪贴板、文件选择 / 保存、本地通知、窗口导航等宿主副作用被重复触发。两端配置检查和测试会锁住 replay 结构。
|
||||
- 2026-06-18 HostBridge request envelope 校验:共享契约提供 `isHostBridgeMethod` 与 `normalizeHostBridgeRequestId`,Expo 壳直接复用,Tauri 壳镜像同一白名单和 id 规则;空 id、控制字符 id、超长 id 和未知 method 都必须在 replay / 能力分发前返回 `invalid_request`,已知但当前壳未实现的登录 / 支付等 method 才返回 `unsupported_method`。Expo 壳捕获原生异常时只透传共享 `HostBridgeError.code` 白名单内且 `message` 为字符串的协议错误,Tauri 壳的 `failed(...)` 出口也必须先校验同一错误码白名单;未知原生错误对象或非法错误码统一归一为 `host_error` 和固定失败文案,不把 native 私有字段、任意错误码或非字符串 message 回传给 H5。
|
||||
- 2026-06-20 桌面 HostBridge command facade 单测边界:Tauri 唯一 `host_bridge_request` command 必须先通过 `prepare_host_bridge_request(...)` 做 envelope、method 和 request id 校验,再进入 `HostBridgeReplayState` reserve / wait / execute;`apps/desktop-shell/src-tauri/src/host_bridge/mod.rs` 的单测必须覆盖非法 envelope 在 replay 前返回 `invalid_request` 且不会占用对应 request id 的 replay slot,桌面配置检查会反查该测试存在。
|
||||
- 2026-06-20 桌面 HostBridge replay 内部失败边界:Tauri `HostBridgeReplayState` 的 cache lock、slot lock 和 condvar wait 异常不得 panic,也不得把 Rust 内部错误细节回传给 H5;桌面壳只写 `desktop host bridge replay failed for ...` stderr 观测日志,并统一返回 `host_error: desktop host bridge request failed`。桌面配置检查反查 `reserve(...)` 的 `Result` 出口、稳定错误响应和 poison lock 单测。
|
||||
- 2026-06-20 桌面 HostBridge replay 内部失败边界:Tauri `HostBridgeReplayState` 的 cache lock、slot lock 和 condvar wait 异常不得 panic,也不得把 Rust 内部错误细节回传给 H5;桌面壳只写 `desktop host bridge replay failed for ...` 固定阶段标签,不把 mutex / condvar 错误文本写入 stderr,并统一返回 `host_error: desktop host bridge request failed`。桌面配置检查反查 `reserve(...)` 的 `Result` 出口、稳定错误响应、label-only 诊断和 poison lock 单测。
|
||||
- 2026-06-20 移动 HostBridge runtime 能力回包边界:Expo `host.getRuntime` 回包里的 `capabilities` 必须直接等于共享契约 `HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES` 或 `HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES`,并使用与 `platform` 字段一致的归一平台值选择 profile;移动壳 runtime 单测和配置检查反查精确 profile 断言,避免 H5 实际消费的能力回包与入口 URL 能力 query 或共享 profile 分叉。
|
||||
- 2026-06-20 移动 production bundle 宿主上下文边界:`apps/mobile-shell/scripts/check-expo-export.mjs` 必须读取 iOS / Android Metro export bundle,确认可分发 bundle metadata 是 Metro version 0、只包含当前平台 `fileMetadata`、指向 Hermes `AppEntry-*.hbc`,且 bundle 包含共享生产 H5 URL、`native_app`、`expo_mobile`、`hostCapabilities`、`hostVersion` 和 `bridgeVersion`,并不包含本机开发 H5 URL;移动壳配置检查反查 export smoke 的这些 token 和 metadata 结构门禁,避免 production bundle 丢失宿主上下文、混入本机入口或导出形态漂移。
|
||||
- 2026-06-20 桌面 release 主窗口宿主上下文边界:Tauri release 配置只保留基础 `index.html`,Rust app 装配层必须在主窗口启动配置单测里断言补齐 `clientRuntime`、`clientType`、`hostShell`、`hostPlatform`、`hostVersion`、`bridgeVersion` 和 `hostCapabilities`;桌面单端配置检查反查这些断言存在,避免首屏 H5 丢失桌面壳运行态 query 后只靠 runtime 回读补救。
|
||||
|
||||
@@ -497,7 +497,7 @@ GameBridge 禁止:
|
||||
|
||||
2026-06-18 追加:HostBridge request envelope 校验收紧。共享契约提供 `isHostBridgeMethod` 和 `normalizeHostBridgeRequestId`;Expo 壳直接复用,Tauri 壳镜像同一 method 白名单和 request id 规则。空 id、控制字符 id、超长 id 和未知 method 都在进入 replay / 能力分发前返回 `invalid_request`,已知但当前壳未实现的登录 / 支付等 method 才返回 `unsupported_method`。Tauri 唯一 `host_bridge_request` command 入口必须先经过 `prepare_host_bridge_request(...)`,再进入 `HostBridgeReplayState` reserve / wait / execute;非法 envelope 不得占用 replay slot,也不得触发任何宿主能力分发。
|
||||
|
||||
2026-06-20 追加:Tauri 桌面壳 HostBridge replay 的内部锁、等待和缓存状态异常只写入桌面壳 stderr 观测日志,H5 侧统一收到 `host_error` 与固定文案 `desktop host bridge request failed`;`HostBridgeReplayState::reserve(...)` 返回 `Result`,`complete(...)` / `wait_for_response(...)` 也不得用 `expect` 让宿主进程 panic。桌面配置检查会反查 replay 失败日志、稳定错误响应和 poison lock 单测,避免后续把内部 mutex / condvar 细节或 Rust panic 泄露到 H5 调用链。
|
||||
2026-06-20 追加:Tauri 桌面壳 HostBridge replay 的内部锁、等待和缓存状态异常只写入桌面壳 stderr 观测日志,H5 侧统一收到 `host_error` 与固定文案 `desktop host bridge request failed`;stderr 只输出 `desktop host bridge replay failed for <stage>` 固定阶段标签,不记录 mutex / condvar 的 Rust 错误文本。`HostBridgeReplayState::reserve(...)` 返回 `Result`,`complete(...)` / `wait_for_response(...)` 也不得用 `expect` 让宿主进程 panic。桌面配置检查会反查 replay 失败日志、稳定错误响应、label-only 诊断和 poison lock 单测,避免后续把内部 mutex / condvar 细节或 Rust panic 泄露到 H5 调用链。
|
||||
|
||||
2026-06-20 追加:Expo 移动壳 `host.getRuntime` 回包里的 `capabilities` 必须直接等于共享契约中的 `HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES` 或 `HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES`,且使用同一个已归一的平台值选择 profile;移动壳 runtime 单测和配置检查会反查精确 profile 断言,避免 H5 实际消费的能力回包与入口 URL 或共享 profile 分叉。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user