抽取cookie清理逻辑为新的第六步

This commit is contained in:
QLHazyCoder
2026-04-17 15:56:19 +08:00
parent b387ebfa91
commit cbe9b1fabf
30 changed files with 630 additions and 603 deletions
@@ -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 },
+22 -20
View File
@@ -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); }
+2 -2
View File
@@ -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 -1
View File
@@ -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',
+31 -56
View File
@@ -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 },
},
]);
});
+14 -14
View File
@@ -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',
+2 -1
View File
@@ -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',
+3 -3
View File
@@ -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));
+1 -1
View File
@@ -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);
+3 -3
View File
@@ -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 () => {