From de70266f8ce2fab33431d8f5ee5d89321ffd3964 Mon Sep 17 00:00:00 2001 From: daniellee2015 Date: Fri, 24 Apr 2026 19:37:14 +0800 Subject: [PATCH] fix(pro7.5): stabilize icloud/session flow and logged-in step skipping --- background.js | 87 +++++++-- background/generated-email-helpers.js | 4 +- background/message-router.js | 8 + background/steps/fetch-login-code.js | 10 + background/steps/fetch-signup-code.js | 10 + background/steps/submit-signup-email.js | 172 +++++++++++++++++- background/verification-flow.js | 156 +++++++++++++++- content/signup-page.js | 14 ++ sidepanel/sidepanel.html | 7 + sidepanel/sidepanel.js | 27 +++ tests/auto-run-step4-restart.test.js | 121 ++++++++++++ ...ckground-message-router-step2-skip.test.js | 14 ++ .../background-signup-step2-branching.test.js | 49 +++++ tests/background-step4-filter-window.test.js | 46 +++++ tests/background-step7-recovery.test.js | 54 ++++++ tests/step4-split-code-submit.test.js | 3 + tests/verification-flow-polling.test.js | 162 +++++++++++++++++ 17 files changed, 909 insertions(+), 35 deletions(-) diff --git a/background.js b/background.js index b8284f5..5935907 100644 --- a/background.js +++ b/background.js @@ -272,6 +272,7 @@ const PERSISTED_SETTING_DEFAULTS = { customEmailPool: [], autoDeleteUsedIcloudAlias: false, icloudHostPreference: 'auto', + icloudFetchMode: 'reuse_existing', accountRunHistoryTextEnabled: false, accountRunHistoryHelperBaseUrl: DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL, gmailBaseEmail: '', @@ -674,6 +675,11 @@ function normalizeEmailGenerator(value = '') { return 'duck'; } +function normalizeIcloudFetchMode(value = '') { + const normalized = String(value || '').trim().toLowerCase(); + return normalized === 'always_new' ? 'always_new' : 'reuse_existing'; +} + function normalizeCustomEmailPool(value = []) { const source = Array.isArray(value) ? value @@ -978,6 +984,8 @@ function normalizePersistentSettingValue(key, value) { return Boolean(value); case 'icloudHostPreference': return normalizeIcloudHost(value) || 'auto'; + case 'icloudFetchMode': + return normalizeIcloudFetchMode(value); case 'accountRunHistoryHelperBaseUrl': return normalizeAccountRunHistoryHelperBaseUrl(value); case 'gmailBaseEmail': @@ -3448,10 +3456,16 @@ async function validateIcloudSession(setupUrl) { return data; } -async function resolveIcloudPremiumMailService() { +async function resolveIcloudPremiumMailService(options = {}) { const errors = []; const state = await getState(); - const setupUrls = await getPreferredIcloudSetupUrls(state); + const explicitHost = normalizeIcloudHost(options?.hostPreference || options?.preferredHost || ''); + const setupUrls = explicitHost + ? (() => { + const forcedSetupUrl = getIcloudSetupUrlForHost(explicitHost); + return forcedSetupUrl ? [forcedSetupUrl] : []; + })() + : await getPreferredIcloudSetupUrls(state); for (const setupUrl of setupUrls) { try { @@ -3480,17 +3494,19 @@ function getIcloudAliasLabel() { return `MultiPage ${dateStr}`; } -async function checkIcloudSession() { - return withIcloudLoginHelp('检查 iCloud 会话', async () => { - const { setupUrl } = await resolveIcloudPremiumMailService(); +async function checkIcloudSession(options = {}) { + const actionLabel = String(options?.actionLabel || '检查 iCloud 会话').trim() || '检查 iCloud 会话'; + const { actionLabel: _ignoredActionLabel, ...resolveOptions } = options || {}; + return withIcloudLoginHelp(actionLabel, async () => { + const { setupUrl } = await resolveIcloudPremiumMailService(resolveOptions); await addLog(`iCloud:会话校验通过(${new URL(setupUrl).host})`, 'ok'); return { ok: true, setupUrl }; }); } -async function listIcloudAliases() { +async function listIcloudAliases(options = {}) { return withIcloudLoginHelp('加载 iCloud 隐私邮箱列表', async () => { - const { serviceUrl } = await resolveIcloudPremiumMailService(); + const { serviceUrl } = await resolveIcloudPremiumMailService(options); const response = await icloudRequest('GET', `${serviceUrl}/v2/hme/list`); const state = await getState(); return normalizeIcloudAliasList(response, { @@ -3580,9 +3596,10 @@ async function deleteUsedIcloudAliases() { return { deleted, skipped }; } -async function fetchIcloudHideMyEmail() { +async function fetchIcloudHideMyEmail(options = {}) { return withIcloudLoginHelp('获取 iCloud 隐私邮箱', async () => { throwIfStopped(); + const generateNew = Boolean(options?.generateNew); await addLog('iCloud:正在校验当前浏览器登录状态...', 'info'); const { serviceUrl, setupUrl } = await resolveIcloudPremiumMailService(); @@ -3595,12 +3612,16 @@ async function fetchIcloudHideMyEmail() { preservedEmails: getPreservedAliasMap(state), }); - const reusableAlias = pickReusableIcloudAlias(existingAliases); - if (reusableAlias) { - await setEmailState(reusableAlias.email); - await addLog(`iCloud:复用未使用别名 ${reusableAlias.email}`, 'ok'); - broadcastIcloudAliasesChanged({ reason: 'selected', email: reusableAlias.email }); - return reusableAlias.email; + if (!generateNew) { + const reusableAlias = pickReusableIcloudAlias(existingAliases); + if (reusableAlias) { + await setEmailState(reusableAlias.email); + await addLog(`iCloud:复用未使用别名 ${reusableAlias.email}`, 'ok'); + broadcastIcloudAliasesChanged({ reason: 'selected', email: reusableAlias.email }); + return reusableAlias.email; + } + } else { + await addLog('iCloud:已启用“始终创建新别名”,本次将跳过复用。', 'info'); } await addLog('iCloud:没有可复用别名,开始生成新的 Hide My Email 地址...', 'warn'); @@ -5970,7 +5991,10 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) { if (attempt > 1) { await addLog(`${generatorLabel}:正在进行第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次自动获取重试...`, 'warn'); } - const generatedEmail = await fetchGeneratedEmail(currentState, { generateNew: true, generator }); + const generatedEmail = await fetchGeneratedEmail(currentState, { + generateNew: generator !== 'icloud', + generator, + }); await addLog( `=== 目标 ${targetRun}/${totalRuns} 轮:${generatorLabel}已就绪:${generatedEmail}(第 ${attemptRuns} 次尝试,第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次获取)===`, 'ok' @@ -6116,7 +6140,10 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) { if (attempt > 1) { await addLog(`${generatorLabel}:正在进行第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次自动获取重试...`, 'warn'); } - const generatedEmail = await fetchGeneratedEmail(currentState, { generateNew: true, generator }); + const generatedEmail = await fetchGeneratedEmail(currentState, { + generateNew: generator !== 'icloud', + generator, + }); await addLog( `=== 目标 ${targetRun}/${totalRuns} 轮:${generatorLabel}已就绪:${generatedEmail}(第 ${attemptRuns} 次尝试,第 ${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS} 次获取)===`, 'ok' @@ -6201,9 +6228,15 @@ async function runAutoSequenceFromStep(startStep, context = {}) { let restartFromStep1WithCurrentEmail = false; let step = Math.max(currentStartStep, 4); while (step <= LAST_STEP_ID) { + const latestState = await getState(); + const currentStatus = latestState.stepStatuses?.[step] || 'pending'; + if (isStepDoneStatus(currentStatus)) { + await addLog(`自动运行:步骤 ${step} 当前状态为 ${currentStatus},将直接继续后续流程。`, 'info'); + step += 1; + continue; + } try { await executeStepAndWait(step, AUTO_STEP_DELAYS[step]); - const latestState = await getState(); step += 1; } catch (err) { if (isStopError(err)) { @@ -6442,6 +6475,7 @@ const verificationFlowHelpers = self.MultiPageBackgroundVerificationFlow?.create getTabId, HOTMAIL_PROVIDER, isMail2925LimitReachedError, + isRetryableContentScriptTransportError, isStopError, LUCKMAIL_PROVIDER, MAIL_2925_VERIFICATION_INTERVAL_MS, @@ -6450,6 +6484,7 @@ const verificationFlowHelpers = self.MultiPageBackgroundVerificationFlow?.create pollHotmailVerificationCode, pollLuckmailVerificationCode, sendToContentScript, + sendToContentScriptResilient, sendToMailContentScriptResilient, setState, setStepStatus, @@ -6467,6 +6502,7 @@ const step2Executor = self.MultiPageBackgroundStep2?.createStep2Executor({ chrome, completeStepFromBackground, ensureContentScriptReadyOnTab, + ensureSignupAuthEntryPageReady, ensureSignupEntryPageReady, ensureSignupPostEmailPageReadyInTab, getTabId, @@ -6487,12 +6523,24 @@ const step3Executor = self.MultiPageBackgroundStep3?.createStep3Executor({ setState, SIGNUP_PAGE_INJECT_FILES, }); + +async function ensureIcloudMailSessionForVerification(options = {}) { + const flowState = options?.state || await getState().catch(() => ({})); + const hostPreference = getConfiguredIcloudHostPreference(flowState) + || normalizeIcloudHost(flowState?.preferredIcloudHost); + return checkIcloudSession({ + ...(hostPreference ? { hostPreference } : {}), + actionLabel: options?.actionLabel || '检查 iCloud 会话', + }); +} + const step4Executor = self.MultiPageBackgroundStep4?.createStep4Executor({ addLog, chrome, completeStepFromBackground, confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass, ensureMail2925MailboxSession, + ensureIcloudMailSession: ensureIcloudMailSessionForVerification, getMailConfig, getTabId, HOTMAIL_PROVIDER, @@ -6539,6 +6587,7 @@ const step8Executor = self.MultiPageBackgroundStep8?.createStep8Executor({ CLOUDFLARE_TEMP_EMAIL_PROVIDER, confirmCustomVerificationStepBypass: verificationFlowHelpers.confirmCustomVerificationStepBypass, ensureMail2925MailboxSession, + ensureIcloudMailSession: ensureIcloudMailSessionForVerification, ensureStep8VerificationPageReady, getOAuthFlowRemainingMs, getOAuthFlowStepTimeoutMs, @@ -6721,6 +6770,10 @@ async function ensureSignupEntryPageReady(step = 1) { return signupFlowHelpers.ensureSignupEntryPageReady(step); } +async function ensureSignupAuthEntryPageReady(step = 1) { + return signupFlowHelpers.ensureSignupEntryPageReady(step); +} + async function ensureSignupPasswordPageReadyInTab(tabId, step = 2, options = {}) { return signupFlowHelpers.ensureSignupPasswordPageReadyInTab(tabId, step, options); } diff --git a/background/generated-email-helpers.js b/background/generated-email-helpers.js index be121ea..343c134 100644 --- a/background/generated-email-helpers.js +++ b/background/generated-email-helpers.js @@ -283,7 +283,9 @@ return fetchManagedAliasEmail(mergedState, options); } if (generator === 'icloud') { - return fetchIcloudHideMyEmail(); + return fetchIcloudHideMyEmail({ + generateNew: Boolean(options.generateNew), + }); } if (generator === 'cloudflare') { return fetchCloudflareEmail(mergedState, options); diff --git a/background/message-router.js b/background/message-router.js index 27b212a..15c503d 100644 --- a/background/message-router.js +++ b/background/message-router.js @@ -191,6 +191,14 @@ lastEmailTimestamp: payload.emailTimestamp || null, signupVerificationRequestedAt: null, }); + if (payload.skipProfileStep) { + const latestState = await getState(); + const step5Status = latestState.stepStatuses?.[5]; + if (step5Status !== 'running' && step5Status !== 'completed' && step5Status !== 'manual_completed') { + await setStepStatus(5, 'skipped'); + await addLog('步骤 4:检测到账号已直接进入已登录态,已自动跳过步骤 5。', 'warn'); + } + } break; case 8: await setState({ diff --git a/background/steps/fetch-login-code.js b/background/steps/fetch-login-code.js index 7baad00..a379a77 100644 --- a/background/steps/fetch-login-code.js +++ b/background/steps/fetch-login-code.js @@ -10,6 +10,7 @@ CLOUDFLARE_TEMP_EMAIL_PROVIDER, confirmCustomVerificationStepBypass, ensureMail2925MailboxSession, + ensureIcloudMailSession, ensureStep8VerificationPageReady, getOAuthFlowRemainingMs, getOAuthFlowStepTimeoutMs, @@ -140,6 +141,15 @@ return; } + if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') { + await addLog('步骤 8:正在确认 iCloud 邮箱登录态...', 'info'); + await ensureIcloudMailSession({ + state, + step: 8, + actionLabel: '步骤 8:确认 iCloud 邮箱登录态', + }); + } + throwIfStopped(); if ( mail.provider === HOTMAIL_PROVIDER diff --git a/background/steps/fetch-signup-code.js b/background/steps/fetch-signup-code.js index 77dbeaa..02f9aa5 100644 --- a/background/steps/fetch-signup-code.js +++ b/background/steps/fetch-signup-code.js @@ -10,6 +10,7 @@ completeStepFromBackground, confirmCustomVerificationStepBypass, ensureMail2925MailboxSession, + ensureIcloudMailSession, getMailConfig, getTabId, HOTMAIL_PROVIDER, @@ -111,6 +112,15 @@ return; } + if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') { + await addLog('步骤 4:正在确认 iCloud 邮箱登录态...', 'info'); + await ensureIcloudMailSession({ + state, + step: 4, + actionLabel: '步骤 4:确认 iCloud 邮箱登录态', + }); + } + throwIfStopped(); if ( mail.provider === HOTMAIL_PROVIDER diff --git a/background/steps/submit-signup-email.js b/background/steps/submit-signup-email.js index 77e6b65..b8cad06 100644 --- a/background/steps/submit-signup-email.js +++ b/background/steps/submit-signup-email.js @@ -7,6 +7,7 @@ chrome, completeStepFromBackground, ensureContentScriptReadyOnTab, + ensureSignupAuthEntryPageReady, ensureSignupEntryPageReady, ensureSignupPostEmailPageReadyInTab, getTabId, @@ -16,6 +17,107 @@ SIGNUP_PAGE_INJECT_FILES, } = deps; + function getErrorMessage(error) { + return String(typeof error === 'string' ? error : error?.message || ''); + } + + function isSignupEntryUnavailableErrorMessage(errorLike) { + const message = getErrorMessage(errorLike); + return /未找到可用的邮箱输入入口|当前页面没有可用的注册入口,也不在邮箱\/密码页/.test(message); + } + + function isRetryableStep2TransportErrorMessage(errorLike) { + const message = getErrorMessage(errorLike); + return /Content script on signup-page did not respond in \d+s|Receiving end does not exist|message channel closed|A listener indicated an asynchronous response|port closed before a response was received|did not respond in \d+s/i.test(message); + } + + function isLikelyLoggedInChatgptHomeUrl(rawUrl) { + const url = String(rawUrl || '').trim(); + if (!url) { + return false; + } + try { + const parsed = new URL(url); + const host = String(parsed.hostname || '').toLowerCase(); + if (!['chatgpt.com', 'www.chatgpt.com'].includes(host)) { + return false; + } + const path = String(parsed.pathname || ''); + if (/^\/(?:auth\/|create-account\/|email-verification|log-in|add-phone)(?:[/?#]|$)/i.test(path)) { + return false; + } + return true; + } catch { + return false; + } + } + + async function shouldForceAuthEntryRetry(tabId) { + if (!Number.isInteger(tabId) || typeof chrome?.tabs?.get !== 'function') { + return false; + } + try { + const tab = await chrome.tabs.get(tabId); + const currentUrl = String(tab?.url || ''); + return isLikelyLoggedInChatgptHomeUrl(currentUrl); + } catch { + return false; + } + } + + async function getTabUrl(tabId) { + if (!Number.isInteger(tabId) || typeof chrome?.tabs?.get !== 'function') { + return ''; + } + try { + const tab = await chrome.tabs.get(tabId); + return String(tab?.url || ''); + } catch { + return ''; + } + } + + async function completeStep2AsLoggedInSession(tabId, resolvedEmail, reasonMessage = '') { + const currentUrl = await getTabUrl(tabId); + if (!isLikelyLoggedInChatgptHomeUrl(currentUrl)) { + return false; + } + const reasonText = getErrorMessage(reasonMessage); + const reasonSuffix = reasonText ? `(触发原因:${reasonText})` : ''; + await addLog(`步骤 2:检测到当前会话已登录 ChatGPT,已跳过注册链路(步骤 3/4/5),将直接进入步骤 6。${reasonSuffix}`, 'warn'); + await completeStepFromBackground(2, { + email: resolvedEmail, + nextSignupState: 'already_logged_in_home', + nextSignupUrl: currentUrl || 'https://chatgpt.com/', + skippedPasswordStep: true, + skipRegistrationFlow: true, + }); + return true; + } + + async function submitSignupEmail(resolvedEmail, options = {}) { + const { + timeoutMs = 35000, + retryDelayMs = 700, + logMessage = '步骤 2:官网注册入口正在切换,等待页面恢复后继续输入邮箱...', + } = options; + + try { + return await sendToContentScriptResilient('signup-page', { + type: 'EXECUTE_STEP', + step: 2, + source: 'background', + payload: { email: resolvedEmail }, + }, { + timeoutMs, + retryDelayMs, + logMessage, + }); + } catch (error) { + return { error: getErrorMessage(error) }; + } + } + async function executeStep2(state) { const resolvedEmail = await resolveSignupEmailForFlow(state); @@ -34,19 +136,73 @@ }); } - const step2Result = await sendToContentScriptResilient('signup-page', { - type: 'EXECUTE_STEP', - step: 2, - source: 'background', - payload: { email: resolvedEmail }, - }, { - timeoutMs: 20000, + if (await shouldForceAuthEntryRetry(signupTabId)) { + await addLog('步骤 2:检测到当前位于已登录 ChatGPT 首页,先切换认证入口页再提交邮箱。', 'warn'); + try { + signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId; + } catch (entryError) { + const entryErrorMessage = getErrorMessage(entryError); + if (await completeStep2AsLoggedInSession(signupTabId, resolvedEmail, entryErrorMessage)) { + return; + } + await addLog('步骤 2:切换认证入口失败,正在重新打开官网入口并重试提交邮箱...', 'warn'); + signupTabId = (await ensureSignupEntryPageReady(2)).tabId; + } + } + + let step2Result = await submitSignupEmail(resolvedEmail, { + timeoutMs: 35000, retryDelayMs: 700, logMessage: '步骤 2:官网注册入口正在切换,等待页面恢复后继续输入邮箱...', }); if (step2Result?.error) { - throw new Error(step2Result.error); + const errorMessage = getErrorMessage(step2Result.error); + if (isSignupEntryUnavailableErrorMessage(errorMessage)) { + await addLog('步骤 2:未找到邮箱输入入口,正在切换认证入口页后重试一次...', 'warn'); + signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId; + step2Result = await submitSignupEmail(resolvedEmail, { + timeoutMs: 35000, + retryDelayMs: 700, + logMessage: '步骤 2:认证入口页已打开,正在重新提交邮箱...', + }); + + if (step2Result?.error) { + const retryErrorMessage = getErrorMessage(step2Result.error); + if (isSignupEntryUnavailableErrorMessage(retryErrorMessage)) { + if (await completeStep2AsLoggedInSession(signupTabId, resolvedEmail, retryErrorMessage)) { + return; + } + await addLog('步骤 2:认证入口仍不可用,正在重新进入官网注册入口再重试一次...', 'warn'); + signupTabId = (await ensureSignupEntryPageReady(2)).tabId; + step2Result = await submitSignupEmail(resolvedEmail, { + timeoutMs: 35000, + retryDelayMs: 700, + logMessage: '步骤 2:重试官网注册入口后正在重新提交邮箱...', + }); + } + } + } else if (isRetryableStep2TransportErrorMessage(errorMessage)) { + await addLog('步骤 2:注册入口页通信超时,正在切换认证入口页并重试提交邮箱...', 'warn'); + signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId; + step2Result = await submitSignupEmail(resolvedEmail, { + timeoutMs: 45000, + retryDelayMs: 700, + logMessage: '步骤 2:认证入口页已打开,正在重新提交邮箱...', + }); + } + } + + if (step2Result?.error) { + const finalErrorMessage = getErrorMessage(step2Result.error); + if ( + (isSignupEntryUnavailableErrorMessage(finalErrorMessage) + || isRetryableStep2TransportErrorMessage(finalErrorMessage)) + && await completeStep2AsLoggedInSession(signupTabId, resolvedEmail, finalErrorMessage) + ) { + return; + } + throw new Error(finalErrorMessage); } if (!step2Result?.alreadyOnPasswordPage) { diff --git a/background/verification-flow.js b/background/verification-flow.js index 36e9411..3b752eb 100644 --- a/background/verification-flow.js +++ b/background/verification-flow.js @@ -23,6 +23,7 @@ pollHotmailVerificationCode, pollLuckmailVerificationCode, sendToContentScript, + sendToContentScriptResilient, sendToMailContentScriptResilient, setState, sleepWithStop, @@ -30,6 +31,12 @@ VERIFICATION_POLL_MAX_ROUNDS, } = deps; + const isRetryableVerificationTransportError = 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|did not respond in \d+s/i.test( + String(typeof error === 'string' ? error : error?.message || '') + )); + function getVerificationCodeStateKey(step) { return step === 4 ? 'lastSignupCode' : 'lastLoginCode'; } @@ -38,6 +45,89 @@ return step === 4 ? '注册' : '登录'; } + function isLikelyLoggedInChatgptHomeUrl(rawUrl) { + const url = String(rawUrl || '').trim(); + if (!url) return false; + + try { + const parsed = new URL(url); + const host = String(parsed.hostname || '').toLowerCase(); + if (!['chatgpt.com', 'www.chatgpt.com'].includes(host)) { + return false; + } + const path = String(parsed.pathname || ''); + if (/^\/(?:auth\/|create-account\/|email-verification|log-in|add-phone)(?:[/?#]|$)/i.test(path)) { + return false; + } + return true; + } catch { + return false; + } + } + + function isSignupProfilePageUrl(rawUrl) { + const url = String(rawUrl || '').trim(); + if (!url) return false; + + try { + const parsed = new URL(url); + const host = String(parsed.hostname || '').toLowerCase(); + if (!['auth.openai.com', 'auth0.openai.com', 'accounts.openai.com'].includes(host)) { + return false; + } + return /\/create-account\/profile(?:[/?#]|$)/i.test(String(parsed.pathname || '')); + } catch { + return false; + } + } + + 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 = ''; + + while (Date.now() - startedAt < timeoutMs) { + throwIfStopped(); + try { + const tab = await chrome.tabs.get(tabId); + const currentUrl = String(tab?.url || '').trim(); + if (currentUrl) { + lastUrl = currentUrl; + } + + if (isLikelyLoggedInChatgptHomeUrl(currentUrl)) { + return { + success: true, + reason: 'chatgpt_home', + skipProfileStep: true, + url: currentUrl, + }; + } + + if (isSignupProfilePageUrl(currentUrl)) { + return { + success: true, + reason: 'signup_profile', + skipProfileStep: false, + url: currentUrl, + }; + } + } catch { + // Keep polling until timeout; tab may be mid-navigation. + } + + await sleepWithStop(pollIntervalMs); + } + + return { + success: false, + reason: 'unknown', + skipProfileStep: false, + url: lastUrl, + }; + } + function getVerificationResendStateKey() { return 'verificationResendCount'; } @@ -448,6 +538,17 @@ `步骤 ${step}:距离下次重新发送验证码还差 ${Math.ceil(remainingBeforeResendMs / 1000)} 秒,继续刷新邮箱(第 ${round}/${maxRounds} 轮)...`, 'info' ); + const configuredIntervalMs = Math.max( + 1, + Number(payloadOverrides.intervalMs) + || Number(pollOverrides.intervalMs) + || 3000 + ); + const cooldownSleepMs = Math.min( + remainingBeforeResendMs, + Math.max(1000, Math.min(configuredIntervalMs, 3000)) + ); + await sleepWithStop(cooldownSleepMs); continue; } @@ -597,19 +698,54 @@ } await chrome.tabs.update(signupTabId, { active: true }); - const result = await sendToContentScript('signup-page', { + const baseResponseTimeoutMs = await getResponseTimeoutMsForStep( + step, + options, + step === 7 ? 45000 : 30000, + `填写${getVerificationCodeLabel(step)}验证码` + ); + const message = { type: 'FILL_CODE', step, source: 'background', payload: { code }, - }, { - responseTimeoutMs: await getResponseTimeoutMsForStep( - step, - options, - step === 7 ? 45000 : 30000, - `填写${getVerificationCodeLabel(step)}验证码` - ), - }); + }; + let result; + if (typeof sendToContentScriptResilient === 'function') { + try { + result = await sendToContentScriptResilient('signup-page', message, { + timeoutMs: Math.max(baseResponseTimeoutMs + 15000, 30000), + retryDelayMs: 700, + responseTimeoutMs: baseResponseTimeoutMs, + logMessage: `步骤 ${step}:认证页正在切换,等待页面重新就绪后继续确认验证码提交结果...`, + }); + } catch (err) { + if (step === 4 && isRetryableVerificationTransportError(err)) { + const fallback = await detectStep4PostSubmitFallback(signupTabId, { + timeoutMs: 9000, + pollIntervalMs: 300, + }); + if (fallback.success) { + const fallbackLabel = fallback.reason === 'chatgpt_home' + ? 'ChatGPT 已登录首页' + : '注册资料页'; + await addLog(`步骤 4:验证码提交后页面已切换到${fallbackLabel},按提交成功继续。`, 'warn'); + return { + success: true, + assumed: true, + transportRecovered: true, + skipProfileStep: Boolean(fallback.skipProfileStep), + url: fallback.url, + }; + } + } + throw err; + } + } else { + result = await sendToContentScript('signup-page', message, { + responseTimeoutMs: baseResponseTimeoutMs, + }); + } if (result && result.error) { throw new Error(result.error); @@ -735,6 +871,7 @@ `步骤 ${step}:提交失败后距离下次重新发送验证码还差 ${Math.ceil(remainingBeforeResendMs / 1000)} 秒,先继续刷新邮箱(${attempt + 1}/${maxSubmitAttempts})...`, 'warn' ); + await sleepWithStop(Math.min(remainingBeforeResendMs, 2000)); continue; } @@ -763,6 +900,7 @@ await completeStepFromBackground(step, { emailTimestamp: result.emailTimestamp, code: result.code, + ...(step === 4 && submitResult?.skipProfileStep ? { skipProfileStep: true } : {}), }); triggerPostSuccessMailboxCleanup(step, mail); return; diff --git a/content/signup-page.js b/content/signup-page.js index 0b74b3e..91c901d 100644 --- a/content/signup-page.js +++ b/content/signup-page.js @@ -2122,6 +2122,20 @@ async function fillVerificationCode(step, payload) { const { code } = payload; if (!code) throw new Error('未提供验证码。'); + if (step === 4 && isStep5Ready()) { + log(`步骤 ${step}:检测到页面已进入下一阶段,本次验证码提交按成功处理。`, 'ok'); + return { success: true, assumed: true, alreadyAdvanced: true }; + } + if (step === 8) { + if (isStep8Ready()) { + log(`步骤 ${step}:检测到页面已进入 OAuth 同意页,本次验证码提交按成功处理。`, 'ok'); + return { success: true, assumed: true, alreadyAdvanced: true }; + } + if (isAddPhonePageReady()) { + return { success: true, addPhonePage: true, url: location.href }; + } + } + log(`步骤 ${step}:正在填写验证码:${code}`); if (step === 8) { diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 7d23e60..fdb8726 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -631,6 +631,13 @@ +
+ 获取策略 + +
自动删除