From f641d2b25f9c4f35390f5fabfb75c3cf0d5f9be9 Mon Sep 17 00:00:00 2001 From: kdletters Date: Sat, 20 Jun 2026 13:26:45 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=81=E5=AE=9A=E5=BE=AE=E4=BF=A1=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E5=A4=B1=E8=B4=A5=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 微信登录和绑手机号失败不再向页面透出原生或后端错误细节 微信 web-view auth 测试覆盖登录失败和绑手机号失败稳定提示 native shell 总门禁反查微信 auth 失败边界 --- miniprogram/shell/webView.js | 42 ++++++++---------- scripts/check-native-shells.mjs | 49 +++++++++++++++++++++ scripts/miniprogram-web-view-auth.test.ts | 52 +++++++++++++++++++++++ 3 files changed, 119 insertions(+), 24 deletions(-) diff --git a/miniprogram/shell/webView.js b/miniprogram/shell/webView.js index bad9b202d..a3acaa47d 100644 --- a/miniprogram/shell/webView.js +++ b/miniprogram/shell/webView.js @@ -24,6 +24,8 @@ const AUTH_RESULT_STORAGE_KEY = 'genarrative:mini-program-auth-result'; const AUTH_ACTION_LOGIN = 'login'; const PAY_RESULT_RECHECK_DELAY_MS = 120; const WEB_VIEW_SHARE_TITLE = '陶泥儿'; +const WECHAT_LOGIN_UNAVAILABLE_MESSAGE = '微信登录失败,请稍后重试。'; +const WECHAT_BIND_PHONE_UNAVAILABLE_MESSAGE = '绑定手机号失败,请稍后重试。'; function showWebViewShareMenu() { if (typeof wx.showShareMenu !== 'function') { @@ -281,10 +283,12 @@ function wxLogin() { resolve(result.code); return; } - reject(new Error('微信登录未返回 code')); + console.error('[web-view] wx.login returned no code', result); + reject(new Error(WECHAT_LOGIN_UNAVAILABLE_MESSAGE)); }, fail(error) { - reject(new Error(error.errMsg || '微信登录失败')); + console.error('[web-view] wx.login failed', error); + reject(new Error(WECHAT_LOGIN_UNAVAILABLE_MESSAGE)); }, }); }); @@ -320,16 +324,12 @@ function requestMiniProgramLogin(code, displayName) { resolve(response.data); return; } - const message = - response.data && - response.data.error && - response.data.error.message - ? response.data.error.message - : `微信登录失败:${response.statusCode}`; - reject(new Error(message)); + console.error('[web-view] mini program login failed', response); + reject(new Error(WECHAT_LOGIN_UNAVAILABLE_MESSAGE)); }, fail(error) { - reject(new Error(error.errMsg || '微信登录请求失败')); + console.error('[web-view] mini program login request failed', error); + reject(new Error(WECHAT_LOGIN_UNAVAILABLE_MESSAGE)); }, }); }); @@ -366,16 +366,12 @@ function requestMiniProgramBindPhone(authToken, wechatPhoneCode, displayName) { resolve(response.data); return; } - const message = - response.data && - response.data.error && - response.data.error.message - ? response.data.error.message - : `绑定手机号失败:${response.statusCode}`; - reject(new Error(message)); + console.error('[web-view] mini program bind phone failed', response); + reject(new Error(WECHAT_BIND_PHONE_UNAVAILABLE_MESSAGE)); }, fail(error) { - reject(new Error(error.errMsg || '绑定手机号请求失败')); + console.error('[web-view] mini program bind phone request failed', error); + reject(new Error(WECHAT_BIND_PHONE_UNAVAILABLE_MESSAGE)); }, }); }); @@ -542,10 +538,10 @@ function createWechatWebViewPage() { webViewUrl: resolveWebViewUrl(authResult, this._lastLaunchQuery || {}), }); } catch (error) { + console.error('[web-view] auth flow failed', error); this.setData({ authResult: null, - errorMessage: - error && error.message ? error.message : '微信登录失败,请稍后重试。', + errorMessage: WECHAT_LOGIN_UNAVAILABLE_MESSAGE, loggingIn: false, loading: false, nicknameRequired: false, @@ -647,12 +643,10 @@ function createWechatWebViewPage() { ), }); } catch (error) { + console.error('[web-view] bind phone failed', error); this.setData({ bindingPhone: false, - errorMessage: - error && error.message - ? error.message - : '绑定手机号失败,请稍后重试。', + errorMessage: WECHAT_BIND_PHONE_UNAVAILABLE_MESSAGE, }); } }, diff --git a/scripts/check-native-shells.mjs b/scripts/check-native-shells.mjs index a48aad5d2..8b2c9f39f 100644 --- a/scripts/check-native-shells.mjs +++ b/scripts/check-native-shells.mjs @@ -1942,6 +1942,52 @@ function assertWechatSubscribeResultBoundaries() { } } +function assertWechatAuthFailureBoundaries() { + const webViewShellSource = fs.readFileSync( + 'miniprogram/shell/webView.js', + 'utf8', + ); + const authTestSource = fs.readFileSync( + 'scripts/miniprogram-web-view-auth.test.ts', + 'utf8', + ); + + for (const snippet of [ + "WECHAT_LOGIN_UNAVAILABLE_MESSAGE = '微信登录失败,请稍后重试。'", + "WECHAT_BIND_PHONE_UNAVAILABLE_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)", + 'errorMessage: WECHAT_LOGIN_UNAVAILABLE_MESSAGE', + 'errorMessage: WECHAT_BIND_PHONE_UNAVAILABLE_MESSAGE', + ]) { + if (!webViewShellSource.includes(snippet)) { + throw new Error(`wechat auth shell must include ${snippet}`); + } + } + for (const forbiddenSnippet of [ + "reject(new Error(error.errMsg || '微信登录失败'))", + "reject(new Error(error.errMsg || '微信登录请求失败'))", + "reject(new Error(error.errMsg || '绑定手机号请求失败'))", + "error && error.message ? error.message : '微信登录失败,请稍后重试。'", + 'response.data.error.message', + ]) { + if (webViewShellSource.includes(forbiddenSnippet)) { + throw new Error(`wechat auth shell must not expose native failure detail via ${forbiddenSnippet}`); + } + } + for (const snippet of [ + '微信登录失败不向页面透出原生错误', + '绑定手机号失败不向页面透出后端错误体', + "expect(page.data.errorMessage).toBe('微信登录失败,请稍后重试。')", + "expect(page.data.errorMessage).toBe('绑定手机号失败,请稍后重试。')", + ]) { + if (!authTestSource.includes(snippet)) { + throw new Error(`wechat auth boundary test must include ${snippet}`); + } + } +} + function assertHostBridgeLayerLayout() { assertSameList( readDirectoryFileList( @@ -2175,6 +2221,9 @@ assertWechatPaymentResultBoundaries(); console.log('[check:native-shells] wechat-subscribe-result-boundaries'); assertWechatSubscribeResultBoundaries(); +console.log('[check:native-shells] wechat-auth-failure-boundaries'); +assertWechatAuthFailureBoundaries(); + console.log('[check:native-shells] h5-host-bridge-event-subscription-gates'); assertH5HostBridgeEventSubscriptionGates(); diff --git a/scripts/miniprogram-web-view-auth.test.ts b/scripts/miniprogram-web-view-auth.test.ts index 64536d517..ae9dcd066 100644 --- a/scripts/miniprogram-web-view-auth.test.ts +++ b/scripts/miniprogram-web-view-auth.test.ts @@ -113,6 +113,7 @@ function loadCommonJsModule( describe('mini-program web-view auth page', () => { beforeEach(() => { vi.clearAllMocks(); + vi.spyOn(console, 'error').mockImplementation(() => {}); }); test('默认进入时不预登录,直接打开未登录 web-view', async () => { @@ -303,4 +304,55 @@ describe('mini-program web-view auth page', () => { expect(page.data.loading).toBe(false); expect(page.data.phoneBindingRequired).toBe(true); }); + + test('微信登录失败不向页面透出原生错误', async () => { + const wxMock = createWxMock(); + const loginError = { errMsg: 'login:fail private native detail' }; + wxMock.login.mockImplementation(({ fail }) => { + fail(loginError); + }); + const page = loadWebViewPage(wxMock); + + await page.onLoad({ authAction: 'login', returnTo: 'previous' }); + + expect(page.data.errorMessage).toBe('微信登录失败,请稍后重试。'); + expect(console.error).toHaveBeenCalledWith( + '[web-view] wx.login failed', + loginError, + ); + expect(page.data.phoneBindingRequired).toBe(false); + }); + + test('绑定手机号失败不向页面透出后端错误体', async () => { + const wxMock = createWxMock(); + const page = loadWebViewPage(wxMock); + page.data.authResult = { + token: 'jwt-pending-wechat', + bindingStatus: 'pending_bind_phone', + }; + wxMock.request.mockImplementation(({ success }) => { + success({ + statusCode: 500, + data: { + error: { + message: 'private backend detail', + }, + }, + }); + }); + + await page.handleGetPhoneNumber({ + detail: { + code: 'wechat-phone-code', + }, + }); + + expect(page.data.errorMessage).toBe('绑定手机号失败,请稍后重试。'); + expect(console.error).toHaveBeenCalledWith( + '[web-view] mini program bind phone failed', + expect.objectContaining({ + statusCode: 500, + }), + ); + }); });