diff --git a/background.js b/background.js index f5547d0..345a732 100644 --- a/background.js +++ b/background.js @@ -1009,6 +1009,35 @@ function isVerificationMailPollingError(error) { return /未在 .*邮箱中找到新的匹配邮件|邮箱轮询结束,但未获取到验证码|无法获取新的(?:注册|登录)验证码|页面未能重新就绪|页面通信异常|did not respond in \d+s/i.test(message); } +const STEP7_RESTART_FROM_STEP6_ERROR_CODE = 'STEP7_RESTART_FROM_STEP6'; + +function createStep7RestartFromStep6Error(details = {}) { + const { reason = 'unknown', url = '' } = details || {}; + const reasonLabel = reason === 'login_timeout_error_page' + ? '检测到登录页超时报错' + : '步骤 7 请求回到步骤 6'; + const error = new Error(`步骤 7:${reasonLabel}。${url ? `URL: ${url}` : ''}`.trim()); + error.code = STEP7_RESTART_FROM_STEP6_ERROR_CODE; + error.restartReason = reason; + error.restartUrl = url; + return error; +} + +function getStep7RestartFromStep6Error(result) { + if (!result?.restartFromStep6) { + return null; + } + return createStep7RestartFromStep6Error(result); +} + +function isStep7RestartFromStep6Error(error) { + return error?.code === STEP7_RESTART_FROM_STEP6_ERROR_CODE; +} + +function isStep7RecoverableError(error) { + return isVerificationMailPollingError(error) || isStep7RestartFromStep6Error(error); +} + function isRestartCurrentAttemptError(error) { const message = String(typeof error === 'string' ? error : error?.message || ''); return /当前邮箱已存在,需要重新开始新一轮/.test(message); @@ -2539,6 +2568,13 @@ async function requestVerificationCodeResend(step) { throw new Error(result.error); } + if (step === 7) { + const restartError = getStep7RestartFromStep6Error(result); + if (restartError) { + throw restartError; + } + } + return Date.now(); } @@ -2625,6 +2661,13 @@ async function submitVerificationCode(step, code) { throw new Error(result.error); } + if (step === 7) { + const restartError = getStep7RestartFromStep6Error(result); + if (restartError) { + throw restartError; + } + } + return result || {}; } @@ -2644,6 +2687,9 @@ async function resolveVerificationStep(step, state, mail, options = {}) { await requestVerificationCodeResend(step); await addLog(`步骤 ${step}:已先请求一封新的${getVerificationCodeLabel(step)}验证码,再开始轮询邮箱。`, 'warn'); } catch (err) { + if (step === 7 && isStep7RestartFromStep6Error(err)) { + throw err; + } await addLog(`步骤 ${step}:首次重新获取验证码失败:${err.message},将继续使用当前时间窗口轮询。`, 'warn'); } } @@ -2840,6 +2886,11 @@ async function runStep7Attempt(state) { throw new Error(prepareResult.error); } + const restartError = getStep7RestartFromStep6Error(prepareResult); + if (restartError) { + throw restartError; + } + await addLog(`步骤 7:正在打开${mail.label}...`); const alive = await isTabAlive(mail.source); @@ -2890,7 +2941,7 @@ async function executeStep7(state) { } catch (err) { lastError = err; - if (!isVerificationMailPollingError(err)) { + if (!isStep7RecoverableError(err)) { throw err; } @@ -2898,11 +2949,20 @@ async function executeStep7(state) { break; } - await addLog(`步骤 7:检测到邮箱轮询类失败,准备从步骤 6 重新开始(${round + 1}/${STEP7_RESTART_MAX_ROUNDS})...`, 'warn'); + await addLog( + isStep7RestartFromStep6Error(err) + ? `步骤 7:检测到登录页超时报错,准备从步骤 6 重新开始(${round + 1}/${STEP7_RESTART_MAX_ROUNDS})...` + : `步骤 7:检测到邮箱轮询类失败,准备从步骤 6 重新开始(${round + 1}/${STEP7_RESTART_MAX_ROUNDS})...`, + 'warn' + ); await rerunStep6ForStep7Recovery(); } } + if (lastError && isStep7RecoverableError(lastError)) { + throw new Error(`步骤 7:登录验证码流程在 ${STEP7_RESTART_MAX_ROUNDS} 轮恢复后仍未成功。最后一次原因:${lastError.message}`); + } + throw lastError || new Error(`步骤 7:登录验证码流程在 ${STEP7_RESTART_MAX_ROUNDS} 轮后仍未成功。`); } diff --git a/content/signup-page.js b/content/signup-page.js index 9934753..4ceb2d4 100644 --- a/content/signup-page.js +++ b/content/signup-page.js @@ -188,6 +188,12 @@ async function prepareLoginCodeFlow(timeout = 15000) { return { ready: true, mode: 'verification_page' }; } + const initialRestartSignal = getStep7RestartFromStep6Signal(); + if (initialRestartSignal) { + log('步骤 7:检测到登录页超时报错,准备回到步骤 6 重新发起登录验证码流程...', 'warn'); + return initialRestartSignal; + } + const start = Date.now(); let switchClickCount = 0; let lastSwitchAttemptAt = 0; @@ -212,6 +218,12 @@ async function prepareLoginCodeFlow(timeout = 15000) { continue; } + const restartSignal = getStep7RestartFromStep6Signal(); + if (restartSignal) { + log('步骤 7:检测到登录页超时报错,准备回到步骤 6 重新发起登录验证码流程...', 'warn'); + return restartSignal; + } + const passwordInput = document.querySelector('input[type="password"]'); const switchTrigger = findOneTimeCodeLoginTrigger(); @@ -239,7 +251,10 @@ async function prepareLoginCodeFlow(timeout = 15000) { async function resendVerificationCode(step, timeout = 45000) { if (step === 7) { - await prepareLoginCodeFlow(); + const prepareResult = await prepareLoginCodeFlow(); + if (prepareResult?.restartFromStep6) { + return prepareResult; + } } const start = Date.now(); @@ -382,8 +397,8 @@ const VERIFICATION_PAGE_PATTERN = /检查您的收件箱|输入我们刚刚向| const OAUTH_CONSENT_PAGE_PATTERN = /使用\s*ChatGPT\s*登录到\s*Codex|login\s+to\s+codex|log\s+in\s+to\s+codex|authorize|授权/i; const ADD_PHONE_PAGE_PATTERN = /add[\s-]*phone|添加手机号|手机号码|手机号|phone\s+number|telephone/i; const STEP5_SUBMIT_ERROR_PATTERN = /无法根据该信息创建帐户|请重试|unable\s+to\s+create\s+(?:your\s+)?account|couldn'?t\s+create\s+(?:your\s+)?account|something\s+went\s+wrong|invalid\s+(?:birthday|birth|date)|生日|出生日期/i; -const SIGNUP_PASSWORD_ERROR_TITLE_PATTERN = /糟糕,出错了|something\s+went\s+wrong|oops/i; -const SIGNUP_PASSWORD_ERROR_DETAIL_PATTERN = /operation\s+timed\s+out|timed\s+out|请求超时|操作超时/i; +const AUTH_TIMEOUT_ERROR_TITLE_PATTERN = /糟糕,出错了|something\s+went\s+wrong|oops/i; +const AUTH_TIMEOUT_ERROR_DETAIL_PATTERN = /operation\s+timed\s+out|timed\s+out|请求超时|操作超时/i; const SIGNUP_EMAIL_EXISTS_PATTERN = /与此电子邮件地址相关联的帐户已存在|account\s+associated\s+with\s+this\s+email\s+address\s+already\s+exists|email\s+address.*already\s+exists/i; function getVerificationErrorText() { @@ -466,6 +481,10 @@ function isAddPhonePageReady() { return ADD_PHONE_PAGE_PATTERN.test(getPageTextSnapshot()); } +function isLoginPage() { + return /\/log-in(?:[/?#]|$)/i.test(location.pathname || ''); +} + function isStep8Ready() { const continueBtn = getPrimaryContinueButton(); if (!continueBtn) return false; @@ -610,31 +629,47 @@ function getSignupPasswordSubmitButton({ allowDisabled = false } = {}) { }) || null; } -function getSignupRetryButton() { +function getAuthRetryButton({ allowDisabled = false } = {}) { const direct = document.querySelector('button[data-dd-action-name="Try again"]'); - if (direct && isVisibleElement(direct) && isActionEnabled(direct)) { + if (direct && isVisibleElement(direct) && (allowDisabled || isActionEnabled(direct))) { return direct; } const candidates = document.querySelectorAll('button, [role="button"]'); return Array.from(candidates).find((el) => { - if (!isVisibleElement(el) || !isActionEnabled(el)) return false; + if (!isVisibleElement(el) || (!allowDisabled && !isActionEnabled(el))) return false; const text = getActionText(el); return /重试|try\s+again/i.test(text); }) || null; } -function isSignupPasswordErrorPage() { - if (!isSignupPasswordPage()) return false; +function matchesAuthTimeoutErrorPage(pathPattern) { + if (!pathPattern.test(location.pathname || '')) return false; const text = getPageTextSnapshot(); return Boolean( - getSignupRetryButton() - && (SIGNUP_PASSWORD_ERROR_TITLE_PATTERN.test(text) - || SIGNUP_PASSWORD_ERROR_DETAIL_PATTERN.test(text) - || SIGNUP_PASSWORD_ERROR_TITLE_PATTERN.test(document.title || '')) + getAuthRetryButton({ allowDisabled: true }) + && (AUTH_TIMEOUT_ERROR_TITLE_PATTERN.test(text) + || AUTH_TIMEOUT_ERROR_DETAIL_PATTERN.test(text) + || AUTH_TIMEOUT_ERROR_TITLE_PATTERN.test(document.title || '')) ); } +function isSignupPasswordErrorPage() { + return matchesAuthTimeoutErrorPage(/\/create-account\/password(?:[/?#]|$)/i); +} + +function getStep7RestartFromStep6Signal() { + if (!isLoginPage() || !matchesAuthTimeoutErrorPage(/\/log-in(?:[/?#]|$)/i)) { + return null; + } + + return { + restartFromStep6: true, + reason: 'login_timeout_error_page', + url: location.href, + }; +} + function isSignupEmailAlreadyExistsPage() { return isSignupPasswordPage() && SIGNUP_EMAIL_EXISTS_PATTERN.test(getPageTextSnapshot()); } @@ -651,7 +686,7 @@ function inspectSignupVerificationState() { if (isSignupPasswordErrorPage()) { return { state: 'error', - retryButton: getSignupRetryButton(), + retryButton: getAuthRetryButton({ allowDisabled: true }), }; } @@ -804,7 +839,10 @@ async function fillVerificationCode(step, payload) { log(`步骤 ${step}:正在填写验证码:${code}`); if (step === 7) { - await prepareLoginCodeFlow(); + const prepareResult = await prepareLoginCodeFlow(); + if (prepareResult?.restartFromStep6) { + return prepareResult; + } } // Find code input — could be a single input or multiple separate inputs