feat: 添加手机号认证失败处理逻辑,确保在遇到手机号页面时自动停止授权流程
This commit is contained in:
+16
-1
@@ -3844,6 +3844,14 @@ function isVerificationMailPollingError(error) {
|
|||||||
return /未在 .*邮箱中找到新的匹配邮件|未在 Hotmail 收件箱中找到新的匹配验证码|邮箱轮询结束,但未获取到验证码|无法获取新的(?:注册|登录)验证码|页面未能重新就绪|页面通信异常|did not respond in \d+s/i.test(message);
|
return /未在 .*邮箱中找到新的匹配邮件|未在 Hotmail 收件箱中找到新的匹配验证码|邮箱轮询结束,但未获取到验证码|无法获取新的(?:注册|登录)验证码|页面未能重新就绪|页面通信异常|did not respond in \d+s/i.test(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isAddPhoneAuthFailure(error) {
|
||||||
|
if (typeof loggingStatus !== 'undefined' && loggingStatus?.isAddPhoneAuthFailure) {
|
||||||
|
return loggingStatus.isAddPhoneAuthFailure(error);
|
||||||
|
}
|
||||||
|
const message = getErrorMessage(error);
|
||||||
|
return /https:\/\/auth\.openai\.com\/add-phone(?:[/?#]|$)|\badd-phone\b|添加手机号|手机号码|手机号页|手机号页面|手机号|phone\s+number|telephone/i.test(message);
|
||||||
|
}
|
||||||
|
|
||||||
function getLoginAuthStateLabel(state) {
|
function getLoginAuthStateLabel(state) {
|
||||||
if (typeof loggingStatus !== 'undefined' && loggingStatus?.getLoginAuthStateLabel) {
|
if (typeof loggingStatus !== 'undefined' && loggingStatus?.getLoginAuthStateLabel) {
|
||||||
return loggingStatus.getLoginAuthStateLabel(state);
|
return loggingStatus.getLoginAuthStateLabel(state);
|
||||||
@@ -5181,6 +5189,7 @@ const autoRunController = self.MultiPageBackgroundAutoRunController?.createAutoR
|
|||||||
getState,
|
getState,
|
||||||
getStopRequested: () => stopRequested,
|
getStopRequested: () => stopRequested,
|
||||||
hasSavedProgress,
|
hasSavedProgress,
|
||||||
|
isAddPhoneAuthFailure,
|
||||||
isRestartCurrentAttemptError,
|
isRestartCurrentAttemptError,
|
||||||
isStopError,
|
isStopError,
|
||||||
launchAutoRunTimerPlan,
|
launchAutoRunTimerPlan,
|
||||||
@@ -5788,6 +5797,7 @@ const step7Executor = self.MultiPageBackgroundStep7?.createStep7Executor({
|
|||||||
getLoginAuthStateLabel,
|
getLoginAuthStateLabel,
|
||||||
getOAuthFlowStepTimeoutMs,
|
getOAuthFlowStepTimeoutMs,
|
||||||
getState,
|
getState,
|
||||||
|
isAddPhoneAuthFailure,
|
||||||
isStep6RecoverableResult,
|
isStep6RecoverableResult,
|
||||||
isStep6SuccessResult,
|
isStep6SuccessResult,
|
||||||
refreshOAuthUrlBeforeStep6,
|
refreshOAuthUrlBeforeStep6,
|
||||||
@@ -6366,7 +6376,7 @@ async function getPostStep6AutoRestartDecision(step, error) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAddPhoneAuthUrl(errorMessage)) {
|
if (isAddPhoneAuthFailure(error) || isAddPhoneAuthUrl(errorMessage)) {
|
||||||
return {
|
return {
|
||||||
shouldRestart: false,
|
shouldRestart: false,
|
||||||
blockedByAddPhone: true,
|
blockedByAddPhone: true,
|
||||||
@@ -6440,6 +6450,11 @@ async function ensureStep8VerificationPageReady(options = {}) {
|
|||||||
throw new Error(`STEP8_RESTART_STEP7::步骤 8:当前认证页进入登录超时报错页,请回到步骤 7 重新开始。${urlPart}`.trim());
|
throw new Error(`STEP8_RESTART_STEP7::步骤 8:当前认证页进入登录超时报错页,请回到步骤 7 重新开始。${urlPart}`.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pageState.state === 'add_phone_page') {
|
||||||
|
const urlPart = pageState.url ? ` URL: ${pageState.url}` : '';
|
||||||
|
throw new Error(`步骤 8:当前认证页进入手机号页面,当前流程无法继续自动授权。${urlPart}`.trim());
|
||||||
|
}
|
||||||
|
|
||||||
const stateLabel = getLoginAuthStateLabel(pageState.state);
|
const stateLabel = getLoginAuthStateLabel(pageState.state);
|
||||||
const urlPart = pageState.url ? ` URL: ${pageState.url}` : '';
|
const urlPart = pageState.url ? ` URL: ${pageState.url}` : '';
|
||||||
throw new Error(`当前未进入登录验证码页面,请先重新完成步骤 7。当前状态:${stateLabel}.${urlPart}`.trim());
|
throw new Error(`当前未进入登录验证码页面,请先重新完成步骤 7。当前状态:${stateLabel}.${urlPart}`.trim());
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
getRunningSteps,
|
getRunningSteps,
|
||||||
getState,
|
getState,
|
||||||
hasSavedProgress,
|
hasSavedProgress,
|
||||||
|
isAddPhoneAuthFailure,
|
||||||
isRestartCurrentAttemptError,
|
isRestartCurrentAttemptError,
|
||||||
isStopError,
|
isStopError,
|
||||||
launchAutoRunTimerPlan,
|
launchAutoRunTimerPlan,
|
||||||
@@ -456,12 +457,36 @@
|
|||||||
|
|
||||||
const reason = getErrorMessage(err);
|
const reason = getErrorMessage(err);
|
||||||
roundSummary.failureReasons.push(reason);
|
roundSummary.failureReasons.push(reason);
|
||||||
const canRetry = autoRunSkipFailures && attemptRun < maxAttemptsForRound;
|
const blockedByAddPhone = typeof isAddPhoneAuthFailure === 'function' && isAddPhoneAuthFailure(err);
|
||||||
|
const canRetry = !blockedByAddPhone && autoRunSkipFailures && attemptRun < maxAttemptsForRound;
|
||||||
|
|
||||||
|
if (blockedByAddPhone) {
|
||||||
|
roundSummary.status = 'failed';
|
||||||
|
roundSummary.finalFailureReason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
await setState({
|
await setState({
|
||||||
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (blockedByAddPhone) {
|
||||||
|
await appendRoundRecordIfNeeded('failed', reason);
|
||||||
|
cancelPendingCommands('当前轮因认证流程进入 add-phone 已终止。');
|
||||||
|
await broadcastStopToContentScripts();
|
||||||
|
await addLog(
|
||||||
|
`第 ${targetRun}/${totalRuns} 轮触发 add-phone/手机号页,当前自动运行将立即停止,不再重试或进入下一轮。`,
|
||||||
|
'warn'
|
||||||
|
);
|
||||||
|
stoppedEarly = true;
|
||||||
|
await broadcastAutoRunStatus('stopped', {
|
||||||
|
currentRun: targetRun,
|
||||||
|
totalRuns,
|
||||||
|
attemptRun,
|
||||||
|
sessionId: 0,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (canRetry) {
|
if (canRetry) {
|
||||||
const retryIndex = attemptRun;
|
const retryIndex = attemptRun;
|
||||||
if (isRestartCurrentAttemptError(err)) {
|
if (isRestartCurrentAttemptError(err)) {
|
||||||
|
|||||||
@@ -61,6 +61,11 @@
|
|||||||
return /未在 .*邮箱中找到新的匹配邮件|未在 Hotmail 收件箱中找到新的匹配验证码|邮箱轮询结束,但未获取到验证码|无法获取新的(?:注册|登录)验证码|页面未能重新就绪|页面通信异常|did not respond in \d+s/i.test(message);
|
return /未在 .*邮箱中找到新的匹配邮件|未在 Hotmail 收件箱中找到新的匹配验证码|邮箱轮询结束,但未获取到验证码|无法获取新的(?:注册|登录)验证码|页面未能重新就绪|页面通信异常|did not respond in \d+s/i.test(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isAddPhoneAuthFailure(error) {
|
||||||
|
const message = getErrorMessage(error);
|
||||||
|
return /https:\/\/auth\.openai\.com\/add-phone(?:[/?#]|$)|\badd-phone\b|添加手机号|手机号码|手机号页|手机号页面|手机号|phone\s+number|telephone/i.test(message);
|
||||||
|
}
|
||||||
|
|
||||||
function getLoginAuthStateLabel(state) {
|
function getLoginAuthStateLabel(state) {
|
||||||
state = state === 'oauth_consent_page' ? 'unknown' : state;
|
state = state === 'oauth_consent_page' ? 'unknown' : state;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
@@ -148,6 +153,7 @@
|
|||||||
return {
|
return {
|
||||||
addLog,
|
addLog,
|
||||||
getAutoRunStatusPayload,
|
getAutoRunStatusPayload,
|
||||||
|
isAddPhoneAuthFailure,
|
||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
getFirstUnfinishedStep,
|
getFirstUnfinishedStep,
|
||||||
getLoginAuthStateLabel,
|
getLoginAuthStateLabel,
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
getLoginAuthStateLabel,
|
getLoginAuthStateLabel,
|
||||||
getOAuthFlowStepTimeoutMs,
|
getOAuthFlowStepTimeoutMs,
|
||||||
getState,
|
getState,
|
||||||
|
isAddPhoneAuthFailure = (error) => {
|
||||||
|
const message = String(typeof error === 'string' ? error : error?.message || '');
|
||||||
|
return /https:\/\/auth\.openai\.com\/add-phone(?:[/?#]|$)|\badd-phone\b|添加手机号|手机号码|手机号页|手机号页面|手机号|phone\s+number|telephone/i.test(message);
|
||||||
|
},
|
||||||
isStep6RecoverableResult,
|
isStep6RecoverableResult,
|
||||||
isStep6SuccessResult,
|
isStep6SuccessResult,
|
||||||
refreshOAuthUrlBeforeStep6,
|
refreshOAuthUrlBeforeStep6,
|
||||||
@@ -19,11 +23,6 @@
|
|||||||
throwIfStopped,
|
throwIfStopped,
|
||||||
} = deps;
|
} = deps;
|
||||||
|
|
||||||
function isAddPhoneAuthFailure(error) {
|
|
||||||
const message = String(typeof error === 'string' ? error : error?.message || '');
|
|
||||||
return /https:\/\/auth\.openai\.com\/add-phone(?:[/?#]|$)|add-phone|手机号页面|手机号码|手机号|phone\s+number|telephone/i.test(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function executeStep7(state) {
|
async function executeStep7(state) {
|
||||||
if (!state.email) {
|
if (!state.email) {
|
||||||
throw new Error('缺少邮箱地址,请先完成步骤 3。');
|
throw new Error('缺少邮箱地址,请先完成步骤 3。');
|
||||||
|
|||||||
@@ -642,6 +642,11 @@
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (submitResult.addPhonePage) {
|
||||||
|
const urlPart = submitResult.url ? ` URL: ${submitResult.url}` : '';
|
||||||
|
throw new Error(`步骤 ${step}:验证码提交后页面进入手机号页面,当前流程无法继续自动授权。${urlPart}`.trim());
|
||||||
|
}
|
||||||
|
|
||||||
await setState({
|
await setState({
|
||||||
lastEmailTimestamp: result.emailTimestamp,
|
lastEmailTimestamp: result.emailTimestamp,
|
||||||
[stateKey]: result.code,
|
[stateKey]: result.code,
|
||||||
|
|||||||
@@ -1442,7 +1442,7 @@ async function waitForVerificationSubmitOutcome(step, timeout) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (step === 8 && isAddPhonePageReady()) {
|
if (step === 8 && isAddPhonePageReady()) {
|
||||||
return { success: true, addPhonePage: true };
|
return { success: true, addPhonePage: true, url: location.href };
|
||||||
}
|
}
|
||||||
|
|
||||||
await sleep(150);
|
await sleep(150);
|
||||||
@@ -1499,7 +1499,7 @@ async function fillVerificationCode(step, payload) {
|
|||||||
if (outcome.invalidCode) {
|
if (outcome.invalidCode) {
|
||||||
log(`步骤 ${step}:验证码被拒绝:${outcome.errorText}`, 'warn');
|
log(`步骤 ${step}:验证码被拒绝:${outcome.errorText}`, 'warn');
|
||||||
} else if (outcome.addPhonePage) {
|
} else if (outcome.addPhonePage) {
|
||||||
log(`步骤 ${step}:验证码已通过,并已跳转到手机号页面。`, 'ok');
|
log(`步骤 ${step}:验证码提交后页面进入手机号页面,当前流程将停止自动授权。`, 'warn');
|
||||||
} else {
|
} else {
|
||||||
log(`步骤 ${step}:验证码已通过${outcome.assumed ? '(按成功推定)' : ''}。`, 'ok');
|
log(`步骤 ${step}:验证码已通过${outcome.assumed ? '(按成功推定)' : ''}。`, 'ok');
|
||||||
}
|
}
|
||||||
@@ -1541,7 +1541,7 @@ async function fillVerificationCode(step, payload) {
|
|||||||
if (outcome.invalidCode) {
|
if (outcome.invalidCode) {
|
||||||
log(`步骤 ${step}:验证码被拒绝:${outcome.errorText}`, 'warn');
|
log(`步骤 ${step}:验证码被拒绝:${outcome.errorText}`, 'warn');
|
||||||
} else if (outcome.addPhonePage) {
|
} else if (outcome.addPhonePage) {
|
||||||
log(`步骤 ${step}:验证码已通过,并已跳转到手机号页面。`, 'ok');
|
log(`步骤 ${step}:验证码提交后页面进入手机号页面,当前流程将停止自动授权。`, 'warn');
|
||||||
} else {
|
} else {
|
||||||
log(`步骤 ${step}:验证码已通过${outcome.assumed ? '(按成功推定)' : ''}。`, 'ok');
|
log(`步骤 ${step}:验证码已通过${outcome.assumed ? '(按成功推定)' : ''}。`, 'ok');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const source = fs.readFileSync('background/auto-run-controller.js', 'utf8');
|
||||||
|
const globalScope = {};
|
||||||
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundAutoRunController;`)(globalScope);
|
||||||
|
|
||||||
|
test('auto-run controller does not retry add-phone failures even when auto retry is enabled', async () => {
|
||||||
|
const events = {
|
||||||
|
logs: [],
|
||||||
|
broadcasts: [],
|
||||||
|
accountRecords: [],
|
||||||
|
runCalls: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let currentState = {
|
||||||
|
stepStatuses: {},
|
||||||
|
vpsUrl: 'https://example.com/vps',
|
||||||
|
vpsPassword: 'secret',
|
||||||
|
customPassword: '',
|
||||||
|
autoRunSkipFailures: true,
|
||||||
|
autoRunFallbackThreadIntervalMinutes: 0,
|
||||||
|
autoRunDelayEnabled: false,
|
||||||
|
autoRunDelayMinutes: 30,
|
||||||
|
autoStepDelaySeconds: null,
|
||||||
|
mailProvider: '163',
|
||||||
|
emailGenerator: 'duck',
|
||||||
|
gmailBaseEmail: '',
|
||||||
|
mail2925BaseEmail: '',
|
||||||
|
emailPrefix: 'demo',
|
||||||
|
inbucketHost: '',
|
||||||
|
inbucketMailbox: '',
|
||||||
|
cloudflareDomain: '',
|
||||||
|
cloudflareDomains: [],
|
||||||
|
tabRegistry: {},
|
||||||
|
sourceLastUrls: {},
|
||||||
|
autoRunRoundSummaries: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const runtime = {
|
||||||
|
state: {
|
||||||
|
autoRunActive: false,
|
||||||
|
autoRunCurrentRun: 0,
|
||||||
|
autoRunTotalRuns: 1,
|
||||||
|
autoRunAttemptRun: 0,
|
||||||
|
autoRunSessionId: 0,
|
||||||
|
},
|
||||||
|
get() {
|
||||||
|
return { ...this.state };
|
||||||
|
},
|
||||||
|
set(updates = {}) {
|
||||||
|
this.state = { ...this.state, ...updates };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let sessionSeed = 0;
|
||||||
|
|
||||||
|
const controller = api.createAutoRunController({
|
||||||
|
addLog: async (message, level = 'info') => {
|
||||||
|
events.logs.push({ message, level });
|
||||||
|
},
|
||||||
|
appendAccountRunRecord: async (status, _state, reason) => {
|
||||||
|
events.accountRecords.push({ status, reason });
|
||||||
|
return { status, reason };
|
||||||
|
},
|
||||||
|
AUTO_RUN_MAX_RETRIES_PER_ROUND: 3,
|
||||||
|
AUTO_RUN_RETRY_DELAY_MS: 3000,
|
||||||
|
AUTO_RUN_TIMER_KIND_BEFORE_RETRY: 'before_retry',
|
||||||
|
AUTO_RUN_TIMER_KIND_BETWEEN_ROUNDS: 'between_rounds',
|
||||||
|
broadcastAutoRunStatus: async (phase, payload = {}) => {
|
||||||
|
events.broadcasts.push({ phase, ...payload });
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
autoRunning: ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying', 'waiting_interval'].includes(phase),
|
||||||
|
autoRunPhase: phase,
|
||||||
|
autoRunCurrentRun: payload.currentRun ?? runtime.state.autoRunCurrentRun,
|
||||||
|
autoRunTotalRuns: payload.totalRuns ?? runtime.state.autoRunTotalRuns,
|
||||||
|
autoRunAttemptRun: payload.attemptRun ?? runtime.state.autoRunAttemptRun,
|
||||||
|
autoRunSessionId: payload.sessionId ?? runtime.state.autoRunSessionId,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
broadcastStopToContentScripts: async () => {},
|
||||||
|
cancelPendingCommands: () => {},
|
||||||
|
clearStopRequest: () => {},
|
||||||
|
createAutoRunSessionId: () => {
|
||||||
|
sessionSeed += 1;
|
||||||
|
return sessionSeed;
|
||||||
|
},
|
||||||
|
getAutoRunStatusPayload: (phase, payload = {}) => ({
|
||||||
|
autoRunning: ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying', 'waiting_interval'].includes(phase),
|
||||||
|
autoRunPhase: phase,
|
||||||
|
autoRunCurrentRun: payload.currentRun ?? 0,
|
||||||
|
autoRunTotalRuns: payload.totalRuns ?? 1,
|
||||||
|
autoRunAttemptRun: payload.attemptRun ?? 0,
|
||||||
|
autoRunSessionId: payload.sessionId ?? 0,
|
||||||
|
}),
|
||||||
|
getErrorMessage: (error) => error?.message || String(error || ''),
|
||||||
|
getFirstUnfinishedStep: () => 1,
|
||||||
|
getPendingAutoRunTimerPlan: () => null,
|
||||||
|
getRunningSteps: () => [],
|
||||||
|
getState: async () => ({
|
||||||
|
...currentState,
|
||||||
|
stepStatuses: { ...(currentState.stepStatuses || {}) },
|
||||||
|
tabRegistry: { ...(currentState.tabRegistry || {}) },
|
||||||
|
sourceLastUrls: { ...(currentState.sourceLastUrls || {}) },
|
||||||
|
}),
|
||||||
|
getStopRequested: () => false,
|
||||||
|
hasSavedProgress: () => false,
|
||||||
|
isAddPhoneAuthFailure: (error) => /add-phone|手机号页面|手机号页|手机号码|手机号/i.test(error?.message || String(error || '')),
|
||||||
|
isRestartCurrentAttemptError: () => false,
|
||||||
|
isStopError: (error) => (error?.message || String(error || '')) === '流程已被用户停止。',
|
||||||
|
launchAutoRunTimerPlan: async () => false,
|
||||||
|
normalizeAutoRunFallbackThreadIntervalMinutes: (value) => Math.max(0, Math.floor(Number(value) || 0)),
|
||||||
|
persistAutoRunTimerPlan: async () => ({}),
|
||||||
|
resetState: async () => {
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
stepStatuses: {},
|
||||||
|
tabRegistry: {},
|
||||||
|
sourceLastUrls: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
runAutoSequenceFromStep: async () => {
|
||||||
|
events.runCalls += 1;
|
||||||
|
throw new Error('步骤 8:验证码提交后页面进入手机号页面,当前流程无法继续自动授权。 URL: https://auth.openai.com/add-phone');
|
||||||
|
},
|
||||||
|
runtime,
|
||||||
|
setState: async (updates = {}) => {
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
...updates,
|
||||||
|
stepStatuses: updates.stepStatuses ? { ...updates.stepStatuses } : currentState.stepStatuses,
|
||||||
|
tabRegistry: updates.tabRegistry ? { ...updates.tabRegistry } : currentState.tabRegistry,
|
||||||
|
sourceLastUrls: updates.sourceLastUrls ? { ...updates.sourceLastUrls } : currentState.sourceLastUrls,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
throwIfAutoRunSessionStopped: (sessionId) => {
|
||||||
|
if (sessionId && sessionId !== runtime.state.autoRunSessionId) {
|
||||||
|
throw new Error('流程已被用户停止。');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
waitForRunningStepsToFinish: async () => currentState,
|
||||||
|
chrome: {
|
||||||
|
runtime: {
|
||||||
|
sendMessage() {
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await controller.autoRunLoop(1, {
|
||||||
|
autoRunSkipFailures: true,
|
||||||
|
mode: 'restart',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(events.runCalls, 1, 'add-phone fatal failure should stop before the next auto attempt starts');
|
||||||
|
assert.equal(events.broadcasts.some(({ phase }) => phase === 'retrying'), false, 'add-phone fatal failure should not enter retrying phase');
|
||||||
|
assert.equal(events.accountRecords.length, 1, 'fatal add-phone should still persist a failed round record');
|
||||||
|
assert.equal(events.accountRecords[0].status, 'failed');
|
||||||
|
assert.match(events.accountRecords[0].reason, /add-phone/);
|
||||||
|
assert.ok(events.logs.some(({ message }) => /add-phone\/手机号页/.test(message)));
|
||||||
|
assert.equal(runtime.state.autoRunActive, false);
|
||||||
|
assert.equal(runtime.state.autoRunSessionId, 0);
|
||||||
|
});
|
||||||
@@ -53,6 +53,7 @@ function extractFunction(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bundle = [
|
const bundle = [
|
||||||
|
extractFunction('isAddPhoneAuthFailure'),
|
||||||
extractFunction('isAddPhoneAuthUrl'),
|
extractFunction('isAddPhoneAuthUrl'),
|
||||||
extractFunction('isAddPhoneAuthState'),
|
extractFunction('isAddPhoneAuthState'),
|
||||||
extractFunction('getPostStep6AutoRestartDecision'),
|
extractFunction('getPostStep6AutoRestartDecision'),
|
||||||
@@ -198,6 +199,22 @@ test('auto-run stops restarting once add-phone is detected', async () => {
|
|||||||
assert.ok(result.events.logs.some(({ message }) => /进入 add-phone/.test(message)));
|
assert.ok(result.events.logs.some(({ message }) => /进入 add-phone/.test(message)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('auto-run stops restarting on generic phone-page failure messages even without add-phone url', async () => {
|
||||||
|
const harness = createHarness({
|
||||||
|
failureStep: 9,
|
||||||
|
failureBudget: 1,
|
||||||
|
failureMessage: '步骤 8:当前认证页进入手机号页面,当前流程无法继续自动授权。',
|
||||||
|
authState: { state: 'password_page', url: 'https://auth.openai.com/log-in' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await harness.runAndCaptureError();
|
||||||
|
|
||||||
|
assert.ok(result?.error);
|
||||||
|
assert.equal(result.events.invalidations.length, 0);
|
||||||
|
assert.deepStrictEqual(result.events.steps, [7, 8, 9]);
|
||||||
|
assert.ok(!result.events.logs.some(({ message }) => /回到步骤 7 重新开始授权流程/.test(message)));
|
||||||
|
});
|
||||||
|
|
||||||
test('auto-run stop errors after step 7 are rethrown immediately instead of restarting', async () => {
|
test('auto-run stop errors after step 7 are rethrown immediately instead of restarting', async () => {
|
||||||
const harness = createHarness({
|
const harness = createHarness({
|
||||||
failureStep: 9,
|
failureStep: 9,
|
||||||
|
|||||||
@@ -139,3 +139,65 @@ test('step 8 disables resend interval for 2925 mailbox polling', async () => {
|
|||||||
assert.equal(capturedOptions.beforeSubmit, undefined);
|
assert.equal(capturedOptions.beforeSubmit, undefined);
|
||||||
assert.equal(typeof capturedOptions.getRemainingTimeMs, 'function');
|
assert.equal(typeof capturedOptions.getRemainingTimeMs, 'function');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('step 8 does not rerun step 7 when verification submit lands on add-phone', async () => {
|
||||||
|
const calls = {
|
||||||
|
executeStep7: 0,
|
||||||
|
logs: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const executor = api.createStep8Executor({
|
||||||
|
addLog: async (message, level = 'info') => {
|
||||||
|
calls.logs.push({ message, level });
|
||||||
|
},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
confirmCustomVerificationStepBypass: async () => {},
|
||||||
|
ensureStep8VerificationPageReady: async () => ({ state: 'verification_page' }),
|
||||||
|
executeStep7: async () => {
|
||||||
|
calls.executeStep7 += 1;
|
||||||
|
},
|
||||||
|
getOAuthFlowRemainingMs: async () => 8000,
|
||||||
|
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => Math.min(defaultTimeoutMs, 8000),
|
||||||
|
getMailConfig: () => ({
|
||||||
|
provider: 'qq',
|
||||||
|
label: 'QQ 邮箱',
|
||||||
|
source: 'mail-qq',
|
||||||
|
url: 'https://mail.qq.com',
|
||||||
|
navigateOnReuse: false,
|
||||||
|
}),
|
||||||
|
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||||
|
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||||
|
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||||
|
isTabAlive: async () => true,
|
||||||
|
isVerificationMailPollingError: () => false,
|
||||||
|
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||||
|
resolveVerificationStep: async () => {
|
||||||
|
throw new Error('步骤 8:验证码提交后页面进入手机号页面,当前流程无法继续自动授权。 URL: https://auth.openai.com/add-phone');
|
||||||
|
},
|
||||||
|
reuseOrCreateTab: async () => {},
|
||||||
|
setState: async () => {},
|
||||||
|
setStepStatus: async () => {},
|
||||||
|
shouldUseCustomRegistrationEmail: () => false,
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000,
|
||||||
|
STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS: 8,
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => executor.executeStep8({
|
||||||
|
email: 'user@example.com',
|
||||||
|
password: 'secret',
|
||||||
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
}),
|
||||||
|
/add-phone/
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(calls.executeStep7, 0);
|
||||||
|
assert.ok(!calls.logs.some(({ message }) => /准备从步骤 7 重新开始/.test(message)));
|
||||||
|
});
|
||||||
|
|||||||
@@ -109,6 +109,71 @@ test('verification flow runs beforeSubmit hook before filling the code', async (
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('verification flow treats add-phone after login code submit as fatal instead of completing step 8', async () => {
|
||||||
|
const events = [];
|
||||||
|
|
||||||
|
const helpers = api.createVerificationFlowHelpers({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
completeStepFromBackground: async (_step, payload) => {
|
||||||
|
events.push(['complete', payload.code]);
|
||||||
|
},
|
||||||
|
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||||
|
getHotmailVerificationPollConfig: () => ({}),
|
||||||
|
getHotmailVerificationRequestTimestamp: () => 0,
|
||||||
|
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 (_source, message) => {
|
||||||
|
if (message.type === 'FILL_CODE') {
|
||||||
|
events.push(['submit', message.payload.code]);
|
||||||
|
return {
|
||||||
|
addPhonePage: true,
|
||||||
|
url: 'https://auth.openai.com/add-phone',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
sendToMailContentScriptResilient: async () => ({
|
||||||
|
code: '654321',
|
||||||
|
emailTimestamp: 123,
|
||||||
|
}),
|
||||||
|
setState: async (payload) => {
|
||||||
|
events.push(['state', payload.lastLoginCode || payload.lastSignupCode]);
|
||||||
|
},
|
||||||
|
setStepStatus: async () => {},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
VERIFICATION_POLL_MAX_ROUNDS: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => helpers.resolveVerificationStep(
|
||||||
|
8,
|
||||||
|
{ email: 'user@example.com', lastLoginCode: null },
|
||||||
|
{ provider: 'qq', label: 'QQ 邮箱' },
|
||||||
|
{}
|
||||||
|
),
|
||||||
|
/验证码提交后页面进入手机号页面/
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(events, [
|
||||||
|
['submit', '654321'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test('verification flow caps mail polling timeout to the remaining oauth budget', async () => {
|
test('verification flow caps mail polling timeout to the remaining oauth budget', async () => {
|
||||||
const mailPollCalls = [];
|
const mailPollCalls = [];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user