From b7b6deed5e8bc331ca0bf865286daac54d836a28 Mon Sep 17 00:00:00 2001 From: imxiaozhan Date: Sat, 9 May 2026 22:08:47 -0400 Subject: [PATCH 1/4] fix: prevent hung phone page-state probes from stalling SMS polling --- background/phone-verification-flow.js | 62 +++++++++----- tests/phone-verification-flow.test.js | 117 +++++++++++++++++++++++++- 2 files changed, 154 insertions(+), 25 deletions(-) diff --git a/background/phone-verification-flow.js b/background/phone-verification-flow.js index f3deb95..193adff 100644 --- a/background/phone-verification-flow.js +++ b/background/phone-verification-flow.js @@ -1459,7 +1459,7 @@ function isAuthContentScriptUnreachableError(error) { const message = String(error?.message || error || '').trim(); - return /Receiving end does not exist|Could not establish connection|Frame with ID \d+ is showing error page/i.test(message); + return /Receiving end does not exist|Could not establish connection|Frame with ID \d+ is showing error page|等待认证页状态检查超时/i.test(message); } function buildPhoneRestartStep7Error(phoneNumber = '') { @@ -4011,29 +4011,46 @@ async function readPhonePageState(tabId, timeoutMs = 10000) { const visibleStep = normalizeLogStep(activePhoneVerificationLogStep) || 9; - await ensureStep8SignupPageReady(tabId, { - timeoutMs, - logMessage: '步骤 9:等待认证页脚本恢复后继续手机号验证。', - visibleStep, - logStepKey: 'phone-verification', - }); - const result = await sendToContentScriptResilient('signup-page', { - type: 'STEP8_GET_STATE', - source: 'background', - payload: { visibleStep }, - }, { - timeoutMs, - responseTimeoutMs: timeoutMs, - retryDelayMs: 600, - logMessage: '步骤 9:认证页正在切换,等待后重新检查手机号验证状态...', - logStep: visibleStep, - logStepKey: 'phone-verification', + const deadlineMs = Math.max(1, Math.floor(Number(timeoutMs) || 0)); + let timeoutId = null; + const timeoutPromise = new Promise((_, reject) => { + timeoutId = setTimeout(() => { + reject(new Error(`步骤 ${visibleStep}:等待认证页状态检查超时。`)); + }, deadlineMs); }); + const readPromise = (async () => { + await ensureStep8SignupPageReady(tabId, { + timeoutMs: deadlineMs, + logMessage: '步骤 9:等待认证页脚本恢复后继续手机号验证。', + visibleStep, + logStepKey: 'phone-verification', + }); + const result = await sendToContentScriptResilient('signup-page', { + type: 'STEP8_GET_STATE', + source: 'background', + payload: { visibleStep }, + }, { + timeoutMs: deadlineMs, + responseTimeoutMs: deadlineMs, + retryDelayMs: 600, + logMessage: '步骤 9:认证页正在切换,等待后重新检查手机号验证状态...', + logStep: visibleStep, + logStepKey: 'phone-verification', + }); - if (result?.error) { - throw new Error(result.error); + if (result?.error) { + throw new Error(result.error); + } + return result || {}; + })(); + + try { + return await Promise.race([readPromise, timeoutPromise]); + } finally { + if (timeoutId) { + clearTimeout(timeoutId); + } } - return result || {}; } function resolveCountryCandidatesForProvider(state = {}, providerId = normalizePhoneSmsProvider(state?.phoneSmsProvider)) { @@ -5388,13 +5405,14 @@ return withPhoneVerificationLogContext({ step: 4, stepKey: 'fetch-signup-code' }, async () => { let state = options?.state || await getState(); const activation = normalizeActivation(options?.activation || state?.signupPhoneActivation); + const pageStateCheckTimeoutMs = Math.max(1, Math.floor(Number(options?.pageStateCheckTimeoutMs) || 5000)); if (!activation) { throw new Error('步骤 4:未找到当前注册手机号激活记录,请重新执行步骤 2。'); } const assertSignupPhoneStillApplicable = async (phaseLabel) => { try { - const pageState = await readPhonePageState(tabId, 5000); + const pageState = await readPhonePageState(tabId, pageStateCheckTimeoutMs); if (isSignupEmailVerificationPageState(pageState)) { throw buildSignupPhoneStaleEmailVerificationError(pageState); } diff --git a/tests/phone-verification-flow.test.js b/tests/phone-verification-flow.test.js index 671f29c..3533d17 100644 --- a/tests/phone-verification-flow.test.js +++ b/tests/phone-verification-flow.test.js @@ -568,6 +568,105 @@ test('signup phone helper fails stale email-verification that appears during SMS assert.deepStrictEqual(contentMessages.map((message) => message.type), ['STEP8_GET_STATE', 'STEP8_GET_STATE']); }); +test('signup phone helper does not let a hung page-state probe stall HeroSMS polling', async () => { + let smsPollCount = 0; + let pageReadyCalls = 0; + const statusActions = []; + const contentMessages = []; + let currentState = { + heroSmsApiKey: 'demo-key', + heroSmsReuseEnabled: false, + phoneCodeWaitSeconds: 15, + phoneCodeTimeoutWindows: 1, + phoneCodePollIntervalSeconds: 1, + phoneCodePollMaxRounds: 1, + signupPhoneNumber: '66959916439', + signupPhoneVerificationPurpose: 'signup', + signupPhoneActivation: { + activationId: 'signup-123', + phoneNumber: '66959916439', + provider: 'hero-sms', + serviceCode: 'dr', + countryId: 52, + successfulUses: 0, + maxUses: 3, + }, + }; + const helpers = api.createPhoneVerificationHelpers({ + addLog: async () => {}, + ensureStep8SignupPageReady: async () => { + pageReadyCalls += 1; + if (pageReadyCalls >= 2) { + return new Promise(() => {}); + } + }, + fetchImpl: async (url) => { + const parsedUrl = new URL(url); + const action = parsedUrl.searchParams.get('action'); + if (action === 'getStatus') { + smsPollCount += 1; + return { + ok: true, + text: async () => 'STATUS_WAIT_CODE', + }; + } + if (action === 'setStatus') { + statusActions.push(parsedUrl.searchParams.get('status')); + return { + ok: true, + text: async () => 'ACCESS_READY', + }; + } + throw new Error(`Unexpected HeroSMS action: ${action}`); + }, + getOAuthFlowStepTimeoutMs: async (fallback) => fallback, + getState: async () => currentState, + sendToContentScriptResilient: async (_source, message) => { + contentMessages.push(message); + if (message.type === 'STEP8_GET_STATE') { + return { + emailVerificationPage: false, + phoneVerificationPage: true, + url: 'https://auth.openai.com/phone-verification', + }; + } + if (message.type === 'SUBMIT_PHONE_VERIFICATION_CODE') { + throw new Error('SMS timeout should fail before submitting a code'); + } + return {}; + }, + setState: async (updates) => { + currentState = { ...currentState, ...updates }; + }, + sleepWithStop: async () => {}, + throwIfStopped: () => {}, + }); + + let caughtError = null; + try { + await Promise.race([ + helpers.completeSignupPhoneVerificationFlow(77, { + state: currentState, + pageStateCheckTimeoutMs: 1, + }), + new Promise((_, reject) => setTimeout( + () => reject(new Error('hung waiting for signup phone page-state probe')), + 50 + )), + ]); + } catch (error) { + caughtError = error; + } + + assert.equal(smsPollCount, 1, 'HeroSMS polling should continue after the first waiting status'); + assert.equal(pageReadyCalls, 2, 'should attempt the page-state probe during SMS polling'); + assert.deepStrictEqual(contentMessages.map((message) => message.type), ['STEP8_GET_STATE']); + assert.deepStrictEqual(statusActions, ['8']); + assert.ok(caughtError, 'expected SMS timeout rather than a stalled page-state probe'); + assert.doesNotMatch(caughtError.message, /hung waiting for signup phone page-state probe/); + assert.match(caughtError.message, /等待手机验证码超时/); +}); + test('signup phone helper fails stale email-verification on 5sim RECEIVED without code during SMS polling', async () => { const fiveSimSource = fs.readFileSync('phone-sms/providers/five-sim.js', 'utf8'); const fiveSimModule = new Function('self', `${fiveSimSource}; return self.PhoneSmsFiveSimProvider;`)({}); @@ -5432,7 +5531,7 @@ test('phone verification helper replaces number immediately when phone-verificat assert.equal(messages.includes('RETURN_TO_ADD_PHONE'), true); }); -test('phone verification helper directly navigates back to add-phone when replace-number recovery loses the auth page', async () => { +test('phone verification helper directly navigates back to add-phone when replace-number recovery page-state probe hangs', async () => { const requests = []; const messages = []; const navigationCalls = []; @@ -5458,13 +5557,24 @@ test('phone verification helper directly navigates back to add-phone when replac ]; let numberIndex = 0; const realDateNow = Date.now; + const realSetTimeout = global.setTimeout; let fakeNow = 0; Date.now = () => fakeNow; + global.setTimeout = (callback, ms, ...args) => realSetTimeout( + callback, + Number(ms) >= 12000 ? 1 : ms, + ...args + ); try { const helpers = api.createPhoneVerificationHelpers({ addLog: async () => {}, - ensureStep8SignupPageReady: async () => {}, + ensureStep8SignupPageReady: async () => { + if (!addPhoneRouteReady) { + return new Promise(() => {}); + } + return undefined; + }, fetchImpl: async (url) => { const parsedUrl = new URL(url); requests.push(parsedUrl); @@ -5539,7 +5649,7 @@ test('phone verification helper directly navigates back to add-phone when replac } if (message.type === 'RETURN_TO_ADD_PHONE') { addPhoneRouteReady = false; - throw new Error('Could not establish connection. Receiving end does not exist.'); + return {}; } if (message.type === 'STEP8_GET_STATE') { if (!addPhoneRouteReady) { @@ -5586,6 +5696,7 @@ test('phone verification helper directly navigates back to add-phone when replac assert.equal(messages.includes('RETURN_TO_ADD_PHONE'), true); } finally { Date.now = realDateNow; + global.setTimeout = realSetTimeout; } }); From b69cadde3e87dfc93e9f235ac0f39988e578882a Mon Sep 17 00:00:00 2001 From: imxiaozhan Date: Sun, 10 May 2026 01:34:59 -0400 Subject: [PATCH 2/4] fix: detect contact-verification 500 when resend disconnects --- background.js | 21 ++++++++ background/phone-verification-flow.js | 41 ++++++++++++++++ tests/phone-verification-flow.test.js | 71 +++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) diff --git a/background.js b/background.js index 48b997e..df04191 100644 --- a/background.js +++ b/background.js @@ -10898,6 +10898,7 @@ const phoneVerificationHelpers = self.MultiPageBackgroundPhoneVerification?.crea DEFAULT_PHONE_CODE_TIMEOUT_WINDOWS, DEFAULT_PHONE_CODE_POLL_INTERVAL_SECONDS, DEFAULT_PHONE_CODE_POLL_ROUNDS, + readAuthTabSnapshot, ensureStep8SignupPageReady, navigateAuthTabToAddPhone: async (tabId, options = {}) => { const visibleStep = Math.floor(Number(options.visibleStep || options.step) || 0) || 9; @@ -12204,6 +12205,26 @@ async function ensureStep8SignupPageReady(tabId, options = {}) { }); } +async function readAuthTabSnapshot(tabId) { + if (!Number.isInteger(tabId)) { + return null; + } + try { + const executionResults = await chrome.scripting.executeScript({ + target: { tabId }, + world: 'ISOLATED', + func: () => ({ + url: String(location.href || ''), + title: String(document.title || ''), + text: String(document.body?.innerText || document.documentElement?.innerText || '').trim(), + }), + }); + return executionResults?.[0]?.result || null; + } catch { + return null; + } +} + async function getStep8PageState(tabId, responseTimeoutMs = 1500, visibleStep = 9) { try { const result = await sendTabMessageWithTimeout(tabId, 'signup-page', { diff --git a/background/phone-verification-flow.js b/background/phone-verification-flow.js index 193adff..7cd9694 100644 --- a/background/phone-verification-flow.js +++ b/background/phone-verification-flow.js @@ -11,6 +11,7 @@ getOAuthFlowStepTimeoutMs, getState, requestStop = null, + readAuthTabSnapshot = null, sendToContentScript, sendToContentScriptResilient, navigateAuthTabToAddPhone = null, @@ -1407,6 +1408,44 @@ return new Error(`${PHONE_RESEND_SERVER_ERROR_PREFIX}${message || 'OpenAI contact-verification page returned HTTP ERROR 500 after resend.'}`); } + function getPhoneResendServerErrorFromSnapshot(snapshot = {}) { + const rawUrl = String(snapshot?.url || snapshot?.href || '').trim(); + if (!/\/contact-verification(?:[/?#]|$)/i.test(rawUrl)) { + return ''; + } + const combined = [ + snapshot?.text, + snapshot?.bodyText, + snapshot?.title, + ] + .filter(Boolean) + .join(' ') + .replace(/\s+/g, ' ') + .trim(); + if (!isPhoneResendServerError(combined)) { + return ''; + } + return combined || 'OpenAI contact-verification page returned HTTP ERROR 500 after resend.'; + } + + async function readPhoneResendServerErrorFromAuthTab(tabId) { + if (typeof readAuthTabSnapshot !== 'function') { + return ''; + } + try { + return getPhoneResendServerErrorFromSnapshot(await readAuthTabSnapshot(tabId)); + } catch (_) { + return ''; + } + } + + async function throwPhoneResendServerErrorIfAuthTabShowsIt(tabId) { + const serverErrorText = await readPhoneResendServerErrorFromAuthTab(tabId); + if (serverErrorText) { + throw buildPhoneResendServerError(serverErrorText); + } + } + function shouldTreatResendThrottledAsBanned(state = {}) { return Boolean(state?.phoneResendThrottledAsBannedEnabled); } @@ -5457,6 +5496,7 @@ if (isPhoneResendServerError(resendError)) { throw buildPhoneResendServerError(resendError); } + await throwPhoneResendServerErrorIfAuthTabShowsIt(tabId); await addLog(`步骤 4:注册手机验证码页面重发失败,将继续轮询短信。${resendError.message}`, 'warn', { step: 4, stepKey: 'fetch-signup-code', @@ -5497,6 +5537,7 @@ if (isPhoneResendServerError(resendError)) { throw buildPhoneResendServerError(resendError); } + await throwPhoneResendServerErrorIfAuthTabShowsIt(tabId); await addLog(`步骤 4:验证码被拒后点击重发失败。${resendError.message}`, 'warn', { step: 4, stepKey: 'fetch-signup-code', diff --git a/tests/phone-verification-flow.test.js b/tests/phone-verification-flow.test.js index 3533d17..705ae17 100644 --- a/tests/phone-verification-flow.test.js +++ b/tests/phone-verification-flow.test.js @@ -5888,6 +5888,77 @@ test('signup phone verification cancels activation when resend lands on contact- assert.equal(currentState.signupPhoneActivation, null); }); +test('signup phone verification cancels activation when resend lands on contact-verification 500 page but content script drops', async () => { + const requests = []; + const tabSnapshots = []; + let currentState = { + heroSmsApiKey: 'demo-key', + heroSmsCountryId: 52, + heroSmsCountryLabel: 'Thailand', + verificationResendCount: 0, + phoneCodeWaitSeconds: 60, + phoneCodeTimeoutWindows: 2, + phoneCodePollIntervalSeconds: 1, + phoneCodePollMaxRounds: 1, + signupPhoneActivation: { + activationId: '930001', + phoneNumber: '66953330002', + provider: 'hero-sms', + countryId: 52, + countryLabel: 'Thailand', + }, + }; + + const helpers = api.createPhoneVerificationHelpers({ + addLog: async () => {}, + fetchImpl: async (url) => { + const parsedUrl = new URL(url); + requests.push(parsedUrl); + const action = parsedUrl.searchParams.get('action'); + const id = parsedUrl.searchParams.get('id'); + if (action === 'getStatus') { + return { ok: true, text: async () => 'STATUS_WAIT_CODE' }; + } + if (action === 'setStatus') { + return { ok: true, text: async () => `STATUS_UPDATED:${id}` }; + } + throw new Error(`Unexpected HeroSMS action: ${action}`); + }, + getState: async () => ({ ...currentState }), + readAuthTabSnapshot: async () => { + tabSnapshots.push('read'); + return { + url: 'https://auth.openai.com/contact-verification', + title: 'auth.openai.com', + text: "This page isn't working auth.openai.com is currently unable to handle this request. HTTP ERROR 500", + }; + }, + sendToContentScriptResilient: async (_source, message) => { + if (message.type === 'RESEND_VERIFICATION_CODE') { + throw new Error('Could not establish connection. Receiving end does not exist.'); + } + throw new Error(`Unexpected content-script message: ${message.type}`); + }, + setState: async (updates) => { + currentState = { ...currentState, ...updates }; + }, + sleepWithStop: async () => {}, + throwIfStopped: () => {}, + }); + + await assert.rejects( + () => helpers.completeSignupPhoneVerificationFlow(1, { state: currentState }), + (error) => { + assert.match(error.message, /^PHONE_RESEND_SERVER_ERROR::This page isn't working/); + return true; + } + ); + + assert.equal(tabSnapshots.length >= 1, true); + assert.equal(currentState.signupPhoneActivation, null); + assert.equal(requests.filter((request) => request.searchParams.get('action') === 'getStatus').length, 1); +}); + test('phone verification helper skips page resend for 5sim timeouts and rotates number directly', async () => { const requests = []; const messages = []; From 3763da7a14ca7fa7d0d34dd8f0a05fed3a957353 Mon Sep 17 00:00:00 2001 From: imxiaozhan Date: Sun, 10 May 2026 04:57:09 -0400 Subject: [PATCH 3/4] fix: detect contact-verification 500 without content scripts --- background.js | 15 +- background/phone-verification-flow.js | 13 +- tests/background-auth-tab-snapshot.test.js | 85 +++++++++++ tests/phone-verification-flow.test.js | 170 +++++++++++++++++++++ 4 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 tests/background-auth-tab-snapshot.test.js diff --git a/background.js b/background.js index df04191..fd0e726 100644 --- a/background.js +++ b/background.js @@ -12209,6 +12209,17 @@ async function readAuthTabSnapshot(tabId) { if (!Number.isInteger(tabId)) { return null; } + let tabSnapshot = null; + try { + const tab = await chrome.tabs.get(tabId); + tabSnapshot = { + url: String(tab?.url || ''), + title: String(tab?.title || ''), + text: '', + }; + } catch { + tabSnapshot = null; + } try { const executionResults = await chrome.scripting.executeScript({ target: { tabId }, @@ -12219,9 +12230,9 @@ async function readAuthTabSnapshot(tabId) { text: String(document.body?.innerText || document.documentElement?.innerText || '').trim(), }), }); - return executionResults?.[0]?.result || null; + return executionResults?.[0]?.result || tabSnapshot; } catch { - return null; + return tabSnapshot; } } diff --git a/background/phone-verification-flow.js b/background/phone-verification-flow.js index 7cd9694..e81bf75 100644 --- a/background/phone-verification-flow.js +++ b/background/phone-verification-flow.js @@ -1413,9 +1413,19 @@ if (!/\/contact-verification(?:[/?#]|$)/i.test(rawUrl)) { return ''; } - const combined = [ + const bodyText = [ snapshot?.text, snapshot?.bodyText, + ] + .filter(Boolean) + .join(' ') + .replace(/\s+/g, ' ') + .trim(); + if (!bodyText) { + return 'OpenAI contact-verification page returned HTTP ERROR 500 after resend.'; + } + const combined = [ + bodyText, snapshot?.title, ] .filter(Boolean) @@ -5460,6 +5470,7 @@ if (isStopRequestedError(error) || isStaleSignupPhoneEmailVerificationError(error)) { throw error; } + await throwPhoneResendServerErrorIfAuthTabShowsIt(tabId); await addLog( `步骤 4:检查注册手机号页面状态(${phaseLabel})失败,将继续等待短信。${error.message}`, 'warn', diff --git a/tests/background-auth-tab-snapshot.test.js b/tests/background-auth-tab-snapshot.test.js new file mode 100644 index 0000000..e1884eb --- /dev/null +++ b/tests/background-auth-tab-snapshot.test.js @@ -0,0 +1,85 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); + +const source = fs.readFileSync('background.js', 'utf8'); + +function extractFunction(name) { + const markers = [`async function ${name}(`, `function ${name}(`]; + const start = markers + .map((marker) => source.indexOf(marker)) + .find((index) => index >= 0); + if (start < 0) { + throw new Error(`missing function ${name}`); + } + + let parenDepth = 0; + let signatureEnded = false; + let braceStart = -1; + for (let i = start; i < source.length; i += 1) { + const ch = source[i]; + if (ch === '(') { + parenDepth += 1; + } else if (ch === ')') { + parenDepth -= 1; + if (parenDepth === 0) { + signatureEnded = true; + } + } else if (ch === '{' && signatureEnded) { + braceStart = i; + break; + } + } + + if (braceStart < 0) { + throw new Error(`missing body for function ${name}`); + } + + let depth = 0; + let end = braceStart; + for (; end < source.length; end += 1) { + const ch = source[end]; + if (ch === '{') depth += 1; + if (ch === '}') { + depth -= 1; + if (depth === 0) { + end += 1; + break; + } + } + } + + return source.slice(start, end); +} + +function createApi(chrome) { + return new Function('chrome', ` +${extractFunction('readAuthTabSnapshot')} +return { readAuthTabSnapshot }; +`)(chrome); +} + +test('readAuthTabSnapshot falls back to tab URL when script execution fails on auth error pages', async () => { + const chrome = { + scripting: { + executeScript: async () => { + throw new Error('Cannot access contents of url "chrome-error://chromewebdata/".'); + }, + }, + tabs: { + get: async () => ({ + id: 1, + url: 'https://auth.openai.com/contact-verification', + title: 'auth.openai.com', + }), + }, + }; + + const api = createApi(chrome); + + assert.deepStrictEqual(await api.readAuthTabSnapshot(1), { + url: 'https://auth.openai.com/contact-verification', + title: 'auth.openai.com', + text: '', + }); +}); diff --git a/tests/phone-verification-flow.test.js b/tests/phone-verification-flow.test.js index 705ae17..e06681c 100644 --- a/tests/phone-verification-flow.test.js +++ b/tests/phone-verification-flow.test.js @@ -5891,6 +5891,7 @@ test('signup phone verification cancels activation when resend lands on contact- test('signup phone verification cancels activation when resend lands on contact-verification 500 page but content script drops', async () => { const requests = []; const tabSnapshots = []; + let resendAttempted = false; let currentState = { heroSmsApiKey: 'demo-key', heroSmsCountryId: 52, @@ -5927,6 +5928,13 @@ test('signup phone verification cancels activation when resend lands on contact- getState: async () => ({ ...currentState }), readAuthTabSnapshot: async () => { tabSnapshots.push('read'); + if (!resendAttempted) { + return { + url: 'https://auth.openai.com/phone-verification', + title: 'Verify your phone', + text: 'Enter the code sent to your phone.', + }; + } return { url: 'https://auth.openai.com/contact-verification', title: 'auth.openai.com', @@ -5934,7 +5942,14 @@ test('signup phone verification cancels activation when resend lands on contact- }; }, sendToContentScriptResilient: async (_source, message) => { + if (message.type === 'STEP8_GET_STATE') { + return { + phoneVerificationPage: true, + url: 'https://auth.openai.com/phone-verification', + }; + } if (message.type === 'RESEND_VERIFICATION_CODE') { + resendAttempted = true; throw new Error('Could not establish connection. Receiving end does not exist.'); } throw new Error(`Unexpected content-script message: ${message.type}`); @@ -5959,6 +5974,161 @@ test('signup phone verification cancels activation when resend lands on contact- assert.equal(requests.filter((request) => request.searchParams.get('action') === 'getStatus').length, 1); }); +test('signup phone verification treats contact-verification URL-only snapshot as resend server error', async () => { + let currentState = { + heroSmsApiKey: 'demo-key', + heroSmsCountryId: 52, + heroSmsCountryLabel: 'Thailand', + verificationResendCount: 0, + phoneCodeWaitSeconds: 60, + phoneCodeTimeoutWindows: 2, + phoneCodePollIntervalSeconds: 1, + phoneCodePollMaxRounds: 1, + signupPhoneActivation: { + activationId: '930002', + phoneNumber: '66953330004', + provider: 'hero-sms', + countryId: 52, + countryLabel: 'Thailand', + }, + }; + + const helpers = api.createPhoneVerificationHelpers({ + addLog: async () => {}, + fetchImpl: async (url) => { + const parsedUrl = new URL(url); + const action = parsedUrl.searchParams.get('action'); + const id = parsedUrl.searchParams.get('id'); + if (action === 'getStatus') { + return { ok: true, text: async () => 'STATUS_WAIT_CODE' }; + } + if (action === 'setStatus') { + return { ok: true, text: async () => `STATUS_UPDATED:${id}` }; + } + throw new Error(`Unexpected HeroSMS action: ${action}`); + }, + getState: async () => ({ ...currentState }), + readAuthTabSnapshot: async () => ({ + url: 'https://auth.openai.com/contact-verification', + title: 'auth.openai.com', + text: '', + }), + sendToContentScriptResilient: async (_source, message) => { + if (message.type === 'RESEND_VERIFICATION_CODE') { + throw new Error('Could not establish connection. Receiving end does not exist.'); + } + throw new Error(`Unexpected content-script message: ${message.type}`); + }, + setState: async (updates) => { + currentState = { ...currentState, ...updates }; + }, + sleepWithStop: async () => {}, + throwIfStopped: () => {}, + }); + + await assert.rejects( + () => helpers.completeSignupPhoneVerificationFlow(1, { state: currentState }), + (error) => { + assert.match(error.message, /^PHONE_RESEND_SERVER_ERROR::OpenAI contact-verification page returned HTTP ERROR 500 after resend\./); + return true; + } + ); + + assert.equal(currentState.signupPhoneActivation, null); +}); + +test('signup phone verification fails when contact-verification 500 appears after successful resend', async () => { + const messages = []; + let pageStateReads = 0; + let resendCalls = 0; + let currentState = { + heroSmsApiKey: 'demo-key', + heroSmsCountryId: 52, + heroSmsCountryLabel: 'Thailand', + verificationResendCount: 0, + phoneCodeWaitSeconds: 60, + phoneCodeTimeoutWindows: 2, + phoneCodePollIntervalSeconds: 1, + phoneCodePollMaxRounds: 1, + signupPhoneActivation: { + activationId: '930003', + phoneNumber: '66953330005', + provider: 'hero-sms', + countryId: 52, + countryLabel: 'Thailand', + }, + }; + + const helpers = api.createPhoneVerificationHelpers({ + addLog: async () => {}, + ensureStep8SignupPageReady: async () => {}, + fetchImpl: async (url) => { + const parsedUrl = new URL(url); + const action = parsedUrl.searchParams.get('action'); + const id = parsedUrl.searchParams.get('id'); + if (action === 'getStatus') { + return { ok: true, text: async () => 'STATUS_WAIT_CODE' }; + } + if (action === 'setStatus') { + return { ok: true, text: async () => `STATUS_UPDATED:${id}` }; + } + throw new Error(`Unexpected HeroSMS action: ${action}`); + }, + getState: async () => ({ ...currentState }), + readAuthTabSnapshot: async () => ({ + url: 'https://auth.openai.com/contact-verification', + title: "This page isn't working", + text: 'auth.openai.com is currently unable to handle this request. HTTP ERROR 500', + }), + sendToContentScriptResilient: async (_source, message) => { + messages.push(message.type); + if (message.type === 'STEP8_GET_STATE') { + pageStateReads += 1; + if (pageStateReads <= 2) { + return { + phoneVerificationPage: true, + url: 'https://auth.openai.com/phone-verification', + }; + } + throw new Error('Could not establish connection. Receiving end does not exist.'); + } + if (message.type === 'RESEND_VERIFICATION_CODE') { + resendCalls += 1; + return { + resent: true, + buttonText: 'Resend code', + url: 'https://auth.openai.com/phone-verification', + }; + } + throw new Error(`Unexpected content-script message: ${message.type}`); + }, + setState: async (updates) => { + currentState = { ...currentState, ...updates }; + }, + sleepWithStop: async () => {}, + throwIfStopped: () => {}, + }); + + await assert.rejects( + () => helpers.completeSignupPhoneVerificationFlow(1, { state: currentState }), + (error) => { + assert.match(error.message, /^PHONE_RESEND_SERVER_ERROR::/); + assert.match(error.message, /HTTP ERROR 500/); + assert.match(error.message, /This page isn't working/); + return true; + } + ); + + assert.equal(resendCalls, 1); + assert.deepStrictEqual(messages, [ + 'STEP8_GET_STATE', + 'STEP8_GET_STATE', + 'RESEND_VERIFICATION_CODE', + 'STEP8_GET_STATE', + ]); + assert.equal(currentState.signupPhoneActivation, null); +}); + test('phone verification helper skips page resend for 5sim timeouts and rotates number directly', async () => { const requests = []; const messages = []; From f09173128b5d13d7359754dde1b9b4fd055df312 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Mon, 11 May 2026 03:39:36 +0800 Subject: [PATCH 4/4] fix: avoid treating URL-only contact verification snapshots as 500 --- background/phone-verification-flow.js | 5 +++-- tests/phone-verification-flow.test.js | 31 +++++++++++++++++++++------ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/background/phone-verification-flow.js b/background/phone-verification-flow.js index e81bf75..6da2f70 100644 --- a/background/phone-verification-flow.js +++ b/background/phone-verification-flow.js @@ -1421,12 +1421,13 @@ .join(' ') .replace(/\s+/g, ' ') .trim(); + const titleText = String(snapshot?.title || '').replace(/\s+/g, ' ').trim(); if (!bodyText) { - return 'OpenAI contact-verification page returned HTTP ERROR 500 after resend.'; + return isPhoneResendServerError(titleText) ? (titleText || 'OpenAI contact-verification page returned HTTP ERROR 500 after resend.') : ''; } const combined = [ bodyText, - snapshot?.title, + titleText, ] .filter(Boolean) .join(' ') diff --git a/tests/phone-verification-flow.test.js b/tests/phone-verification-flow.test.js index e06681c..2e3a7f5 100644 --- a/tests/phone-verification-flow.test.js +++ b/tests/phone-verification-flow.test.js @@ -5974,7 +5974,8 @@ test('signup phone verification cancels activation when resend lands on contact- assert.equal(requests.filter((request) => request.searchParams.get('action') === 'getStatus').length, 1); }); -test('signup phone verification treats contact-verification URL-only snapshot as resend server error', async () => { +test('signup phone verification does not treat contact-verification URL-only snapshot as resend server error', async () => { + let resendAttempted = false; let currentState = { heroSmsApiKey: 'demo-key', heroSmsCountryId: 52, @@ -6008,13 +6009,28 @@ test('signup phone verification treats contact-verification URL-only snapshot as throw new Error(`Unexpected HeroSMS action: ${action}`); }, getState: async () => ({ ...currentState }), - readAuthTabSnapshot: async () => ({ - url: 'https://auth.openai.com/contact-verification', - title: 'auth.openai.com', - text: '', - }), + readAuthTabSnapshot: async () => ( + resendAttempted + ? { + url: 'https://auth.openai.com/contact-verification', + title: 'auth.openai.com', + text: '', + } + : { + url: 'https://auth.openai.com/phone-verification', + title: 'Verify your phone', + text: 'Enter the code sent to your phone.', + } + ), sendToContentScriptResilient: async (_source, message) => { + if (message.type === 'STEP8_GET_STATE') { + return { + phoneVerificationPage: true, + url: 'https://auth.openai.com/phone-verification', + }; + } if (message.type === 'RESEND_VERIFICATION_CODE') { + resendAttempted = true; throw new Error('Could not establish connection. Receiving end does not exist.'); } throw new Error(`Unexpected content-script message: ${message.type}`); @@ -6029,7 +6045,8 @@ test('signup phone verification treats contact-verification URL-only snapshot as await assert.rejects( () => helpers.completeSignupPhoneVerificationFlow(1, { state: currentState }), (error) => { - assert.match(error.message, /^PHONE_RESEND_SERVER_ERROR::OpenAI contact-verification page returned HTTP ERROR 500 after resend\./); + assert.doesNotMatch(error.message, /^PHONE_RESEND_SERVER_ERROR::/); + assert.match(error.message, /等待手机验证码超时/); return true; } );