|
|
@@ -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} 轮后仍未成功。`);
|
|
|
}
|
|
|
|
|
|
// ============================================================
|