From 2165a0537f58a758e79cc4dfc482da6d1c86e7a4 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Fri, 10 Apr 2026 00:25:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=AD=A5=E9=AA=A47?= =?UTF-8?q?=E9=87=8D=E8=AF=95=E6=9C=BA=E5=88=B6=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=9B=9E=E9=80=80=E9=87=8D=E8=B7=91=E6=AD=A5=E9=AA=A4=E6=97=B6?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=A4=B1=E6=95=88=E5=90=8E=E7=BB=AD=E7=8A=B6?= =?UTF-8?q?=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- background.js | 133 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 2 deletions(-) diff --git a/background.js b/background.js index df14442..88b0deb 100644 --- a/background.js +++ b/background.js @@ -7,6 +7,7 @@ const DUCK_AUTOFILL_URL = 'https://duckduckgo.com/email/settings/autofill'; const STOP_ERROR_MESSAGE = '流程已被用户停止。'; const HUMAN_STEP_DELAY_MIN = 700; const HUMAN_STEP_DELAY_MAX = 2200; +const STEP7_RESTART_MAX_ROUNDS = 5; initializeSessionStorageAccess(); @@ -643,6 +644,15 @@ function isRetryableContentScriptTransportError(error) { return /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/i.test(message); } +function getErrorMessage(error) { + return String(typeof error === 'string' ? error : error?.message || ''); +} + +function isVerificationMailPollingError(error) { + const message = getErrorMessage(error); + return /未在 .*邮箱中找到新的匹配邮件|邮箱轮询结束,但未获取到验证码|无法获取新的(?:注册|登录)验证码|页面未能重新就绪|页面通信异常|did not respond in \d+s/i.test(message); +} + function isRestartCurrentAttemptError(error) { const message = String(typeof error === 'string' ? error : error?.message || ''); return /当前邮箱已存在,需要重新开始新一轮/.test(message); @@ -670,6 +680,80 @@ function hasSavedProgress(statuses = {}) { return Object.values({ ...DEFAULT_STATE.stepStatuses, ...statuses }).some((status) => status !== 'pending'); } +function getDownstreamStateResets(step) { + if (step <= 1) { + return { + oauthUrl: null, + flowStartTime: null, + password: null, + lastEmailTimestamp: null, + lastSignupCode: null, + lastLoginCode: null, + localhostUrl: null, + }; + } + if (step === 2) { + return { + password: null, + lastEmailTimestamp: null, + lastSignupCode: null, + lastLoginCode: null, + localhostUrl: null, + }; + } + if (step === 3 || step === 4) { + return { + lastEmailTimestamp: null, + lastSignupCode: null, + lastLoginCode: null, + localhostUrl: null, + }; + } + if (step === 5 || step === 6 || step === 7) { + return { + lastLoginCode: null, + localhostUrl: null, + }; + } + if (step === 8) { + return { + localhostUrl: null, + }; + } + return {}; +} + +async function invalidateDownstreamAfterStepRestart(step, options = {}) { + const { logLabel = `步骤 ${step} 重新执行` } = options; + const state = await getState(); + const statuses = { ...(state.stepStatuses || {}) }; + const changedSteps = []; + + for (let downstream = step + 1; downstream <= 9; downstream++) { + if (statuses[downstream] !== 'pending') { + statuses[downstream] = 'pending'; + changedSteps.push(downstream); + } + } + + if (changedSteps.length) { + await setState({ stepStatuses: statuses }); + for (const downstream of changedSteps) { + chrome.runtime.sendMessage({ + type: 'STEP_STATUS_CHANGED', + payload: { step: downstream, status: 'pending' }, + }).catch(() => {}); + } + await addLog(`${logLabel},已重置后续步骤状态:${changedSteps.join(', ')}`, 'warn'); + } + + const resets = getDownstreamStateResets(step); + if (Object.keys(resets).length) { + await setState(resets); + broadcastDataUpdate(resets); + } +} + function clearStopRequest() { stopRequested = false; } @@ -928,6 +1012,9 @@ async function handleMessage(message, sender) { await ensureManualInteractionAllowed('手动执行步骤'); } const step = message.payload.step; + if (message.source === 'sidepanel') { + await invalidateDownstreamAfterStepRestart(step, { logLabel: `步骤 ${step} 重新执行` }); + } // Save email if provided (from side panel step 3) if (message.payload.email) { await setEmailState(message.payload.email); @@ -1340,6 +1427,9 @@ async function runAutoSequenceFromStep(startStep, context = {}) { `步骤 9:检测到 OAuth callback 超时,正在回到步骤 6 重新开始授权流程(${step9RestartAttempts}/${maxStep9RestartAttempts})...`, 'warn' ); + await invalidateDownstreamAfterStepRestart(6, { + logLabel: `步骤 9 超时后准备回到步骤 6 重试(${step9RestartAttempts}/${maxStep9RestartAttempts})`, + }); step = 6; continue; } @@ -2022,7 +2112,7 @@ async function executeStep6(state) { // Step 7: Get Login Verification Code (qq-mail.js polls, then fills in chatgpt.js) // ============================================================ -async function executeStep7(state) { +async function runStep7Attempt(state) { const mail = getMailConfig(state); if (mail.error) throw new Error(mail.error); const stepStartedAt = Date.now(); @@ -2073,7 +2163,46 @@ async function executeStep7(state) { filterAfterTimestamp: stepStartedAt, requestFreshCodeFirst: true, }); - return; +} + +async function rerunStep6ForStep7Recovery() { + const currentState = await getState(); + const waitForStep6 = waitForStepComplete(6, 120000); + await addLog('步骤 7:正在回到步骤 6,重新发起登录验证码流程...', 'warn'); + await executeStep6(currentState); + await waitForStep6; + await sleepWithStop(3000); +} + +async function executeStep7(state) { + let lastError = null; + + for (let round = 1; round <= STEP7_RESTART_MAX_ROUNDS; round++) { + const currentState = round === 1 ? state : await getState(); + + try { + if (round > 1) { + await addLog(`步骤 7:正在进行第 ${round}/${STEP7_RESTART_MAX_ROUNDS} 轮登录验证码恢复尝试。`, 'warn'); + } + await runStep7Attempt(currentState); + return; + } catch (err) { + lastError = err; + + if (!isVerificationMailPollingError(err)) { + throw err; + } + + if (round >= STEP7_RESTART_MAX_ROUNDS) { + break; + } + + await addLog(`步骤 7:检测到邮箱轮询类失败,准备从步骤 6 重新开始(${round + 1}/${STEP7_RESTART_MAX_ROUNDS})...`, 'warn'); + await rerunStep6ForStep7Recovery(); + } + } + + throw lastError || new Error(`步骤 7:登录验证码流程在 ${STEP7_RESTART_MAX_ROUNDS} 轮后仍未成功。`); } // ============================================================