From c9bef31ac2613daf134c87c94c3e78c2badac53a Mon Sep 17 00:00:00 2001 From: Wenjing Zhu Date: Mon, 8 Jun 2026 12:10:32 +0800 Subject: [PATCH 1/2] fix: ensure SMS channel selected before submitting phone number on add-phone page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenAI's add-phone page shows a 'Send code via' radio group with 'Text Message' (SMS) and 'WhatsApp' options that can appear in random DOM order. Previously submitPhoneNumber did not check which channel was active, so if WhatsApp happened to be selected the verification code would be sent via WhatsApp — which SMS-based verification platforms cannot read. - Add getAddPhoneChannelInput / getSelectedChannel to read the hidden channel input and checked radio state - Add getSmsChannelRadio / getWhatsAppChannelRadio to locate options by their stable value attribute regardless of DOM order - Add ensureSmsChannelSelected to click the SMS radio when needed - Call ensureSmsChannelSelected in submitPhoneNumber before the phone number is submitted, failing fast if SMS cannot be selected - Export the new helpers so background flows can also query channel state when needed Co-Authored-By: Claude Opus 4.8 --- flows/openai/content/phone-auth.js | 57 ++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/flows/openai/content/phone-auth.js b/flows/openai/content/phone-auth.js index 85a6c083..203cbe99 100644 --- a/flows/openai/content/phone-auth.js +++ b/flows/openai/content/phone-auth.js @@ -394,6 +394,53 @@ return selectedCountryMatchesPhoneNumber(phoneNumber); } + function getAddPhoneChannelInput() { + const form = getAddPhoneForm(); + if (!form) return null; + return form.querySelector('input[type="hidden"][name="channel"]'); + } + + function getSelectedChannel() { + const channelInput = getAddPhoneChannelInput(); + if (channelInput) { + return String(channelInput.value || '').trim().toLowerCase(); + } + const radio = document.querySelector('input[type="radio"][name^="segmented-control"]:checked'); + return String(radio?.value || '').trim().toLowerCase(); + } + + function getSmsChannelRadio() { + const form = getAddPhoneForm(); + if (!form) return null; + return form.querySelector('input[type="radio"][value="sms"]'); + } + + function getWhatsAppChannelRadio() { + const form = getAddPhoneForm(); + if (!form) return null; + return form.querySelector('input[type="radio"][value="whatsapp"]'); + } + + async function ensureSmsChannelSelected() { + const channelRadio = getSmsChannelRadio(); + if (!channelRadio) { + return true; + } + const currentChannel = getSelectedChannel(); + if (currentChannel === 'sms') { + return true; + } + const label = channelRadio.closest('label'); + if (!label) { + return currentChannel === 'sms'; + } + await performOperationWithDelay({ stepKey: 'phone-auth', kind: 'click', label: 'channel-select-sms' }, async () => { + simulateClick(label); + }); + await sleep(250); + return getSelectedChannel() === 'sms'; + } + function getAddPhoneSubmitButton() { const form = getAddPhoneForm(); if (!form) return null; @@ -775,6 +822,11 @@ throw new Error(`Failed to select "${countryLabel || 'target country'}" on the add-phone page.`); } + const smsChannelEnsured = await ensureSmsChannelSelected(); + if (!smsChannelEnsured) { + throw new Error('Failed to select "Text Message" (SMS) channel on the add-phone page.'); + } + const dialCode = getDisplayedDialCode(); if (!dialCode && !isExplicitInternational) { throw new Error(`Could not determine the dial code for "${countryLabel}" on the add-phone page.`); @@ -1055,6 +1107,11 @@ submitPhoneNumber, submitPhoneVerificationCode, toE164PhoneNumber, + getAddPhoneChannelInput, + getSelectedChannel, + getSmsChannelRadio, + getWhatsAppChannelRadio, + ensureSmsChannelSelected, }; } From 789cdb6c552831ccd4ebf49406ad96b5aacf859f Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Mon, 8 Jun 2026 15:06:01 +0800 Subject: [PATCH 2/2] fix: skip registration wait after logged-in signup verification --- background/message-router.js | 27 +++ background/verification-flow.js | 103 ++++++++++- flows/openai/content/openai-auth.js | 11 +- ...ckground-message-router-step2-skip.test.js | 54 ++++++ tests/step4-submit-retry-recovery.test.js | 1 + tests/verification-flow-polling.test.js | 175 ++++++++++++++++++ 6 files changed, 369 insertions(+), 2 deletions(-) diff --git a/background/message-router.js b/background/message-router.js index 9776be4f..ef6a4687 100644 --- a/background/message-router.js +++ b/background/message-router.js @@ -646,6 +646,27 @@ }; } + async function skipRegistrationWaitStepAfter(currentStep, state = {}) { + const step6 = findStepByKeyAfter(currentStep, 'wait-registration-success', state) + || (getStepKeyForState(6, state) === 'wait-registration-success' ? 6 : null); + if (!step6) { + return false; + } + const step6Status = getNodeStatusByStep(step6, state); + if (isStepProtectedFromAutoSkip(step6Status)) { + return false; + } + await setNodeStatusByStep(step6, 'skipped', state); + const currentStepKey = getStepKeyForState(currentStep, state) + || (Number(currentStep) === 3 ? 'fill-password' : 'fetch-signup-code'); + await addLog( + `步骤 ${currentStep}:账号已进入 ChatGPT 已登录态,已自动跳过步骤 ${step6},流程将直接进入后续节点。`, + 'warn', + { step: currentStep, stepKey: currentStepKey } + ); + return true; + } + function findStepByKeyAfter(currentOrder, targetKey, state = {}) { const activeStepIds = typeof getStepIdsForState === 'function' ? getStepIdsForState(state) @@ -994,6 +1015,9 @@ await addLog('步骤 3:页面已直接进入已登录态,已自动跳过步骤 5。', 'warn'); } } + if (payload.skipRegistrationWaitStep) { + await skipRegistrationWaitStepAfter(step, await getState()); + } if (payload.loginVerificationRequestedAt) { await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt }); } @@ -1021,6 +1045,9 @@ } } } + if (payload.skipRegistrationWaitStep) { + await skipRegistrationWaitStepAfter(step, await getState()); + } break; case 7: await syncStepAccountIdentityFromPayload(payload); diff --git a/background/verification-flow.js b/background/verification-flow.js index 137adb35..4a977f19 100644 --- a/background/verification-flow.js +++ b/background/verification-flow.js @@ -78,12 +78,18 @@ : ''; } - const isRetryableVerificationTransportError = typeof deps.isRetryableContentScriptTransportError === 'function' + const baseRetryableVerificationTransportError = typeof deps.isRetryableContentScriptTransportError === 'function' ? deps.isRetryableContentScriptTransportError : ((error) => /back\/forward cache|message channel is closed|Receiving end does not exist|port closed before a response was received|A listener indicated an asynchronous response|内容脚本\s+\d+(?:\.\d+)?\s*秒内未响应|did not respond in \d+s/i.test( String(typeof error === 'string' ? error : error?.message || '') )); + function isRetryableVerificationTransportError(error) { + const message = String(typeof error === 'string' ? error : error?.message || ''); + return Boolean(baseRetryableVerificationTransportError(error)) + || /页面刚完成跳转或刷新,内容脚本还没有重新接回/i.test(message); + } + function getVerificationCodeStateKey(step) { return step === 4 ? 'lastSignupCode' : 'lastLoginCode'; } @@ -179,11 +185,87 @@ } } + async function inspectChatgptHomeLoggedInSignal(tabId) { + if (!chrome?.scripting?.executeScript || !Number.isInteger(Number(tabId))) { + return null; + } + try { + const results = await chrome.scripting.executeScript({ + target: { tabId: Number(tabId) }, + func: () => { + const url = String(location.href || ''); + const parsed = new URL(url); + const host = String(parsed.hostname || '').toLowerCase(); + if (!['chatgpt.com', 'www.chatgpt.com', 'chat.openai.com'].includes(host)) { + return { loggedInHome: false, reason: 'host', url }; + } + const path = String(parsed.pathname || ''); + if (/^\/(?:auth\/|create-account\/|email-verification|log-in|add-phone)(?:[/?#]|$)/i.test(path)) { + return { loggedInHome: false, reason: 'auth_path', url }; + } + + const isVisible = (el) => { + if (!el) return false; + const style = window.getComputedStyle(el); + if (!style || style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') { + return false; + } + const rect = el.getBoundingClientRect(); + return rect.width> 0 && rect.height> 0; + }; + const isEnabled = (el) => Boolean(el) + && !el.disabled + && String(el.getAttribute?.('aria-disabled') || '').toLowerCase() !== 'true'; + const getActionText = (el) => [ + el?.textContent, + el?.value, + el?.getAttribute?.('aria-label'), + el?.getAttribute?.('title'), + ] + .filter(Boolean) + .join(' ') + .replace(/\s+/g, ' ') + .trim(); + + const entryPattern = /免费注册|立即注册|注册|登录|ログイン|サインイン|無料でサインアップ|サインアップ|新規登録|登録する|登録|アカウントを作成|アカウント作成|log\s*in|sign\s*in|sign\s*up|register|create\s*account|create\s+account/i; + const actions = Array.from(document.querySelectorAll( + 'a, button, [role="button"], [role="link"], input[type="button"], input[type="submit"]' + )); + const hasLoggedOutEntryAction = actions.some((el) => ( + isVisible(el) + && isEnabled(el) + && entryPattern.test(getActionText(el)) + )); + if (hasLoggedOutEntryAction) { + return { loggedInHome: false, reason: 'logged_out_entry', url }; + } + + const pageText = String(document.body?.innerText || document.body?.textContent || '').replace(/\s+/g, ' ').trim(); + if (document.readyState === 'loading' || !pageText) { + return { loggedInHome: null, reason: 'loading_or_empty', url }; + } + return { loggedInHome: true, reason: 'no_logged_out_entry', url }; + }, + }); + const value = results?.[0]?.result; + if (value?.loggedInHome === true) { + return true; + } + if (value?.loggedInHome === false) { + return false; + } + } catch { + return null; + } + return null; + } + async function detectStep4PostSubmitFallback(tabId, options = {}) { const timeoutMs = Math.max(1000, Number(options.timeoutMs) || 8000); const pollIntervalMs = Math.max(100, Number(options.pollIntervalMs) || 250); const startedAt = Date.now(); let lastUrl = ''; + let pendingChatgptHomeUrl = ''; while (Date.now() - startedAt < timeoutMs) { throwIfStopped(); @@ -195,10 +277,17 @@ } if (isLikelyLoggedInChatgptHomeUrl(currentUrl)) { + const loggedInSignal = await inspectChatgptHomeLoggedInSignal(tabId); + if (loggedInSignal === null) { + pendingChatgptHomeUrl = currentUrl; + await sleepWithStop(pollIntervalMs); + continue; + } return { success: true, reason: 'chatgpt_home', skipProfileStep: true, + skipRegistrationWaitStep: loggedInSignal === true, url: currentUrl, }; } @@ -218,6 +307,16 @@ await sleepWithStop(pollIntervalMs); } + if (pendingChatgptHomeUrl) { + return { + success: true, + reason: 'chatgpt_home', + skipProfileStep: true, + skipRegistrationWaitStep: false, + url: pendingChatgptHomeUrl, + }; + } + return { success: false, reason: 'unknown', @@ -1176,6 +1275,7 @@ assumed: true, transportRecovered: true, skipProfileStep: Boolean(fallback.skipProfileStep), + skipRegistrationWaitStep: Boolean(fallback.skipRegistrationWaitStep), url: fallback.url, }; } @@ -1439,6 +1539,7 @@ code: result.code, phoneVerificationRequired: Boolean(submitResult.addPhonePage), ...(step === 4 && submitResult?.skipProfileStep ? { skipProfileStep: true } : {}), + ...(step === 4 && submitResult?.skipRegistrationWaitStep ? { skipRegistrationWaitStep: true } : {}), ...(step === 4 && submitResult?.skipProfileStepReason ? { skipProfileStepReason: submitResult.skipProfileStepReason } : {}), diff --git a/flows/openai/content/openai-auth.js b/flows/openai/content/openai-auth.js index 6c3d12f4..e0278e40 100644 --- a/flows/openai/content/openai-auth.js +++ b/flows/openai/content/openai-auth.js @@ -778,6 +778,7 @@ function inspectSignupEntryState() { return { state: 'logged_in_home', skipProfileStep: true, + skipRegistrationWaitStep: true, url: postVerificationState.url || location.href, }; } @@ -2707,6 +2708,7 @@ async function step3_fillEmailPassword(payload) { skippedPasswordPage: true, deferredSubmit: false, ...(snapshot.skipProfileStep ? { skipProfileStep: true } : {}), + ...(snapshot.skipRegistrationWaitStep ? { skipRegistrationWaitStep: true } : {}), }; log('步骤 3:当前页面已进入验证码或后续阶段,密码页按已跳过处理。', 'warn'); reportComplete(3, completionPayload); @@ -3065,6 +3067,7 @@ function getStep4PostVerificationState(options = {}) { return { state: 'logged_in_home', skipProfileStep: true, + skipRegistrationWaitStep: true, url: location.href, }; } @@ -5040,6 +5043,7 @@ function inspectSignupVerificationState() { return { state: 'logged_in_home', skipProfileStep: true, + skipRegistrationWaitStep: true, url: postVerificationState.url || location.href, }; } @@ -5200,11 +5204,12 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { } if (snapshot.state === 'logged_in_home') { - log(`${prepareLogLabel}:页面已直接进入 ChatGPT 已登录态,本步骤按已完成处理,并将跳过步骤 5。`, 'ok'); + log(`${prepareLogLabel}:页面已直接进入 ChatGPT 已登录态,本步骤按已完成处理,并将跳过步骤 5/6。`, 'ok'); return { ready: true, alreadyVerified: true, skipProfileStep: true, + skipRegistrationWaitStep: true, retried: recoveryRound, prepareSource, }; @@ -5219,6 +5224,7 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) { skipLoginVerificationStep: true, directOAuthConsentPage: true, skipProfileStep: true, + skipRegistrationWaitStep: true, retried: recoveryRound, prepareSource, }; @@ -5341,6 +5347,7 @@ async function waitForVerificationSubmitOutcome(step, timeout, options = {}) { return { success: true, skipProfileStep: true, + skipRegistrationWaitStep: true, url: postVerificationState.url || location.href, }; } @@ -5384,6 +5391,7 @@ async function waitForVerificationSubmitOutcome(step, timeout, options = {}) { return { success: true, skipProfileStep: true, + skipRegistrationWaitStep: true, url: postVerificationState.url || location.href, }; } @@ -5521,6 +5529,7 @@ async function fillVerificationCode(step, payload) { assumed: true, alreadyAdvanced: true, skipProfileStep: true, + skipRegistrationWaitStep: true, url: postVerificationState.url || location.href, }; } diff --git a/tests/background-message-router-step2-skip.test.js b/tests/background-message-router-step2-skip.test.js index 7dff9088..4d329a2b 100644 --- a/tests/background-message-router-step2-skip.test.js +++ b/tests/background-message-router-step2-skip.test.js @@ -398,6 +398,60 @@ test('message router skips step 5 when step 4 reports already logged-in transiti assert.equal(events.logs[0]?.message, '步骤 4:检测到账号已直接进入已登录态,已自动跳过步骤 5。'); }); +test('message router skips steps 5 and registration wait when step 4 reaches logged-in home', async () => { + const { router, events } = createRouter({ + state: { stepStatuses: { 5: 'pending', 6: 'pending' } }, + }); + + await router.handleStepData(4, { + emailTimestamp: 123, + skipProfileStep: true, + skipRegistrationWaitStep: true, + }); + + assert.deepStrictEqual(events.stepStatuses, [ + { step: 5, status: 'skipped' }, + { step: 6, status: 'skipped' }, + ]); + assert.equal(events.logs[0]?.message, '步骤 4:检测到账号已直接进入已登录态,已自动跳过步骤 5。'); + assert.equal(events.logs[1]?.message, '步骤 4:账号已进入 ChatGPT 已登录态,已自动跳过步骤 6,流程将直接进入后续节点。'); +}); + +test('message router can skip dynamic registration wait step independently of profile skip', async () => { + const stepKeys = { + 4: 'fetch-signup-code', + 5: 'fill-profile', + 7: 'wait-registration-success', + 8: 'openai-upload-session-to-webchat', + }; + const { router, events } = createRouter({ + state: { stepStatuses: { 5: 'pending', 7: 'pending' } }, + nodeByStep: { + 4: 'fetch-signup-code', + 5: 'fill-profile', + 7: 'wait-registration-success', + 8: 'openai-upload-session-to-webchat', + }, + stepByNode: { + 'fetch-signup-code': 4, + 'fill-profile': 5, + 'wait-registration-success': 7, + 'openai-upload-session-to-webchat': 8, + }, + getStepDefinitionForState: (step) => ({ id: step, key: stepKeys[step] || '' }), + getStepIdsForState: () => [4, 5, 7, 8], + getNodeIdsForState: () => ['fetch-signup-code', 'fill-profile', 'wait-registration-success', 'openai-upload-session-to-webchat'], + }); + + await router.handleStepData(4, { + emailTimestamp: 123, + skipRegistrationWaitStep: true, + }); + + assert.deepStrictEqual(events.stepStatuses, [{ step: 7, status: 'skipped' }]); + assert.equal(events.logs[0]?.message, '步骤 4:账号已进入 ChatGPT 已登录态,已自动跳过步骤 7,流程将直接进入后续节点。'); +}); + test('message router skips login-code step when oauth login lands on consent page', async () => { const stepKeys = { 7: 'oauth-login', diff --git a/tests/step4-submit-retry-recovery.test.js b/tests/step4-submit-retry-recovery.test.js index 0035e176..e2f473ba 100644 --- a/tests/step4-submit-retry-recovery.test.js +++ b/tests/step4-submit-retry-recovery.test.js @@ -195,6 +195,7 @@ return { assert.deepStrictEqual(result, { success: true, skipProfileStep: true, + skipRegistrationWaitStep: true, url: 'https://chatgpt.com/', }); }); diff --git a/tests/verification-flow-polling.test.js b/tests/verification-flow-polling.test.js index 8e9d21c3..55bac1f8 100644 --- a/tests/verification-flow-polling.test.js +++ b/tests/verification-flow-polling.test.js @@ -2005,6 +2005,69 @@ test('verification flow keeps combined signup profile skip reason when completin ]); }); +test('verification flow forwards registration wait skip when completing signup verification', async () => { + const completed = []; + + const helpers = api.createVerificationFlowHelpers({ + addLog: async () => {}, + chrome: { + tabs: { + update: async () => {}, + }, + }, + CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email', + completeNodeFromBackground: async (nodeId, payload) => { + completed.push({ nodeId, payload }); + }, + confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }), + getHotmailVerificationPollConfig: () => ({}), + getHotmailVerificationRequestTimestamp: () => 0, + getNodeIdByStepForState: getTestNodeIdByStepForState, + getState: async () => ({}), + getTabId: async () => 1, + HOTMAIL_PROVIDER: 'hotmail-api', + isStopError: () => false, + LUCKMAIL_PROVIDER: 'luckmail-api', + MAIL_2925_VERIFICATION_INTERVAL_MS: 15000, + MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15, + pollCloudflareTempEmailVerificationCode: async () => ({}), + pollHotmailVerificationCode: async () => ({}), + pollLuckmailVerificationCode: async () => ({}), + sendToContentScript: async () => { + throw new Error('should not use non-resilient channel'); + }, + sendToContentScriptResilient: async () => ({ + success: true, + skipProfileStep: true, + skipRegistrationWaitStep: true, + }), + sendToMailContentScriptResilient: async () => ({ + code: '654321', + emailTimestamp: 123, + }), + setState: async () => {}, + setStepStatus: async () => {}, + sleepWithStop: async () => {}, + throwIfStopped: () => {}, + VERIFICATION_POLL_MAX_ROUNDS: 5, + }); + + await helpers.resolveVerificationStep(4, {}, { provider: 'custom', source: 'custom' }); + + assert.deepStrictEqual(completed, [ + { + nodeId: 'fetch-signup-code', + payload: { + emailTimestamp: 123, + code: '654321', + phoneVerificationRequired: false, + skipProfileStep: true, + skipRegistrationWaitStep: true, + }, + }, + ]); +}); + test('verification flow treats retryable submit transport failure as success when step 4 already redirected to logged-in home', async () => { const logs = []; @@ -2017,6 +2080,9 @@ test('verification flow treats retryable submit transport failure as success whe update: async () => {}, get: async () => ({ url: 'https://chatgpt.com/' }), }, + scripting: { + executeScript: async () => [{ result: { loggedInHome: true, reason: 'no_logged_out_entry', url: 'https://chatgpt.com/' } }], + }, }, CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email', completeNodeFromBackground: async () => {}, @@ -2052,11 +2118,120 @@ test('verification flow treats retryable submit transport failure as success whe assert.equal(result.success, true); assert.equal(result.skipProfileStep, true); + assert.equal(result.skipRegistrationWaitStep, true); assert.equal(result.assumed, true); assert.equal(result.transportRecovered, true); assert.equal(logs.some(({ message }) => /验证码提交后页面已切换到ChatGPT 已登录首页/.test(message)), true); }); +test('verification flow waits for logged-in home DOM signal before skipping registration wait', async () => { + let inspectCalls = 0; + + const helpers = api.createVerificationFlowHelpers({ + addLog: async () => {}, + chrome: { + tabs: { + update: async () => {}, + get: async () => ({ url: 'https://chatgpt.com/' }), + }, + scripting: { + executeScript: async () => { + inspectCalls += 1; + if (inspectCalls === 1) { + return [{ result: { loggedInHome: null, reason: 'loading_or_empty', url: 'https://chatgpt.com/' } }]; + } + return [{ result: { loggedInHome: true, reason: 'no_logged_out_entry', url: 'https://chatgpt.com/' } }]; + }, + }, + }, + CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email', + completeNodeFromBackground: async () => {}, + confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }), + getHotmailVerificationPollConfig: () => ({}), + getHotmailVerificationRequestTimestamp: () => 0, + getState: async () => ({}), + getTabId: async () => 1, + HOTMAIL_PROVIDER: 'hotmail-api', + isRetryableContentScriptTransportError: (error) => /message channel is closed/i.test(String(error?.message || error || '')), + isStopError: () => false, + LUCKMAIL_PROVIDER: 'luckmail-api', + MAIL_2925_VERIFICATION_INTERVAL_MS: 15000, + MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15, + pollCloudflareTempEmailVerificationCode: async () => ({}), + pollHotmailVerificationCode: async () => ({}), + pollLuckmailVerificationCode: async () => ({}), + sendToContentScript: async () => { + throw new Error('should not use non-resilient channel'); + }, + sendToContentScriptResilient: async () => { + throw new Error('The page keeping the extension port is moved into back/forward cache, so the message channel is closed.'); + }, + sendToMailContentScriptResilient: async () => ({}), + setState: async () => {}, + setStepStatus: async () => {}, + sleepWithStop: async () => {}, + throwIfStopped: () => {}, + VERIFICATION_POLL_MAX_ROUNDS: 5, + }); + + const result = await helpers.submitVerificationCode(4, '654321'); + + assert.equal(inspectCalls, 2); + assert.equal(result.success, true); + assert.equal(result.skipProfileStep, true); + assert.equal(result.skipRegistrationWaitStep, true); +}); + +test('verification flow does not skip registration wait when fallback sees logged-out chatgpt home', async () => { + const helpers = api.createVerificationFlowHelpers({ + addLog: async () => {}, + chrome: { + tabs: { + update: async () => {}, + get: async () => ({ url: 'https://chatgpt.com/' }), + }, + scripting: { + executeScript: async () => [{ result: { loggedInHome: false, reason: 'logged_out_entry', url: 'https://chatgpt.com/' } }], + }, + }, + CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email', + completeNodeFromBackground: async () => {}, + confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }), + getHotmailVerificationPollConfig: () => ({}), + getHotmailVerificationRequestTimestamp: () => 0, + getState: async () => ({}), + getTabId: async () => 1, + HOTMAIL_PROVIDER: 'hotmail-api', + isRetryableContentScriptTransportError: (error) => /message channel is closed/i.test(String(error?.message || error || '')), + isStopError: () => false, + LUCKMAIL_PROVIDER: 'luckmail-api', + MAIL_2925_VERIFICATION_INTERVAL_MS: 15000, + MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15, + pollCloudflareTempEmailVerificationCode: async () => ({}), + pollHotmailVerificationCode: async () => ({}), + pollLuckmailVerificationCode: async () => ({}), + sendToContentScript: async () => { + throw new Error('should not use non-resilient channel'); + }, + sendToContentScriptResilient: async () => { + throw new Error('The page keeping the extension port is moved into back/forward cache, so the message channel is closed.'); + }, + sendToMailContentScriptResilient: async () => ({}), + setState: async () => {}, + setStepStatus: async () => {}, + sleepWithStop: async () => {}, + throwIfStopped: () => {}, + VERIFICATION_POLL_MAX_ROUNDS: 5, + }); + + const result = await helpers.submitVerificationCode(4, '654321'); + + assert.equal(result.success, true); + assert.equal(result.skipProfileStep, true); + assert.equal(result.skipRegistrationWaitStep, false); + assert.equal(result.assumed, true); +}); + test('verification flow avoids resend storms when iCloud polling keeps hitting transport errors', async () => { let resendRequests = 0; let pollAttempts = 0;

AltStyle によって変換されたページ (->オリジナル) /