收口微信认证诊断日志

微信 web-view 认证失败日志改为固定阶段标签

原生壳门禁拒绝认证日志输出原生错误对象

同步宿主壳方案和共享决策记录
This commit is contained in:
2026-06-21 15:47:00 +08:00
parent c84c77616c
commit 3991b6cc23
5 changed files with 53 additions and 22 deletions
@@ -106,6 +106,7 @@
- 2026-06-21 微信 WebView env 诊断日志收口:微信 `web-view` 壳读取小程序 envVersion 失败时只记录 `[web-view] read mini program env failed` 固定标签,不输出 `wx.getAccountInfoSync()` 原生异常对象;根级原生壳门禁拒绝恢复 `console.warn(..., error)`。
- 2026-06-21 微信九宫切图诊断日志收口:微信 `share-grid` 壳下载封面、读取图片、导出切图、保存相册或最终保存流程失败时,只记录 `[share-grid] <stage>` 固定标签,不输出 `wx.downloadFile`、`wx.getImageInfo`、`wx.canvasToTempFilePath`、`wx.saveImageToPhotosAlbum` 或其它原生错误对象;页面仍只展示 `九宫切图保存失败。` 稳定文案。
- 2026-06-21 微信支付与订阅诊断日志收口:微信支付参数解析、虚拟支付失败和订阅消息请求失败只记录 `[wechat-pay] <stage>` / `[subscribe-message] <stage>` 固定标签,不输出微信原生错误对象;H5 回灌继续只使用稳定 `wechat payment unavailable` / `wechat subscribe unavailable` 语义。
- 2026-06-21 微信 WebView 认证诊断日志收口:微信 `web-view` 壳解析认证结果、`wx.login`、小程序登录请求、手机号绑定请求、认证流程和手机号授权拒绝失败时,只记录 `[web-view] <stage>` 固定标签,不输出微信原生错误对象、HTTP response 或授权 detail;页面仍只展示稳定登录 / 绑手机号错误文案。
- 2026-06-19 微信壳路由一致性门禁:`npm run check:native-shells` 必须反查 `miniprogram/app.json.pages`、`miniprogram/host-bridge/protocol.js`、H5 `src/services/host-bridge/hostBridge.ts` 小程序页面常量、H5 `src/services/wechatMiniProgramSubscribe.ts` 订阅授权页面常量、`miniprogram/host-bridge/webView.js` 分享入口 / 分享消息类型、`miniprogram/config.js` source query / 域名格式、`miniprogram/shell/webView.js` 请求头来源标记、H5 runtime parser 和 H5 路由保留字段。新增或调整小程序页面、登录派生 URL、支付页、九宫切图页、订阅页、WebView 来源标记、H5 入口域名、API base URL 或宿主上下文 query 字段时,必须同步这几处常量并保持生产 / 开发域名都显式配置为纯 HTTPS domain;运行时开发域名回退生产域名只作为异常兜底。
- 2026-06-18 登录 / 支付能力禁伪声明:`auth.requestLogin` 和 `payment.request` 保留在共享 HostBridge 契约中供未来真实接入,但 Expo / Tauri 壳在真实 SDK、渠道流程和后端契约落地前不得声明这些 capability,也不得把它们写入入口 URL `hostCapabilities`;两端检查脚本会拒绝伪声明,请求实际到达壳层时必须返回明确 `unsupported_method` 并让 H5 fallback,两端壳测试直接覆盖这两个 method。
- 2026-06-20 桌面壳未声明 method 禁伪成功:Tauri 桌面壳只声明真实可用 capability;共享 HostBridge method 白名单中未进入桌面 capability profile 的 method,例如 `auth.requestLogin`、`payment.request`、`file.captureImage`、`scanner.scanQrCode` 和 `haptics.impact`,请求实际到达桌面壳时必须统一返回明确 `unsupported_method`,不得伪造成功或半接入。桌面 Rust 测试必须从 `HOST_BRIDGE_METHODS` 与 `capabilities()` 差集派生 unsupported 覆盖清单,桌面单端配置检查会反查该派生路径,后续共享契约新增 method 时必须同步声明真实桌面能力或补进 unsupported 语义。
@@ -542,6 +542,8 @@ GameBridge 禁止:
2026-06-21 追加:微信支付参数解析、虚拟支付失败和订阅消息请求失败诊断只允许记录 `[wechat-pay] <stage>` / `[subscribe-message] <stage>` 固定标签,不把微信原生错误对象写入生产日志;H5 回灌仍只使用稳定支付 / 订阅失败语义。
2026-06-21 追加:微信 `web-view` 认证链路诊断只允许记录 `[web-view] <stage>` 固定标签,不把 `wx.login` 错误、登录 / 绑手机号 HTTP response、手机号授权 detail 或认证异常对象写入生产日志;页面仍只展示稳定登录 / 绑手机号错误文案。
2026-06-19 追加:微信小程序壳路由一致性进入原生壳统一验收。`npm run check:native-shells` 会反查 `miniprogram/app.json.pages` 与 `miniprogram/host-bridge/protocol.js` 页面 URL 常量一致,H5 `src/services/host-bridge/hostBridge.ts` 的小程序登录、支付、九宫切图页面常量、H5 `src/services/wechatMiniProgramSubscribe.ts` 的订阅授权页面常量与微信协议常量一致,`miniprogram/host-bridge/webView.js` 的 WebView 分享入口和分享目标消息类型不漂移;同时会校验 `miniprogram/config.js` 的生产 / 开发 H5 入口与 API base URL 都是显式配置的纯 HTTPS 域名,`WEB_VIEW_SOURCE_QUERY` 与共享 `HOST_BRIDGE_WECHAT_MINI_PROGRAM_SOURCE_QUERY` 一致,`miniprogram/shell/webView.js` 请求头从 `WEB_VIEW_SOURCE_QUERY` 读取 `clientType` / `clientRuntime`,H5 runtime parser 读取共享 `HOST_BRIDGE_RUNTIME_CONTEXT_QUERY_KEY`,H5 路由保留字段读取共享 `HOST_BRIDGE_PRESERVED_RUNTIME_CONTEXT_QUERY_KEYS`。运行时对开发域名的生产域名回退只作为异常兜底,不作为配置口径;新增小程序页面、改页面路径、调整来源 query 或切换域名格式时,必须同步协议常量、H5 HostBridge / 订阅服务常量、`app.json`、共享 HostBridge query 契约和这条门禁。
2026-06-18 追加:原生壳本地生成物不作为生产源码门禁输入。Expo `.expo/`、Expo export smoke 临时目录、Tauri `target/`、Tauri schema `gen/` 和 Tauri 自动生成权限目录都必须保持 gitignored;根级生产壳敏感词扫描只检查可提交的壳源码和配置,避免本机工具输出影响生产门禁。手写 capability / 权限配置仍需保留在扫描范围内。
+14 -10
View File
@@ -177,6 +177,10 @@ function readMiniProgramEnvVersion() {
}
}
function logWebViewAuthFailure(label, _detail) {
console.error(`[web-view] ${label}`);
}
function resolveMiniProgramRuntimeConfig() {
const miniProgramEnv =
normalizeMiniProgramEnv(readMiniProgramEnvVersion()) ||
@@ -252,7 +256,7 @@ function consumeAuthResult() {
bindingStatus: String(parsed.bindingStatus || 'pending_bind_phone'),
};
} catch (error) {
console.error('[web-view] parse auth result failed', error);
logWebViewAuthFailure('parse auth result failed', error);
return null;
}
}
@@ -288,11 +292,11 @@ function wxLogin() {
resolve(result.code);
return;
}
console.error('[web-view] wx.login returned no code', result);
logWebViewAuthFailure('wx.login returned no code', result);
reject(new Error(WECHAT_LOGIN_UNAVAILABLE_MESSAGE));
},
fail(error) {
console.error('[web-view] wx.login failed', error);
logWebViewAuthFailure('wx.login failed', error);
reject(new Error(WECHAT_LOGIN_UNAVAILABLE_MESSAGE));
},
});
@@ -329,11 +333,11 @@ function requestMiniProgramLogin(code, displayName) {
resolve(response.data);
return;
}
console.error('[web-view] mini program login failed', response);
logWebViewAuthFailure('mini program login failed', response);
reject(new Error(WECHAT_LOGIN_UNAVAILABLE_MESSAGE));
},
fail(error) {
console.error('[web-view] mini program login request failed', error);
logWebViewAuthFailure('mini program login request failed', error);
reject(new Error(WECHAT_LOGIN_UNAVAILABLE_MESSAGE));
},
});
@@ -371,11 +375,11 @@ function requestMiniProgramBindPhone(authToken, wechatPhoneCode, displayName) {
resolve(response.data);
return;
}
console.error('[web-view] mini program bind phone failed', response);
logWebViewAuthFailure('mini program bind phone failed', response);
reject(new Error(WECHAT_BIND_PHONE_UNAVAILABLE_MESSAGE));
},
fail(error) {
console.error('[web-view] mini program bind phone request failed', error);
logWebViewAuthFailure('mini program bind phone request failed', error);
reject(new Error(WECHAT_BIND_PHONE_UNAVAILABLE_MESSAGE));
},
});
@@ -543,7 +547,7 @@ function createWechatWebViewPage() {
webViewUrl: resolveWebViewUrl(authResult, this._lastLaunchQuery || {}),
});
} catch (error) {
console.error('[web-view] auth flow failed', error);
logWebViewAuthFailure('auth flow failed', error);
this.setData({
authResult: null,
errorMessage: WECHAT_LOGIN_UNAVAILABLE_MESSAGE,
@@ -598,7 +602,7 @@ function createWechatWebViewPage() {
const detail = event.detail || {};
if (!detail.code) {
console.error('[web-view] bind phone auth declined', detail);
logWebViewAuthFailure('bind phone auth declined', detail);
this.setData({
errorMessage: WECHAT_BIND_PHONE_AUTH_REQUIRED_MESSAGE,
});
@@ -649,7 +653,7 @@ function createWechatWebViewPage() {
),
});
} catch (error) {
console.error('[web-view] bind phone failed', error);
logWebViewAuthFailure('bind phone failed', error);
this.setData({
bindingPhone: false,
errorMessage: WECHAT_BIND_PHONE_UNAVAILABLE_MESSAGE,
+30 -4
View File
@@ -2753,10 +2753,18 @@ function assertWechatAuthFailureBoundaries() {
"WECHAT_LOGIN_UNAVAILABLE_MESSAGE = '微信登录失败,请稍后重试。'",
"WECHAT_BIND_PHONE_UNAVAILABLE_MESSAGE = '绑定手机号失败,请稍后重试。'",
"WECHAT_BIND_PHONE_AUTH_REQUIRED_MESSAGE = '需要授权手机号后才能完成绑定。'",
"console.error('[web-view] wx.login failed', error)",
"console.error('[web-view] mini program login request failed', error)",
"console.error('[web-view] mini program bind phone failed', response)",
"console.error('[web-view] bind phone auth declined', detail)",
'function logWebViewAuthFailure(label, _detail)',
'console.error(`[web-view] ${label}`)',
"logWebViewAuthFailure('parse auth result failed', error)",
"logWebViewAuthFailure('wx.login returned no code', result)",
"logWebViewAuthFailure('wx.login failed', error)",
"logWebViewAuthFailure('mini program login failed', response)",
"logWebViewAuthFailure('mini program login request failed', error)",
"logWebViewAuthFailure('mini program bind phone failed', response)",
"logWebViewAuthFailure('mini program bind phone request failed', error)",
"logWebViewAuthFailure('auth flow failed', error)",
"logWebViewAuthFailure('bind phone auth declined', detail)",
"logWebViewAuthFailure('bind phone failed', error)",
'errorMessage: WECHAT_LOGIN_UNAVAILABLE_MESSAGE',
'errorMessage: WECHAT_BIND_PHONE_UNAVAILABLE_MESSAGE',
'errorMessage: WECHAT_BIND_PHONE_AUTH_REQUIRED_MESSAGE',
@@ -2772,6 +2780,16 @@ function assertWechatAuthFailureBoundaries() {
"error && error.message ? error.message : '微信登录失败,请稍后重试。'",
"detail.errMsg || '需要授权手机号后才能完成绑定。'",
'response.data.error.message',
"console.error('[web-view] parse auth result failed', error)",
"console.error('[web-view] wx.login returned no code', result)",
"console.error('[web-view] wx.login failed', error)",
"console.error('[web-view] mini program login failed', response)",
"console.error('[web-view] mini program login request failed', error)",
"console.error('[web-view] mini program bind phone failed', response)",
"console.error('[web-view] mini program bind phone request failed', error)",
"console.error('[web-view] auth flow failed', error)",
"console.error('[web-view] bind phone auth declined', detail)",
"console.error('[web-view] bind phone failed', error)",
]) {
if (webViewShellSource.includes(forbiddenSnippet)) {
throw new Error(`wechat auth shell must not expose native failure detail via ${forbiddenSnippet}`);
@@ -2784,6 +2802,14 @@ function assertWechatAuthFailureBoundaries() {
"expect(page.data.errorMessage).toBe('微信登录失败,请稍后重试。')",
"expect(page.data.errorMessage).toBe('绑定手机号失败,请稍后重试。')",
"expect(page.data.errorMessage).toBe('需要授权手机号后才能完成绑定。')",
"expect(console.error).toHaveBeenCalledWith('[web-view] wx.login failed')",
"expect(console.error).toHaveBeenCalledWith('[web-view] auth flow failed')",
'expect(console.error.mock.calls.flat()).not.toContain(loginError)',
"expect(console.error).toHaveBeenCalledWith(\n '[web-view] mini program bind phone failed'",
"expect(console.error).toHaveBeenCalledWith('[web-view] bind phone failed')",
"expect(console.error.mock.calls.flat()).not.toContain('private backend detail')",
"expect(console.error).toHaveBeenCalledWith(\n '[web-view] bind phone auth declined'",
'expect(console.error.mock.calls.flat()).not.toContain(authDeclined)',
]) {
if (!authTestSource.includes(snippet)) {
throw new Error(`wechat auth boundary test must include ${snippet}`);
+6 -8
View File
@@ -319,10 +319,9 @@ describe('mini-program web-view auth page', () => {
await page.onLoad({ authAction: 'login', returnTo: 'previous' });
expect(page.data.errorMessage).toBe('微信登录失败,请稍后重试。');
expect(console.error).toHaveBeenCalledWith(
'[web-view] wx.login failed',
loginError,
);
expect(console.error).toHaveBeenCalledWith('[web-view] wx.login failed');
expect(console.error).toHaveBeenCalledWith('[web-view] auth flow failed');
expect(console.error.mock.calls.flat()).not.toContain(loginError);
expect(page.data.phoneBindingRequired).toBe(false);
});
@@ -353,10 +352,9 @@ describe('mini-program web-view auth page', () => {
expect(page.data.errorMessage).toBe('绑定手机号失败,请稍后重试。');
expect(console.error).toHaveBeenCalledWith(
'[web-view] mini program bind phone failed',
expect.objectContaining({
statusCode: 500,
}),
);
expect(console.error).toHaveBeenCalledWith('[web-view] bind phone failed');
expect(console.error.mock.calls.flat()).not.toContain('private backend detail');
});
test('拒绝手机号授权不向页面透出微信原生错误', async () => {
@@ -378,7 +376,7 @@ describe('mini-program web-view auth page', () => {
expect(page.data.errorMessage).not.toContain('private native detail');
expect(console.error).toHaveBeenCalledWith(
'[web-view] bind phone auth declined',
authDeclined,
);
expect(console.error.mock.calls.flat()).not.toContain(authDeclined);
});
});