抽取cookie清理逻辑为新的第六步
This commit is contained in:
@@ -70,6 +70,7 @@ const AUTO_RUN_MAX_RETRIES_PER_ROUND = 3;
|
||||
const AUTO_RUN_RETRY_DELAY_MS = 3000;
|
||||
const AUTO_RUN_TIMER_KIND_BETWEEN_ROUNDS = 'between_rounds';
|
||||
const AUTO_RUN_TIMER_KIND_BEFORE_RETRY = 'before_retry';
|
||||
const STEP_IDS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
const DEFAULT_STATE = {
|
||||
stepStatuses: {
|
||||
1: 'pending',
|
||||
@@ -81,6 +82,7 @@ const DEFAULT_STATE = {
|
||||
7: 'pending',
|
||||
8: 'pending',
|
||||
9: 'pending',
|
||||
10: 'pending',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -220,6 +222,7 @@ async function runAutoSequenceFromStep() {
|
||||
7: 'completed',
|
||||
8: 'completed',
|
||||
9: 'completed',
|
||||
10: 'completed',
|
||||
},
|
||||
tabRegistry: {
|
||||
'signup-page': { tabId: 88, ready: true },
|
||||
|
||||
@@ -61,15 +61,17 @@ const bundle = [
|
||||
|
||||
function createHarness(options = {}) {
|
||||
const {
|
||||
startStep = 6,
|
||||
failureStep = 9,
|
||||
startStep = 7,
|
||||
failureStep = 10,
|
||||
failureBudget = 1,
|
||||
failureMessage = '认证失败: Request failed with status code 502',
|
||||
authState = { state: 'password_page', url: 'https://auth.openai.com/log-in' },
|
||||
} = options;
|
||||
|
||||
return new Function(`
|
||||
const AUTO_STEP_DELAYS = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0 };
|
||||
const AUTO_STEP_DELAYS = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0 };
|
||||
const LAST_STEP_ID = 10;
|
||||
const FINAL_OAUTH_CHAIN_START_STEP = 7;
|
||||
const LOG_PREFIX = '[test]';
|
||||
const chrome = {
|
||||
tabs: {
|
||||
@@ -157,9 +159,9 @@ return {
|
||||
`)();
|
||||
}
|
||||
|
||||
test('auto-run keeps restarting from step 6 after post-login failures without a hard cap', async () => {
|
||||
test('auto-run keeps restarting from step 7 after post-login failures without a hard cap', async () => {
|
||||
const harness = createHarness({
|
||||
failureStep: 9,
|
||||
failureStep: 10,
|
||||
failureBudget: 6,
|
||||
failureMessage: '认证失败: Request failed with status code 502',
|
||||
authState: { state: 'password_page', url: 'https://auth.openai.com/log-in' },
|
||||
@@ -171,23 +173,23 @@ test('auto-run keeps restarting from step 6 after post-login failures without a
|
||||
assert.deepStrictEqual(
|
||||
events.steps,
|
||||
[
|
||||
6, 7, 8, 9,
|
||||
6, 7, 8, 9,
|
||||
6, 7, 8, 9,
|
||||
6, 7, 8, 9,
|
||||
6, 7, 8, 9,
|
||||
6, 7, 8, 9,
|
||||
6, 7, 8, 9,
|
||||
7, 8, 9, 10,
|
||||
7, 8, 9, 10,
|
||||
7, 8, 9, 10,
|
||||
7, 8, 9, 10,
|
||||
7, 8, 9, 10,
|
||||
7, 8, 9, 10,
|
||||
7, 8, 9, 10,
|
||||
]
|
||||
);
|
||||
assert.ok(events.logs.some(({ message }) => /回到步骤 6 重新开始授权流程/.test(message)));
|
||||
assert.ok(events.logs.some(({ message }) => /回到步骤 7 重新开始授权流程/.test(message)));
|
||||
});
|
||||
|
||||
test('auto-run stops restarting once add-phone is detected', async () => {
|
||||
const harness = createHarness({
|
||||
failureStep: 6,
|
||||
failureStep: 7,
|
||||
failureBudget: 1,
|
||||
failureMessage: '当前页面已进入手机号页。URL: https://auth.openai.com/add-phone',
|
||||
failureMessage: '当前页面已进入手机号页面。URL: https://auth.openai.com/add-phone',
|
||||
authState: { state: 'add_phone_page', url: 'https://auth.openai.com/add-phone' },
|
||||
});
|
||||
|
||||
@@ -195,13 +197,13 @@ test('auto-run stops restarting once add-phone is detected', async () => {
|
||||
|
||||
assert.ok(result?.error);
|
||||
assert.equal(result.events.invalidations.length, 0);
|
||||
assert.deepStrictEqual(result.events.steps, [6]);
|
||||
assert.deepStrictEqual(result.events.steps, [7]);
|
||||
assert.ok(result.events.logs.some(({ message }) => /进入 add-phone/.test(message)));
|
||||
});
|
||||
|
||||
test('auto-run stop errors after step 6 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({
|
||||
failureStep: 8,
|
||||
failureStep: 9,
|
||||
failureBudget: 1,
|
||||
failureMessage: '流程已被用户停止。',
|
||||
authState: { state: 'password_page', url: 'https://auth.openai.com/log-in' },
|
||||
@@ -211,6 +213,6 @@ test('auto-run stop errors after step 6 are rethrown immediately instead of rest
|
||||
|
||||
assert.equal(result?.error?.message, '流程已被用户停止。');
|
||||
assert.equal(result.events.invalidations.length, 0);
|
||||
assert.deepStrictEqual(result.events.steps, [6, 7, 8]);
|
||||
assert.ok(!result.events.logs.some(({ message }) => /回到步骤 6 重新开始授权流程/.test(message)));
|
||||
assert.deepStrictEqual(result.events.steps, [7, 8, 9]);
|
||||
assert.ok(!result.events.logs.some(({ message }) => /回到步骤 7 重新开始授权流程/.test(message)));
|
||||
});
|
||||
|
||||
@@ -67,7 +67,10 @@ const PERSISTED_SETTING_DEFAULTS = {
|
||||
};
|
||||
function normalizePanelMode(value) { return value === 'sub2api' ? 'sub2api' : 'cpa'; }
|
||||
function normalizeLocalCpaStep9Mode(value) { return value === 'bypass' ? 'bypass' : 'submit'; }
|
||||
function normalizeCpaCallbackMode(value) { return value === 'step6' ? 'step6' : 'step8'; }
|
||||
function normalizeCpaCallbackMode(value) {
|
||||
if (value === 'step7' || value === 'step6') return 'step7';
|
||||
return value === 'step9' || value === 'step8' ? 'step9' : 'step9';
|
||||
}
|
||||
function normalizeAutoRunFallbackThreadIntervalMinutes(value) { return Number(value) || 0; }
|
||||
function normalizeAutoRunDelayMinutes(value) { return Number(value) || 30; }
|
||||
function normalizeAutoStepDelaySeconds(value) { return value == null || value === '' ? null : Number(value); }
|
||||
|
||||
@@ -496,7 +496,7 @@ test('resetState preserves LuckMail session config, used map, and preserve tag c
|
||||
assert.equal(snapshot.storedPayload.currentLuckmailMailCursor, null);
|
||||
});
|
||||
|
||||
test('handleStepData step 9 marks current LuckMail purchase as used and clears runtime state', async () => {
|
||||
test('handleStepData step 10 marks current LuckMail purchase as used and clears runtime state', async () => {
|
||||
const bundle = extractFunction('handleStepData');
|
||||
|
||||
const factory = new Function(`
|
||||
@@ -561,7 +561,7 @@ return {
|
||||
`);
|
||||
|
||||
const api = factory();
|
||||
await api.handleStepData(9, {
|
||||
await api.handleStepData(10, {
|
||||
localhostUrl: 'http://localhost:1455/auth/callback?code=abc&state=xyz',
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
test('background imports step 1~9 modules', () => {
|
||||
test('background imports step 1~10 modules', () => {
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
|
||||
[
|
||||
@@ -11,6 +11,7 @@ test('background imports step 1~9 modules', () => {
|
||||
'background/steps/fill-password.js',
|
||||
'background/steps/fetch-signup-code.js',
|
||||
'background/steps/fill-profile.js',
|
||||
'background/steps/clear-login-cookies.js',
|
||||
'background/steps/oauth-login.js',
|
||||
'background/steps/fetch-login-code.js',
|
||||
'background/steps/confirm-oauth.js',
|
||||
|
||||
@@ -2,19 +2,43 @@ const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep6;`)(globalScope);
|
||||
test('step 6 runs cookie cleanup and completes from background', async () => {
|
||||
const source = fs.readFileSync('background/steps/clear-login-cookies.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep6;`)(globalScope);
|
||||
|
||||
test('step 6 retries up to configured limit and then fails', async () => {
|
||||
const events = {
|
||||
cleanupCalls: 0,
|
||||
completedSteps: [],
|
||||
};
|
||||
|
||||
const executor = api.createStep6Executor({
|
||||
completeStepFromBackground: async (step) => {
|
||||
events.completedSteps.push(step);
|
||||
},
|
||||
runPreStep6CookieCleanup: async () => {
|
||||
events.cleanupCalls += 1;
|
||||
},
|
||||
});
|
||||
|
||||
await executor.executeStep6();
|
||||
|
||||
assert.equal(events.cleanupCalls, 1);
|
||||
assert.deepStrictEqual(events.completedSteps, [6]);
|
||||
});
|
||||
|
||||
test('step 7 retries up to configured limit and then fails', async () => {
|
||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
|
||||
const events = {
|
||||
refreshCalls: 0,
|
||||
sendCalls: 0,
|
||||
completed: 0,
|
||||
};
|
||||
|
||||
const executor = api.createStep6Executor({
|
||||
const executor = api.createStep7Executor({
|
||||
addLog: async () => {},
|
||||
completeStepFromBackground: async () => {
|
||||
events.completed += 1;
|
||||
@@ -29,15 +53,12 @@ test('step 6 retries up to configured limit and then fails', async () => {
|
||||
return `https://oauth.example/${events.refreshCalls}`;
|
||||
},
|
||||
reuseOrCreateTab: async () => {},
|
||||
runPreStep6CookieCleanup: async () => {
|
||||
events.cleanupCalls += 1;
|
||||
},
|
||||
sendToContentScriptResilient: async () => {
|
||||
events.sendCalls += 1;
|
||||
return {
|
||||
step6Outcome: 'recoverable',
|
||||
state: 'email_page',
|
||||
message: '当前仍停留在邮箱页',
|
||||
message: '当前仍停留在邮箱页。',
|
||||
};
|
||||
},
|
||||
shouldSkipLoginVerificationForCpaCallback: () => false,
|
||||
@@ -47,57 +68,11 @@ test('step 6 retries up to configured limit and then fails', async () => {
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => executor.executeStep6({ email: 'user@example.com', password: 'secret' }),
|
||||
() => executor.executeStep7({ email: 'user@example.com', password: 'secret' }),
|
||||
/已重试 2 次,仍未成功/
|
||||
);
|
||||
|
||||
assert.equal(events.cleanupCalls, 1);
|
||||
assert.equal(events.refreshCalls, 3);
|
||||
assert.equal(events.sendCalls, 3);
|
||||
assert.equal(events.completed, 0);
|
||||
});
|
||||
|
||||
test('step 6 can skip pre-login cleanup during step 7 recovery replay', async () => {
|
||||
const events = {
|
||||
cleanupCalls: 0,
|
||||
completedPayloads: [],
|
||||
};
|
||||
|
||||
const executor = api.createStep6Executor({
|
||||
addLog: async () => {},
|
||||
completeStepFromBackground: async (step, payload) => {
|
||||
events.completedPayloads.push({ step, payload });
|
||||
},
|
||||
getErrorMessage: (error) => error?.message || String(error || ''),
|
||||
getLoginAuthStateLabel: (state) => state || 'unknown',
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||
isStep6RecoverableResult: () => false,
|
||||
isStep6SuccessResult: (result) => result?.step6Outcome === 'success',
|
||||
refreshOAuthUrlBeforeStep6: async () => 'https://oauth.example/latest',
|
||||
reuseOrCreateTab: async () => {},
|
||||
runPreStep6CookieCleanup: async () => {
|
||||
events.cleanupCalls += 1;
|
||||
},
|
||||
sendToContentScriptResilient: async () => ({
|
||||
step6Outcome: 'success',
|
||||
loginVerificationRequestedAt: 123,
|
||||
}),
|
||||
shouldSkipLoginVerificationForCpaCallback: () => false,
|
||||
skipLoginVerificationStepsForCpaCallback: async () => {},
|
||||
STEP6_MAX_ATTEMPTS: 3,
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await executor.executeStep6(
|
||||
{ email: 'user@example.com', password: 'secret' },
|
||||
{ skipPreLoginCleanup: true }
|
||||
);
|
||||
|
||||
assert.equal(events.cleanupCalls, 0);
|
||||
assert.deepStrictEqual(events.completedPayloads, [
|
||||
{
|
||||
step: 6,
|
||||
payload: { loginVerificationRequestedAt: 123 },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -4,17 +4,17 @@ const fs = require('node:fs');
|
||||
|
||||
const source = fs.readFileSync('background/steps/fetch-login-code.js', 'utf8');
|
||||
const globalScope = {};
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep8;`)(globalScope);
|
||||
|
||||
test('step 7 refreshes CPA oauth via step 6 replay before submitting verification code', async () => {
|
||||
test('step 8 refreshes CPA oauth via step 7 replay before submitting verification code', async () => {
|
||||
const calls = {
|
||||
ensureReady: 0,
|
||||
executeStep6: [],
|
||||
executeStep7: 0,
|
||||
sleep: [],
|
||||
resolveOptions: null,
|
||||
};
|
||||
|
||||
const executor = api.createStep7Executor({
|
||||
const executor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
@@ -23,12 +23,12 @@ test('step 7 refreshes CPA oauth via step 6 replay before submitting verificatio
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
confirmCustomVerificationStepBypass: async () => {},
|
||||
ensureStep7VerificationPageReady: async () => {
|
||||
ensureStep8VerificationPageReady: async () => {
|
||||
calls.ensureReady += 1;
|
||||
return { state: 'verification_page' };
|
||||
},
|
||||
executeStep6: async (_state, options = {}) => {
|
||||
calls.executeStep6.push(options);
|
||||
executeStep7: async () => {
|
||||
calls.executeStep7 += 1;
|
||||
},
|
||||
getMailConfig: () => ({
|
||||
provider: 'qq',
|
||||
@@ -61,7 +61,7 @@ test('step 7 refreshes CPA oauth via step 6 replay before submitting verificatio
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await executor.executeStep7({
|
||||
await executor.executeStep8({
|
||||
email: 'user@example.com',
|
||||
password: 'secret',
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
@@ -69,15 +69,15 @@ test('step 7 refreshes CPA oauth via step 6 replay before submitting verificatio
|
||||
|
||||
assert.equal(typeof calls.resolveOptions.beforeSubmit, 'function');
|
||||
assert.equal(calls.ensureReady, 2);
|
||||
assert.deepStrictEqual(calls.executeStep6, [{ skipPreLoginCleanup: true }]);
|
||||
assert.equal(calls.executeStep7, 1);
|
||||
assert.deepStrictEqual(calls.sleep, [1200]);
|
||||
assert.equal(calls.resolveOptions.resendIntervalMs, 25000);
|
||||
});
|
||||
|
||||
test('step 7 disables resend interval for 2925 mailbox polling', async () => {
|
||||
test('step 8 disables resend interval for 2925 mailbox polling', async () => {
|
||||
let capturedOptions = null;
|
||||
|
||||
const executor = api.createStep7Executor({
|
||||
const executor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
@@ -86,8 +86,8 @@ test('step 7 disables resend interval for 2925 mailbox polling', async () => {
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
confirmCustomVerificationStepBypass: async () => {},
|
||||
ensureStep7VerificationPageReady: async () => ({ state: 'verification_page' }),
|
||||
executeStep6: async () => {},
|
||||
ensureStep8VerificationPageReady: async () => ({ state: 'verification_page' }),
|
||||
executeStep7: async () => {},
|
||||
getMailConfig: () => ({
|
||||
provider: '2925',
|
||||
label: '2925 邮箱',
|
||||
@@ -116,7 +116,7 @@ test('step 7 disables resend interval for 2925 mailbox polling', async () => {
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await executor.executeStep7({
|
||||
await executor.executeStep8({
|
||||
email: 'user@example.com',
|
||||
password: 'secret',
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
|
||||
@@ -10,7 +10,7 @@ test('step definitions module exposes ordered shared step metadata', () => {
|
||||
const steps = api.getSteps();
|
||||
|
||||
assert.equal(Array.isArray(steps), true);
|
||||
assert.equal(steps.length >= 9, true);
|
||||
assert.equal(steps.length >= 10, true);
|
||||
assert.deepStrictEqual(
|
||||
steps.map((step) => step.order),
|
||||
steps.map((step) => step.order).slice().sort((left, right) => left - right)
|
||||
@@ -23,6 +23,7 @@ test('step definitions module exposes ordered shared step metadata', () => {
|
||||
'fill-password',
|
||||
'fetch-signup-code',
|
||||
'fill-profile',
|
||||
'clear-login-cookies',
|
||||
'oauth-login',
|
||||
'fetch-login-code',
|
||||
'confirm-oauth',
|
||||
|
||||
@@ -203,7 +203,7 @@ function setStep8PendingReject(handler) {
|
||||
${helperBundle}
|
||||
${step8ModuleSource}
|
||||
|
||||
const executor = self.MultiPageBackgroundStep8.createStep8Executor({
|
||||
const executor = self.MultiPageBackgroundStep9.createStep9Executor({
|
||||
addLog,
|
||||
chrome,
|
||||
cleanupStep8NavigationListeners,
|
||||
@@ -237,7 +237,7 @@ const executor = self.MultiPageBackgroundStep8.createStep8Executor({
|
||||
});
|
||||
|
||||
return {
|
||||
executeStep8: executor.executeStep8,
|
||||
executeStep9: executor.executeStep9,
|
||||
requestStop,
|
||||
resolveTabId(tabId) {
|
||||
if (!resolveTabId) {
|
||||
@@ -263,7 +263,7 @@ return {
|
||||
`)(step8ModuleSource);
|
||||
|
||||
(async () => {
|
||||
const step8Promise = api.executeStep8({ oauthUrl: 'https://example.com/oauth' });
|
||||
const step8Promise = api.executeStep9({ oauthUrl: 'https://example.com/oauth' });
|
||||
const settledStep8Promise = step8Promise.catch((err) => err);
|
||||
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
@@ -192,7 +192,7 @@ return {
|
||||
},
|
||||
});
|
||||
|
||||
await api.handleStepData(9, { localhostUrl: codexCallbackUrl });
|
||||
await api.handleStepData(10, { localhostUrl: codexCallbackUrl });
|
||||
let snapshot = api.snapshot();
|
||||
assert.deepStrictEqual(snapshot.removedBatches, [[1], [2]]);
|
||||
assert.strictEqual(snapshot.currentState.tabRegistry['signup-page'], null);
|
||||
|
||||
@@ -35,12 +35,12 @@ test('verification flow extends 2925 polling window', () => {
|
||||
});
|
||||
|
||||
const step4Payload = helpers.getVerificationPollPayload(4, { email: 'user@example.com', mailProvider: '2925' });
|
||||
const step7Payload = helpers.getVerificationPollPayload(7, { email: 'user@example.com', mailProvider: '2925' });
|
||||
const step8Payload = helpers.getVerificationPollPayload(8, { email: 'user@example.com', mailProvider: '2925' });
|
||||
|
||||
assert.equal(step4Payload.maxAttempts, 15);
|
||||
assert.equal(step4Payload.intervalMs, 15000);
|
||||
assert.equal(step7Payload.maxAttempts, 15);
|
||||
assert.equal(step7Payload.intervalMs, 15000);
|
||||
assert.equal(step8Payload.maxAttempts, 15);
|
||||
assert.equal(step8Payload.intervalMs, 15000);
|
||||
});
|
||||
|
||||
test('verification flow runs beforeSubmit hook before filling the code', async () => {
|
||||
|
||||
Reference in New Issue
Block a user