重构 OAuth 总超时为 CPA 来源专属策略

This commit is contained in:
QLHazyCoder
2026-05-24 20:53:32 +08:00
parent 3a60560e27
commit a1793334db
21 changed files with 111 additions and 88 deletions
+34 -5
View File
@@ -1350,7 +1350,6 @@ const PERSISTED_SETTING_DEFAULTS = {
gopayHelperApiKeyStatus: '', gopayHelperApiKeyStatus: '',
autoRunSkipFailures: false, autoRunSkipFailures: false,
autoRunFallbackThreadIntervalMinutes: 0, autoRunFallbackThreadIntervalMinutes: 0,
oauthFlowTimeoutEnabled: true,
operationDelayEnabled: true, operationDelayEnabled: true,
autoStepDelaySeconds: null, autoStepDelaySeconds: null,
step6CookieCleanupEnabled: false, step6CookieCleanupEnabled: false,
@@ -3329,7 +3328,6 @@ function normalizePersistentSettingValue(key, value) {
case 'gopayHelperRemainingUses': case 'gopayHelperRemainingUses':
return Math.max(0, Number(value) || 0); return Math.max(0, Number(value) || 0);
case 'autoRunSkipFailures': case 'autoRunSkipFailures':
case 'oauthFlowTimeoutEnabled':
case 'gopayHelperLocalSmsHelperEnabled': case 'gopayHelperLocalSmsHelperEnabled':
case 'gopayHelperAutoModeEnabled': case 'gopayHelperAutoModeEnabled':
return Boolean(value); return Boolean(value);
@@ -14441,15 +14439,46 @@ function normalizeOAuthFlowSourceUrl(value) {
return normalized || null; return normalized || null;
} }
function resolveOAuthTimeoutBudgetScope(state = {}) {
const activeFlowId = self.MultiPageFlowRegistry?.normalizeFlowId
? self.MultiPageFlowRegistry.normalizeFlowId(
state?.activeFlowId || state?.flowId,
DEFAULT_ACTIVE_FLOW_ID
)
: (String(state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase()
|| DEFAULT_ACTIVE_FLOW_ID);
const capabilityState = typeof resolveCurrentFlowCapabilities === 'function'
? resolveCurrentFlowCapabilities(state, { activeFlowId })
: null;
const targetId = capabilityState?.effectiveTargetId || (self.MultiPageFlowRegistry?.normalizeTargetId
? self.MultiPageFlowRegistry.normalizeTargetId(
activeFlowId,
state?.targetId,
self.MultiPageFlowRegistry.getDefaultTargetId?.(activeFlowId)
)
: String(state?.targetId || '').trim().toLowerCase());
const targetCapabilities = capabilityState?.targetCapabilities || (self.MultiPageFlowRegistry?.getTargetCapabilities
? self.MultiPageFlowRegistry.getTargetCapabilities(activeFlowId, targetId)
: null);
return {
activeFlowId,
targetId,
enabled: activeFlowId === DEFAULT_ACTIVE_FLOW_ID && Boolean(targetCapabilities?.usesOauthTimeoutBudget),
};
}
function shouldUseOAuthTimeoutBudget(state = {}) {
return resolveOAuthTimeoutBudgetScope(state).enabled;
}
async function startOAuthFlowTimeoutWindow(options = {}) { async function startOAuthFlowTimeoutWindow(options = {}) {
const step = Number(options.step) || 7; const step = Number(options.step) || 7;
const state = options.state || await getState(); const state = options.state || await getState();
if (state?.oauthFlowTimeoutEnabled === false) { if (!shouldUseOAuthTimeoutBudget(state)) {
await setState({ await setState({
oauthFlowDeadlineAt: null, oauthFlowDeadlineAt: null,
oauthFlowDeadlineSourceUrl: null, oauthFlowDeadlineSourceUrl: null,
}); });
await addLog(`步骤 ${step}:已拿到新的 OAuth 登录地址,授权后链总超时已关闭,仅保留各步骤本地等待超时。`, 'info');
return null; return null;
} }
@@ -14466,7 +14495,7 @@ async function getOAuthFlowRemainingMs(options = {}) {
const step = Number(options.step) || 7; const step = Number(options.step) || 7;
const actionLabel = String(options.actionLabel || '后续授权流程').trim() || '后续授权流程'; const actionLabel = String(options.actionLabel || '后续授权流程').trim() || '后续授权流程';
const state = options.state || await getState(); const state = options.state || await getState();
if (state?.oauthFlowTimeoutEnabled === false) { if (!shouldUseOAuthTimeoutBudget(state)) {
return null; return null;
} }
-6
View File
@@ -1423,16 +1423,10 @@
|| (nextPlusModeEnabled && plusPaymentChanged) || (nextPlusModeEnabled && plusPaymentChanged)
|| (nextPlusModeEnabled && plusAccountAccessStrategyChanged) || (nextPlusModeEnabled && plusAccountAccessStrategyChanged)
|| phoneSignupReloginAfterBindEmailChanged; || phoneSignupReloginAfterBindEmailChanged;
const oauthFlowTimeoutDisabled = Object.prototype.hasOwnProperty.call(updates, 'oauthFlowTimeoutEnabled')
&& updates.oauthFlowTimeoutEnabled === false;
const canonicalSettingsUpdates = await setPersistentSettings(updates); const canonicalSettingsUpdates = await setPersistentSettings(updates);
const stateUpdates = { const stateUpdates = {
...canonicalSettingsUpdates, ...canonicalSettingsUpdates,
...sessionUpdates, ...sessionUpdates,
...(oauthFlowTimeoutDisabled ? {
oauthFlowDeadlineAt: null,
oauthFlowDeadlineSourceUrl: null,
} : {}),
}; };
if (Object.prototype.hasOwnProperty.call(canonicalSettingsUpdates, 'activeFlowId') if (Object.prototype.hasOwnProperty.call(canonicalSettingsUpdates, 'activeFlowId')
&& !Object.prototype.hasOwnProperty.call(stateUpdates, 'flowId')) { && !Object.prototype.hasOwnProperty.call(stateUpdates, 'flowId')) {
+1 -1
View File
@@ -33,7 +33,6 @@
contributionAdapterIds: [], contributionAdapterIds: [],
supportedTargetIds: [], supportedTargetIds: [],
supportsLuckmail: false, supportsLuckmail: false,
supportsOauthTimeoutBudget: false,
canSwitchFlow: true, canSwitchFlow: true,
stepDefinitionMode: 'default', stepDefinitionMode: 'default',
targetSelectorLabel: '来源', targetSelectorLabel: '来源',
@@ -59,6 +58,7 @@
const DEFAULT_TARGET_CAPABILITIES = Object.freeze({ const DEFAULT_TARGET_CAPABILITIES = Object.freeze({
supportsPhoneSignup: true, supportsPhoneSignup: true,
requiresPhoneSignupWarning: false, requiresPhoneSignupWarning: false,
usesOauthTimeoutBudget: false,
supportedPlusAccountAccessStrategies: Object.freeze([PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH]), supportedPlusAccountAccessStrategies: Object.freeze([PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH]),
}); });
-1
View File
@@ -18,7 +18,6 @@
contributionAdapterIds: [], contributionAdapterIds: [],
supportedTargetIds: [], supportedTargetIds: [],
supportsLuckmail: false, supportsLuckmail: false,
supportsOauthTimeoutBudget: false,
canSwitchFlow: true, canSwitchFlow: true,
stepDefinitionMode: 'default', stepDefinitionMode: 'default',
targetSelectorLabel: '\u6765\u6e90', targetSelectorLabel: '\u6765\u6e90',
+1 -2
View File
@@ -49,7 +49,6 @@ flowCapabilities.openai = {
supportsContributionMode: true, supportsContributionMode: true,
supportsPlatformBinding: ['cpa', 'sub2api', 'codex2api'], supportsPlatformBinding: ['cpa', 'sub2api', 'codex2api'],
supportsLuckmail: true, supportsLuckmail: true,
supportsOauthTimeoutBudget: true,
stepDefinitionMode: 'openai-dynamic', stepDefinitionMode: 'openai-dynamic',
}; };
@@ -61,7 +60,6 @@ flowCapabilities.siteA = {
supportsContributionMode: false, supportsContributionMode: false,
supportsPlatformBinding: ['siteA-panel'], supportsPlatformBinding: ['siteA-panel'],
supportsLuckmail: false, supportsLuckmail: false,
supportsOauthTimeoutBudget: false,
stepDefinitionMode: 'siteA', stepDefinitionMode: 'siteA',
}; };
``` ```
@@ -82,6 +80,7 @@ panelCapabilities = {
cpa: { cpa: {
supportsPhoneSignup: true, supportsPhoneSignup: true,
requiresPhoneSignupWarning: true, requiresPhoneSignupWarning: true,
usesOauthTimeoutBudget: true,
}, },
sub2api: { sub2api: {
supportsPhoneSignup: true, supportsPhoneSignup: true,
-1
View File
@@ -30,7 +30,6 @@
contributionAdapterIds: [], contributionAdapterIds: [],
supportedTargetIds: ['webchat2api'], supportedTargetIds: ['webchat2api'],
supportsLuckmail: false, supportsLuckmail: false,
supportsOauthTimeoutBudget: false,
canSwitchFlow: true, canSwitchFlow: true,
stepDefinitionMode: 'grok', stepDefinitionMode: 'grok',
targetSelectorLabel: '来源', targetSelectorLabel: '来源',
-1
View File
@@ -34,7 +34,6 @@
"kiro-rs" "kiro-rs"
], ],
"supportsLuckmail": false, "supportsLuckmail": false,
"supportsOauthTimeoutBudget": false,
"canSwitchFlow": true, "canSwitchFlow": true,
"stepDefinitionMode": "kiro", "stepDefinitionMode": "kiro",
"targetSelectorLabel": "来源" "targetSelectorLabel": "来源"
+1 -2
View File
@@ -38,7 +38,6 @@
"codex2api" "codex2api"
], ],
"supportsLuckmail": true, "supportsLuckmail": true,
"supportsOauthTimeoutBudget": true,
"canSwitchFlow": true, "canSwitchFlow": true,
"stepDefinitionMode": "openai-dynamic", "stepDefinitionMode": "openai-dynamic",
"targetSelectorLabel": "来源" "targetSelectorLabel": "来源"
@@ -427,7 +426,6 @@
"id": "openai-oauth", "id": "openai-oauth",
"label": "OAuth", "label": "OAuth",
"rowIds": [ "rowIds": [
"row-oauth-flow-timeout",
"row-oauth-display", "row-oauth-display",
"row-oauth-callback" "row-oauth-callback"
] ]
@@ -444,6 +442,7 @@
"cpa": { "cpa": {
"supportsPhoneSignup": true, "supportsPhoneSignup": true,
"requiresPhoneSignupWarning": true, "requiresPhoneSignupWarning": true,
"usesOauthTimeoutBudget": true,
"supportedPlusAccountAccessStrategies": [ "supportedPlusAccountAccessStrategies": [
"oauth", "oauth",
"cpa_codex_session" "cpa_codex_session"
-11
View File
@@ -2231,17 +2231,6 @@ header {
text-align: center; text-align: center;
} }
.oauth-flow-timeout-setting {
gap: 8px;
min-width: 0;
}
.oauth-flow-timeout-caption {
min-width: 0 !important;
overflow: hidden;
text-overflow: ellipsis;
}
.setting-caption-left { .setting-caption-left {
text-align: left; text-align: left;
} }
-16
View File
@@ -728,22 +728,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="data-row" id="row-oauth-flow-timeout">
<span class="data-label">授权总超时</span>
<div class="data-inline setting-pair">
<div class="setting-group setting-group-primary oauth-flow-timeout-setting">
<label class="toggle-switch" for="input-oauth-flow-timeout-enabled"
title="关闭后不再用 5 分钟总预算重启授权链,但页面、点击、回调等本地等待超时仍会生效">
<input type="checkbox" id="input-oauth-flow-timeout-enabled" checked />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
<span>启用</span>
</label>
<span class="setting-caption oauth-flow-timeout-caption">关闭后只取消 Step 7 后链总预算</span>
</div>
</div>
</div>
<div class="data-row" id="row-step-execution-range"> <div class="data-row" id="row-step-execution-range">
<span class="data-label">执行范围</span> <span class="data-label">执行范围</span>
<div class="data-inline setting-pair step-execution-range-setting"> <div class="data-inline setting-pair step-execution-range-setting">
-12
View File
@@ -429,7 +429,6 @@ const inputAutoSkipFailures = document.getElementById('input-auto-skip-failures'
const inputAutoSkipFailuresThreadIntervalMinutes = document.getElementById('input-auto-skip-failures-thread-interval-minutes'); const inputAutoSkipFailuresThreadIntervalMinutes = document.getElementById('input-auto-skip-failures-thread-interval-minutes');
const inputStep6CookieCleanupEnabled = document.getElementById('input-step6-cookie-cleanup-enabled'); const inputStep6CookieCleanupEnabled = document.getElementById('input-step6-cookie-cleanup-enabled');
const inputAutoStepDelaySeconds = document.getElementById('input-auto-step-delay-seconds'); const inputAutoStepDelaySeconds = document.getElementById('input-auto-step-delay-seconds');
const inputOAuthFlowTimeoutEnabled = document.getElementById('input-oauth-flow-timeout-enabled');
const rowStepExecutionRange = document.getElementById('row-step-execution-range'); const rowStepExecutionRange = document.getElementById('row-step-execution-range');
const inputStepExecutionRangeEnabled = document.getElementById('input-step-execution-range-enabled'); const inputStepExecutionRangeEnabled = document.getElementById('input-step-execution-range-enabled');
const inputStepExecutionRangeFrom = document.getElementById('input-step-execution-range-from'); const inputStepExecutionRangeFrom = document.getElementById('input-step-execution-range-from');
@@ -5119,9 +5118,6 @@ function collectSettingsPayload() {
? buildStepExecutionRangeByFlowPayload(latestState?.stepExecutionRangeByFlow) ? buildStepExecutionRangeByFlowPayload(latestState?.stepExecutionRangeByFlow)
: (latestState?.stepExecutionRangeByFlow || {}), : (latestState?.stepExecutionRangeByFlow || {}),
autoStepDelaySeconds: normalizeAutoStepDelaySeconds(inputAutoStepDelaySeconds.value), autoStepDelaySeconds: normalizeAutoStepDelaySeconds(inputAutoStepDelaySeconds.value),
oauthFlowTimeoutEnabled: typeof inputOAuthFlowTimeoutEnabled !== 'undefined' && inputOAuthFlowTimeoutEnabled
? Boolean(inputOAuthFlowTimeoutEnabled.checked)
: true,
phoneVerificationEnabled: effectivePhoneVerificationEnabled, phoneVerificationEnabled: effectivePhoneVerificationEnabled,
signupMethod: effectiveSignupMethod, signupMethod: effectiveSignupMethod,
phoneSignupReloginAfterBindEmailEnabled: typeof inputPhoneSignupReloginAfterBindEmail !== 'undefined' && inputPhoneSignupReloginAfterBindEmail phoneSignupReloginAfterBindEmailEnabled: typeof inputPhoneSignupReloginAfterBindEmail !== 'undefined' && inputPhoneSignupReloginAfterBindEmail
@@ -11374,11 +11370,6 @@ function applySettingsState(state) {
inputStep6CookieCleanupEnabled.checked = Boolean(state?.step6CookieCleanupEnabled); inputStep6CookieCleanupEnabled.checked = Boolean(state?.step6CookieCleanupEnabled);
} }
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(state?.autoStepDelaySeconds); inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(state?.autoStepDelaySeconds);
if (typeof inputOAuthFlowTimeoutEnabled !== 'undefined' && inputOAuthFlowTimeoutEnabled) {
inputOAuthFlowTimeoutEnabled.checked = state?.oauthFlowTimeoutEnabled !== undefined
? Boolean(state.oauthFlowTimeoutEnabled)
: true;
}
if (inputVerificationResendCount) { if (inputVerificationResendCount) {
const restoredVerificationResendCount = state?.verificationResendCount !== undefined const restoredVerificationResendCount = state?.verificationResendCount !== undefined
? state.verificationResendCount ? state.verificationResendCount
@@ -17849,9 +17840,6 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message.payload.autoStepDelaySeconds !== undefined) { if (message.payload.autoStepDelaySeconds !== undefined) {
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(message.payload.autoStepDelaySeconds); inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(message.payload.autoStepDelaySeconds);
} }
if (message.payload.oauthFlowTimeoutEnabled !== undefined && typeof inputOAuthFlowTimeoutEnabled !== 'undefined' && inputOAuthFlowTimeoutEnabled) {
inputOAuthFlowTimeoutEnabled.checked = Boolean(message.payload.oauthFlowTimeoutEnabled);
}
if ( if (
( (
message.payload.verificationResendCount !== undefined message.payload.verificationResendCount !== undefined
+51 -6
View File
@@ -48,6 +48,33 @@ function extractFunction(name) {
return source.slice(start, end); return source.slice(start, end);
} }
const OAUTH_TIMEOUT_BUDGET_TEST_REGISTRY = `
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
const self = {
MultiPageFlowRegistry: {
normalizeFlowId(value, fallback = 'openai') {
const normalized = String(value || '').trim().toLowerCase();
return ['openai', 'kiro', 'grok'].includes(normalized) ? normalized : fallback;
},
normalizeTargetId(flowId, value, fallback = 'cpa') {
const normalized = String(value || '').trim().toLowerCase();
if (flowId === 'openai' && ['cpa', 'sub2api', 'codex2api'].includes(normalized)) return normalized;
if (flowId === 'kiro') return 'kiro-rs';
if (flowId === 'grok') return 'webchat2api';
return fallback;
},
getDefaultTargetId(flowId) {
return flowId === 'openai' ? 'cpa' : '';
},
getTargetCapabilities(flowId, targetId) {
return flowId === 'openai' && targetId === 'cpa'
? { usesOauthTimeoutBudget: true }
: { usesOauthTimeoutBudget: false };
},
},
};
`;
test('background auth chain set does not include Plus session import nodes', () => { test('background auth chain set does not include Plus session import nodes', () => {
const authChainStart = source.indexOf('const AUTH_CHAIN_NODE_IDS = new Set(['); const authChainStart = source.indexOf('const AUTH_CHAIN_NODE_IDS = new Set([');
const authChainEnd = source.indexOf(']);', authChainStart); const authChainEnd = source.indexOf(']);', authChainStart);
@@ -431,6 +458,9 @@ test('oauth timeout budget ignores stale deadlines from an old oauth url', async
const api = new Function(` const api = new Function(`
const LOG_PREFIX = '[test]'; const LOG_PREFIX = '[test]';
const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000; const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000;
${OAUTH_TIMEOUT_BUDGET_TEST_REGISTRY}
${extractFunction('resolveOAuthTimeoutBudgetScope')}
${extractFunction('shouldUseOAuthTimeoutBudget')}
${extractFunction('normalizeOAuthFlowDeadlineAt')} ${extractFunction('normalizeOAuthFlowDeadlineAt')}
${extractFunction('normalizeOAuthFlowSourceUrl')} ${extractFunction('normalizeOAuthFlowSourceUrl')}
${extractFunction('getOAuthFlowRemainingMs')} ${extractFunction('getOAuthFlowRemainingMs')}
@@ -444,6 +474,8 @@ return {
step: 8, step: 8,
actionLabel: '登录验证码流程', actionLabel: '登录验证码流程',
state: { state: {
activeFlowId: 'openai',
targetId: 'cpa',
oauthUrl: 'https://oauth.example/current', oauthUrl: 'https://oauth.example/current',
oauthFlowDeadlineAt: Date.now() + 1200, oauthFlowDeadlineAt: Date.now() + 1200,
oauthFlowDeadlineSourceUrl: 'https://oauth.example/old', oauthFlowDeadlineSourceUrl: 'https://oauth.example/old',
@@ -457,7 +489,10 @@ test('oauth timeout budget clamps local timeout when enabled by default', async
const api = new Function(` const api = new Function(`
const LOG_PREFIX = '[test]'; const LOG_PREFIX = '[test]';
const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000; const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000;
${OAUTH_TIMEOUT_BUDGET_TEST_REGISTRY}
${extractFunction('buildOAuthFlowTimeoutError')} ${extractFunction('buildOAuthFlowTimeoutError')}
${extractFunction('resolveOAuthTimeoutBudgetScope')}
${extractFunction('shouldUseOAuthTimeoutBudget')}
${extractFunction('normalizeOAuthFlowDeadlineAt')} ${extractFunction('normalizeOAuthFlowDeadlineAt')}
${extractFunction('normalizeOAuthFlowSourceUrl')} ${extractFunction('normalizeOAuthFlowSourceUrl')}
${extractFunction('getOAuthFlowRemainingMs')} ${extractFunction('getOAuthFlowRemainingMs')}
@@ -471,6 +506,8 @@ return {
step: 8, step: 8,
actionLabel: '登录验证码流程', actionLabel: '登录验证码流程',
state: { state: {
activeFlowId: 'openai',
targetId: 'cpa',
oauthUrl: 'https://oauth.example/current', oauthUrl: 'https://oauth.example/current',
oauthFlowDeadlineAt: Date.now() + 1200, oauthFlowDeadlineAt: Date.now() + 1200,
oauthFlowDeadlineSourceUrl: 'https://oauth.example/current', oauthFlowDeadlineSourceUrl: 'https://oauth.example/current',
@@ -481,11 +518,14 @@ return {
assert(timeoutMs >= 1000); assert(timeoutMs >= 1000);
}); });
test('oauth timeout budget disabled mode ignores active deadlines', async () => { test('oauth timeout budget ignores active deadlines outside openai cpa target', async () => {
const api = new Function(` const api = new Function(`
const LOG_PREFIX = '[test]'; const LOG_PREFIX = '[test]';
const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000; const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000;
${OAUTH_TIMEOUT_BUDGET_TEST_REGISTRY}
${extractFunction('buildOAuthFlowTimeoutError')} ${extractFunction('buildOAuthFlowTimeoutError')}
${extractFunction('resolveOAuthTimeoutBudgetScope')}
${extractFunction('shouldUseOAuthTimeoutBudget')}
${extractFunction('normalizeOAuthFlowDeadlineAt')} ${extractFunction('normalizeOAuthFlowDeadlineAt')}
${extractFunction('normalizeOAuthFlowSourceUrl')} ${extractFunction('normalizeOAuthFlowSourceUrl')}
${extractFunction('getOAuthFlowRemainingMs')} ${extractFunction('getOAuthFlowRemainingMs')}
@@ -499,7 +539,8 @@ return {
step: 9, step: 9,
actionLabel: 'OAuth localhost 回调', actionLabel: 'OAuth localhost 回调',
state: { state: {
oauthFlowTimeoutEnabled: false, activeFlowId: 'openai',
targetId: 'sub2api',
oauthUrl: 'https://oauth.example/current', oauthUrl: 'https://oauth.example/current',
oauthFlowDeadlineAt: Date.now() - 1000, oauthFlowDeadlineAt: Date.now() - 1000,
oauthFlowDeadlineSourceUrl: 'https://oauth.example/current', oauthFlowDeadlineSourceUrl: 'https://oauth.example/current',
@@ -509,16 +550,18 @@ return {
assert.equal(timeoutMs, 15000); assert.equal(timeoutMs, 15000);
}); });
test('startOAuthFlowTimeoutWindow clears stale deadline when timeout is disabled', async () => { test('startOAuthFlowTimeoutWindow clears stale deadline outside openai cpa target', async () => {
const events = { const events = {
stateUpdates: [], stateUpdates: [],
logs: [], logs: [],
}; };
const api = new Function('events', ` const api = new Function('events', `
const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000; const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000;
${OAUTH_TIMEOUT_BUDGET_TEST_REGISTRY}
async function getState() { async function getState() {
return { return {
oauthFlowTimeoutEnabled: false, activeFlowId: 'openai',
targetId: 'sub2api',
oauthFlowDeadlineAt: Date.now() - 1000, oauthFlowDeadlineAt: Date.now() - 1000,
oauthFlowDeadlineSourceUrl: 'https://oauth.example/old', oauthFlowDeadlineSourceUrl: 'https://oauth.example/old',
}; };
@@ -529,6 +572,8 @@ async function setState(update) {
async function addLog(message, level) { async function addLog(message, level) {
events.logs.push({ message, level }); events.logs.push({ message, level });
} }
${extractFunction('resolveOAuthTimeoutBudgetScope')}
${extractFunction('shouldUseOAuthTimeoutBudget')}
${extractFunction('normalizeOAuthFlowSourceUrl')} ${extractFunction('normalizeOAuthFlowSourceUrl')}
${extractFunction('startOAuthFlowTimeoutWindow')} ${extractFunction('startOAuthFlowTimeoutWindow')}
return { return {
@@ -546,7 +591,7 @@ return {
oauthFlowDeadlineAt: null, oauthFlowDeadlineAt: null,
oauthFlowDeadlineSourceUrl: null, oauthFlowDeadlineSourceUrl: null,
}]); }]);
assert.match(events.logs[0].message, /授权后链总超时已关闭/); assert.deepStrictEqual(events.logs, []);
}); });
test('oauth localhost timeout recovery resumes from bound-email relogin tail when present', async () => { test('oauth localhost timeout recovery resumes from bound-email relogin tail when present', async () => {
@@ -156,12 +156,11 @@ return {
}); });
}); });
test('oauth flow timeout setting is persisted and normalized as boolean', () => { test('cloudflare temp email settings payload ignores removed UI-only flags', () => {
const api = new Function(` const api = new Function(`
const DEFAULT_VERIFICATION_RESEND_COUNT = 4; const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
const PERSISTED_SETTING_DEFAULTS = { const PERSISTED_SETTING_DEFAULTS = {
panelMode: 'cpa', panelMode: 'cpa',
oauthFlowTimeoutEnabled: true,
autoStepDelaySeconds: null, autoStepDelaySeconds: null,
verificationResendCount: DEFAULT_VERIFICATION_RESEND_COUNT, verificationResendCount: DEFAULT_VERIFICATION_RESEND_COUNT,
mailProvider: '163', mailProvider: '163',
@@ -222,15 +221,10 @@ return {
}; };
`)(); `)();
assert.equal(api.normalizePersistentSettingValue('oauthFlowTimeoutEnabled', 0), false);
assert.equal(api.normalizePersistentSettingValue('oauthFlowTimeoutEnabled', 1), true);
assert.deepEqual(api.buildPersistentSettingsPayload({ assert.deepEqual(api.buildPersistentSettingsPayload({
oauthFlowTimeoutEnabled: false, removedUiOnlyFlag: false,
}), { }), {});
oauthFlowTimeoutEnabled: false,
});
const defaults = api.buildPersistentSettingsPayload({}, { fillDefaults: true }); const defaults = api.buildPersistentSettingsPayload({}, { fillDefaults: true });
assert.equal(defaults.oauthFlowTimeoutEnabled, true); assert.equal(Object.prototype.hasOwnProperty.call(defaults, 'removedUiOnlyFlag'), false);
}); });
+2
View File
@@ -27,6 +27,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
assert.equal(enabledState.canUsePhoneSignup, true); assert.equal(enabledState.canUsePhoneSignup, true);
assert.equal(enabledState.effectiveSignupMethod, 'phone'); assert.equal(enabledState.effectiveSignupMethod, 'phone');
assert.equal(enabledState.shouldWarnCpaPhoneSignup, true); assert.equal(enabledState.shouldWarnCpaPhoneSignup, true);
assert.equal(enabledState.targetCapabilities.usesOauthTimeoutBudget, true);
assert.deepEqual(enabledState.effectiveSignupMethods, ['email', 'phone']); assert.deepEqual(enabledState.effectiveSignupMethods, ['email', 'phone']);
const plusLockedState = registry.resolveSidepanelCapabilities({ const plusLockedState = registry.resolveSidepanelCapabilities({
@@ -43,6 +44,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
assert.equal(plusLockedState.canUsePhoneSignup, false); assert.equal(plusLockedState.canUsePhoneSignup, false);
assert.equal(plusLockedState.effectiveSignupMethod, 'email'); assert.equal(plusLockedState.effectiveSignupMethod, 'email');
assert.equal(plusLockedState.shouldWarnCpaPhoneSignup, false); assert.equal(plusLockedState.shouldWarnCpaPhoneSignup, false);
assert.equal(plusLockedState.targetCapabilities.usesOauthTimeoutBudget, false);
assert.deepEqual(plusLockedState.effectiveSignupMethods, ['email']); assert.deepEqual(plusLockedState.effectiveSignupMethods, ['email']);
}); });
@@ -29,6 +29,14 @@ test('flow registry exposes canonical flow and target metadata', () => {
flowRegistry.getFlowDefinition('openai')?.targets?.cpa?.defaultState, flowRegistry.getFlowDefinition('openai')?.targets?.cpa?.defaultState,
{ vpsUrl: '', vpsPassword: '', localCpaStep9Mode: 'submit' } { vpsUrl: '', vpsPassword: '', localCpaStep9Mode: 'submit' }
); );
assert.equal(
flowRegistry.getTargetCapabilities('openai', 'cpa')?.usesOauthTimeoutBudget,
true
);
assert.equal(
flowRegistry.getTargetCapabilities('openai', 'sub2api')?.usesOauthTimeoutBudget,
undefined
);
assert.deepEqual( assert.deepEqual(
flowRegistry.getFlowDefinition('kiro')?.targets?.['kiro-rs']?.defaultState, flowRegistry.getFlowDefinition('kiro')?.targets?.['kiro-rs']?.defaultState,
{ baseUrl: '', apiKey: '' } { baseUrl: '', apiKey: '' }
@@ -248,7 +248,6 @@ const inputTempEmailUseRandomSubdomain = { checked: true };
const inputAutoSkipFailures = { checked: false }; const inputAutoSkipFailures = { checked: false };
const inputAutoSkipFailuresThreadIntervalMinutes = { value: '5' }; const inputAutoSkipFailuresThreadIntervalMinutes = { value: '5' };
const inputAutoStepDelaySeconds = { value: '10' }; const inputAutoStepDelaySeconds = { value: '10' };
const inputOAuthFlowTimeoutEnabled = { checked: true };
const inputVerificationResendCount = { value: '6' }; const inputVerificationResendCount = { value: '6' };
const DEFAULT_VERIFICATION_RESEND_COUNT = 4; const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms'; const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
-2
View File
@@ -133,7 +133,6 @@ const inputTempEmailUseRandomSubdomain = { checked: false };
const inputAutoSkipFailures = { checked: false }; const inputAutoSkipFailures = { checked: false };
const inputAutoSkipFailuresThreadIntervalMinutes = { value: '0' }; const inputAutoSkipFailuresThreadIntervalMinutes = { value: '0' };
const inputAutoStepDelaySeconds = { value: '' }; const inputAutoStepDelaySeconds = { value: '' };
const inputOAuthFlowTimeoutEnabled = { checked: true };
const inputVerificationResendCount = { value: '4' }; const inputVerificationResendCount = { value: '4' };
const DEFAULT_VERIFICATION_RESEND_COUNT = 4; const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms'; const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
@@ -406,7 +405,6 @@ const inputLuckmailDomain = { value: '' };
const inputAutoSkipFailures = { checked: false }; const inputAutoSkipFailures = { checked: false };
const inputAutoSkipFailuresThreadIntervalMinutes = { value: '' }; const inputAutoSkipFailuresThreadIntervalMinutes = { value: '' };
const inputAutoStepDelaySeconds = { value: '' }; const inputAutoStepDelaySeconds = { value: '' };
const inputOAuthFlowTimeoutEnabled = { checked: true };
const inputVerificationResendCount = { value: '' }; const inputVerificationResendCount = { value: '' };
const inputPhoneVerificationEnabled = { checked: false }; const inputPhoneVerificationEnabled = { checked: false };
const selectPhoneSmsProvider = { value: 'hero-sms' }; const selectPhoneSmsProvider = { value: 'hero-sms' };
@@ -204,7 +204,6 @@ const inputTempEmailUseRandomSubdomain = { checked: false };
const inputAutoSkipFailures = { checked: false }; const inputAutoSkipFailures = { checked: false };
const inputAutoSkipFailuresThreadIntervalMinutes = { value: '0' }; const inputAutoSkipFailuresThreadIntervalMinutes = { value: '0' };
const inputAutoStepDelaySeconds = { value: '' }; const inputAutoStepDelaySeconds = { value: '' };
const inputOAuthFlowTimeoutEnabled = { checked: true };
const inputVerificationResendCount = { value: '4' }; const inputVerificationResendCount = { value: '4' };
const DEFAULT_VERIFICATION_RESEND_COUNT = 4; const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms'; const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
+3 -4
View File
@@ -48,7 +48,6 @@ test('sidepanel splits shared auto-run controls from openai oauth controls', ()
const step6CookieIndex = html.indexOf('id="row-step6-cookie-settings"'); const step6CookieIndex = html.indexOf('id="row-step6-cookie-settings"');
const sharedAutoRunIndex = html.indexOf('id="row-shared-auto-run"'); const sharedAutoRunIndex = html.indexOf('id="row-shared-auto-run"');
const threadIntervalIndex = html.indexOf('id="row-auto-run-thread-interval"'); const threadIntervalIndex = html.indexOf('id="row-auto-run-thread-interval"');
const oauthTimeoutIndex = html.indexOf('id="row-oauth-flow-timeout"');
const stepRangeIndex = html.indexOf('id="row-step-execution-range"'); const stepRangeIndex = html.indexOf('id="row-step-execution-range"');
const oauthDisplayIndex = html.indexOf('id="row-oauth-display"'); const oauthDisplayIndex = html.indexOf('id="row-oauth-display"');
const oauthCallbackIndex = html.indexOf('id="row-oauth-callback"'); const oauthCallbackIndex = html.indexOf('id="row-oauth-callback"');
@@ -57,15 +56,15 @@ test('sidepanel splits shared auto-run controls from openai oauth controls', ()
assert.notEqual(step6CookieIndex, -1); assert.notEqual(step6CookieIndex, -1);
assert.notEqual(sharedAutoRunIndex, -1); assert.notEqual(sharedAutoRunIndex, -1);
assert.notEqual(threadIntervalIndex, -1); assert.notEqual(threadIntervalIndex, -1);
assert.notEqual(oauthTimeoutIndex, -1); assert.doesNotMatch(html, /id="row-oauth-flow-timeout"/);
assert.doesNotMatch(html, /id="input-oauth-flow-timeout-enabled"/);
assert.notEqual(stepRangeIndex, -1); assert.notEqual(stepRangeIndex, -1);
assert.notEqual(oauthDisplayIndex, -1); assert.notEqual(oauthDisplayIndex, -1);
assert.notEqual(oauthCallbackIndex, -1); assert.notEqual(oauthCallbackIndex, -1);
assert.notEqual(settingsActionsIndex, -1); assert.notEqual(settingsActionsIndex, -1);
assert.ok(sharedAutoRunIndex > step6CookieIndex, 'shared auto-run should render below the openai step6 cookie row'); assert.ok(sharedAutoRunIndex > step6CookieIndex, 'shared auto-run should render below the openai step6 cookie row');
assert.ok(threadIntervalIndex > sharedAutoRunIndex, 'thread interval should be part of the shared auto-run block'); assert.ok(threadIntervalIndex > sharedAutoRunIndex, 'thread interval should be part of the shared auto-run block');
assert.ok(threadIntervalIndex < oauthTimeoutIndex, 'thread interval should stay outside openai oauth controls'); assert.ok(stepRangeIndex > threadIntervalIndex, 'step execution range should render below shared thread interval');
assert.ok(stepRangeIndex > oauthTimeoutIndex, 'step execution range should render below oauth timeout');
assert.ok(stepRangeIndex < oauthDisplayIndex, 'step execution range should stay above oauth runtime display'); assert.ok(stepRangeIndex < oauthDisplayIndex, 'step execution range should stay above oauth runtime display');
assert.ok(oauthCallbackIndex > oauthDisplayIndex, 'openai callback row should follow the oauth display'); assert.ok(oauthCallbackIndex > oauthDisplayIndex, 'openai callback row should follow the oauth display');
assert.ok(settingsActionsIndex > oauthCallbackIndex, 'save settings action should live outside the callback row'); assert.ok(settingsActionsIndex > oauthCallbackIndex, 'save settings action should live outside the callback row');
+1 -1
View File
@@ -281,7 +281,7 @@
- Kiro 目标配置:`flows.kiro.targetId``flows.kiro.targets["kiro-rs"] = { baseUrl, apiKey }` - Kiro 目标配置:`flows.kiro.targetId``flows.kiro.targets["kiro-rs"] = { baseUrl, apiKey }`
- 自动运行默认配置 - 自动运行默认配置
- 操作间延迟兼容字段 `operationDelayEnabled`:当前会被严格归一为启用;sidepanel 不再暴露真正关闭入口,但内容脚本仍通过 `chrome.storage.local` 监听该字段完成兼容同步 - 操作间延迟兼容字段 `operationDelayEnabled`:当前会被严格归一为启用;sidepanel 不再暴露真正关闭入口,但内容脚本仍通过 `chrome.storage.local` 监听该字段完成兼容同步
- OAuth 授权后链总超时开关 `oauthFlowTimeoutEnabled`:默认开启;关闭后会立即清空已存在的 OAuth 总预算 deadline,仅保留各步骤本地等待超时 - OAuth 授权后链总超时:不再作为侧栏配置项持久化,只由 `openai + cpa` target capability 自动启用;其他来源与 flow 不启动 OAuth 总预算,仅保留各步骤本地等待超时
- flow 级执行范围配置 `stepExecutionRangeByFlow`:按 flowId 保存,例如 `openai: { enabled: true, fromStep: 3, toStep: 6 }`;侧栏中的 `codex` 会归一到内部默认 flowId `openai`。这是通用配置结构,但当前只给 codex/openai flow 暴露 UI。 - flow 级执行范围配置 `stepExecutionRangeByFlow`:按 flowId 保存,例如 `openai: { enabled: true, fromStep: 3, toStep: 6 }`;侧栏中的 `codex` 会归一到内部默认 flowId `openai`。这是通用配置结构,但当前只给 codex/openai flow 暴露 UI。
- 账号运行历史 `accountRunHistory`(以统一账号标识为主键;邮箱账号保留邮箱主键,手机号注册保留手机号主键;同一轮中后续绑定的邮箱或手机号会写入同一条记录的副身份字段,保存最近一次状态:成功/失败/停止) - 账号运行历史 `accountRunHistory`(以统一账号标识为主键;邮箱账号保留邮箱主键,手机号注册保留手机号主键;同一轮中后续绑定的邮箱或手机号会写入同一条记录的副身份字段,保存最近一次状态:成功/失败/停止)
- 账号运行历史本地同步 helper 地址 - 账号运行历史本地同步 helper 地址
+5 -5
View File
@@ -21,7 +21,7 @@
- `.gitignore`:定义仓库忽略规则,当前忽略 `docs/md/``.github/``_metadata/``.vscode/` 等目录。 - `.gitignore`:定义仓库忽略规则,当前忽略 `docs/md/``.github/``_metadata/``.vscode/` 等目录。
- `LICENSE`:项目许可证文件。 - `LICENSE`:项目许可证文件。
- `README.md`:面向使用者的精简说明文档,主要介绍项目用途、功能范围、快速开始与文档入口;不再承载过多技术实现细节。 - `README.md`:面向使用者的精简说明文档,主要介绍项目用途、功能范围、快速开始与文档入口;不再承载过多技术实现细节。
- `background.js`:扩展后台 Service Worker 入口壳,负责模块装配、初始化、全局常量、少量保留的领域函数与运行入口;当前已按 `activeFlowId` 装配 flow-aware 的步骤定义、执行注册表、自动运行与状态同步,`openai` flow 继续承接 `oauthFlowTimeoutEnabled` `stepExecutionRangeByFlow``kiro` flow 走独立的“注册页 1-6 步 -> 桌面授权 7-8 步 -> `kiro.rs` 上传 9 步”链路,`grok` flow 走独立的“注册页 1-4 步 -> SSO Cookie 提取 5 步 -> `webchat2api` 上传 6 步”链路。 - `background.js`:扩展后台 Service Worker 入口壳,负责模块装配、初始化、全局常量、少量保留的领域函数与运行入口;当前已按 `activeFlowId` 装配 flow-aware 的步骤定义、执行注册表、自动运行与状态同步,OAuth 后链 5 分钟总预算只由 `openai + cpa` target capability 自动启用,其他来源与 flow 不启用该预算;`openai` flow 继续承接 `stepExecutionRangeByFlow``kiro` flow 走独立的“注册页 1-6 步 -> 桌面授权 7-8 步 -> `kiro.rs` 上传 9 步”链路,`grok` flow 走独立的“注册页 1-4 步 -> SSO Cookie 提取 5 步 -> `webchat2api` 上传 6 步”链路。
- `cloudmail-utils.js`Cloud Mail / SkyMail 相关的纯工具函数,负责 API 地址、域名、鉴权头、邮件列表响应与邮件正文归一化。 - `cloudmail-utils.js`Cloud Mail / SkyMail 相关的纯工具函数,负责 API 地址、域名、鉴权头、邮件列表响应与邮件正文归一化。
- `cloudflare-temp-email-utils.js`Cloudflare Temp Email 相关的纯工具函数,负责 URL、域名、邮件内容与 MIME 数据归一化。 - `cloudflare-temp-email-utils.js`Cloudflare Temp Email 相关的纯工具函数,负责 URL、域名、邮件内容与 MIME 数据归一化。
- `gopay-utils.js`GoPay / GPC Plus 支付相关纯工具函数,负责支付方式归一化、GoPay 手机号/OTP/PIN 归一化、GPC API 地址归一化、API Key 请求头、任务创建/查询/OTP/PIN/停止 URL 与余额响应解析。 - `gopay-utils.js`GoPay / GPC Plus 支付相关纯工具函数,负责支付方式归一化、GoPay 手机号/OTP/PIN 归一化、GPC API 地址归一化、API Key 请求头、任务创建/查询/OTP/PIN/停止 URL 与余额响应解析。
@@ -68,7 +68,7 @@
- `background/mail-2925-session.js`:2925 会话模块,负责 2925 账号池持久化、当前账号切换、cookie 清理登出、自动登录、命中“子邮箱已达上限邮箱”后的 24 小时禁用与自动切号。 - `background/mail-2925-session.js`:2925 会话模块,负责 2925 账号池持久化、当前账号切换、cookie 清理登出、自动登录、命中“子邮箱已达上限邮箱”后的 24 小时禁用与自动切号。
- `background/mail-rule-registry.js`flow-aware 邮件规则注册表,负责按 `activeFlowId` 选择验证码规则构造器,并统一输出注册/登录验证码轮询 payload。 - `background/mail-rule-registry.js`flow-aware 邮件规则注册表,负责按 `activeFlowId` 选择验证码规则构造器,并统一输出注册/登录验证码轮询 payload。
- `background/flow-mail-polling.js`flow-aware 邮件轮询调度层,负责读取共享邮箱配置、调用当前 flow 的 mail rules、分派 Hotmail / LuckMail / Cloudflare Temp Email / Cloud Mail / YYDS Mail API provider、准备 iCloud / 2925 / 网页邮箱会话,并统一通过邮箱内容脚本轮询验证码。 - `background/flow-mail-polling.js`flow-aware 邮件轮询调度层,负责读取共享邮箱配置、调用当前 flow 的 mail rules、分派 Hotmail / LuckMail / Cloudflare Temp Email / Cloud Mail / YYDS Mail API provider、准备 iCloud / 2925 / 网页邮箱会话,并统一通过邮箱内容脚本轮询验证码。
- `background/message-router.js`:后台消息路由层,负责处理 `chrome.runtime.onMessage` 进入的所有业务消息;`LOG / STEP_COMPLETE / STEP_ERROR` 会把消息里的真实 `step` 继续传给结构化日志,跳过登录验证码这类派生日志也按当前步骤 key 写入;当前额外接入 PayPal 账号池的新增与切换、2925 账号池的新增、导入、切换、登录、禁用与删除消息,以及 IP 代理的同步、切换、Change 与出口检测消息;手动改邮箱、手动取邮箱等入口也统一经这里落到 `setEmailState`,同步维护注册邮箱运行态;当侧栏关闭 `oauthFlowTimeoutEnabled` 时,会立即清空已存在的 OAuth 总预算 deadline手动 `EXECUTE_NODE` 会先按 `stepExecutionRangeByFlow` 校验当前节点是否允许执行。 - `background/message-router.js`:后台消息路由层,负责处理 `chrome.runtime.onMessage` 进入的所有业务消息;`LOG / STEP_COMPLETE / STEP_ERROR` 会把消息里的真实 `step` 继续传给结构化日志,跳过登录验证码这类派生日志也按当前步骤 key 写入;当前额外接入 PayPal 账号池的新增与切换、2925 账号池的新增、导入、切换、登录、禁用与删除消息,以及 IP 代理的同步、切换、Change 与出口检测消息;手动改邮箱、手动取邮箱等入口也统一经这里落到 `setEmailState`,同步维护注册邮箱运行态;手动 `EXECUTE_NODE` 会先按 `stepExecutionRangeByFlow` 校验当前节点是否允许执行。
- `background/registration-email-state.js`:注册邮箱运行态共享模块,负责维护 `registrationEmailState = { current, previous, source, updatedAt }`,并提供“当前邮箱清空时保留上一比较基线”“Duck 生成前解析比较基线”“Step 8 `add-email` 写入邮箱时按需保留手机号身份”的统一工具。 - `background/registration-email-state.js`:注册邮箱运行态共享模块,负责维护 `registrationEmailState = { current, previous, source, updatedAt }`,并提供“当前邮箱清空时保留上一比较基线”“Duck 生成前解析比较基线”“Step 8 `add-email` 写入邮箱时按需保留手机号身份”的统一工具。
- `core/flow-kernel/runtime-state.js`:运行态视图与 patch 构造模块,负责把会话字段归一到 `runtimeState / sharedState / serviceState / flowState` 结构,并维护 flow-aware 的 session patch、节点状态默认值与兼容视图输出。 - `core/flow-kernel/runtime-state.js`:运行态视图与 patch 构造模块,负责把会话字段归一到 `runtimeState / sharedState / serviceState / flowState` 结构,并维护 flow-aware 的 session patch、节点状态默认值与兼容视图输出。
- `background/navigation-utils.js`:导航与 URL 判断工具层,负责 callback、入口页、CPA / SUB2API / Codex2API 地址归一化、来源标签页家族判断与步骤跳转相关判断。 - `background/navigation-utils.js`:导航与 URL 判断工具层,负责 callback、入口页、CPA / SUB2API / Codex2API 地址归一化、来源标签页家族判断与步骤跳转相关判断。
@@ -84,7 +84,7 @@
## `background/steps/` ## `background/steps/`
- `flows/openai/background/steps/wait-registration-success.js`:步骤 6 实现,负责注册资料提交后等待 20 秒,让注册成功状态和页面跳转稳定;默认不清理 cookies,只有侧栏第六步 `清 Cookies` 开关开启时才会在等待结束后清理 ChatGPT / OpenAI 相关 cookies。 - `flows/openai/background/steps/wait-registration-success.js`:步骤 6 实现,负责注册资料提交后等待 20 秒,让注册成功状态和页面跳转稳定;默认不清理 cookies,只有侧栏第六步 `清 Cookies` 开关开启时才会在等待结束后清理 ChatGPT / OpenAI 相关 cookies。
- `flows/openai/background/steps/confirm-oauth.js`:步骤 9 实现,负责 OAuth 同意页按钮定位、点击、localhost 回调监听与回调完成;等待 callback 期间会动态检查 OAuth 总预算开关,关闭后仅保留本地回调等待超时。 - `flows/openai/background/steps/confirm-oauth.js`:步骤 9 实现,负责 OAuth 同意页按钮定位、点击、localhost 回调监听与回调完成;等待 callback 期间会动态检查 OAuth 总预算,只有 `openai + cpa` 来源启用 5 分钟后链预算,其他来源和 flow 仅保留本地回调等待超时。
- `flows/openai/background/steps/create-plus-checkout.js`:Plus 模式第 6 步实现,负责在已登录 ChatGPT 页面创建 PayPal / GoPay checkout session,或在 GPC 模式下读取 accessToken 并通过 `https://gpc.qlhazycoder.top/api/gp/tasks` 创建队列任务,记录 checkout / GPC task 运行态。 - `flows/openai/background/steps/create-plus-checkout.js`:Plus 模式第 6 步实现,负责在已登录 ChatGPT 页面创建 PayPal / GoPay checkout session,或在 GPC 模式下读取 accessToken 并通过 `https://gpc.qlhazycoder.top/api/gp/tasks` 创建队列任务,记录 checkout / GPC task 运行态。
- `flows/openai/background/steps/fetch-login-code.js`:步骤 8 实现,负责按真实认证页状态分发登录后续流程:邮箱验证码页走邮箱轮询、验证码回填与回退控制;真实 `phone-verification` 页才复用 OpenAI 专属手机号验证码流程;手机号注册登录后若进入 `add-email`,会先按共享邮箱状态持久化规则生成/解析邮箱并提交绑定,在不丢失当前手机号身份的前提下再进入邮箱验证码轮询;邮箱分支对非 2925 provider 会固定当前验证码页显示邮箱作为本次 Step 8 的目标邮箱,当 provider 为 2925 时,会在轮询前先确保当前 2925 账号已自动登录;命中 `email_in_use` 时会仅清空当前邮箱并保留上一轮比较基线,再在当前 Step 8 内重开 `add-email` 获取新邮箱,`max_check_attempts` 也只重启当前步骤恢复链。 - `flows/openai/background/steps/fetch-login-code.js`:步骤 8 实现,负责按真实认证页状态分发登录后续流程:邮箱验证码页走邮箱轮询、验证码回填与回退控制;真实 `phone-verification` 页才复用 OpenAI 专属手机号验证码流程;手机号注册登录后若进入 `add-email`,会先按共享邮箱状态持久化规则生成/解析邮箱并提交绑定,在不丢失当前手机号身份的前提下再进入邮箱验证码轮询;邮箱分支对非 2925 provider 会固定当前验证码页显示邮箱作为本次 Step 8 的目标邮箱,当 provider 为 2925 时,会在轮询前先确保当前 2925 账号已自动登录;命中 `email_in_use` 时会仅清空当前邮箱并保留上一轮比较基线,再在当前 Step 8 内重开 `add-email` 获取新邮箱,`max_check_attempts` 也只重启当前步骤恢复链。
- `flows/openai/background/steps/fetch-signup-code.js`:步骤 4 实现,负责注册验证码阶段的页面准备与验证码流程入口;邮箱注册继续走邮箱验证码,手机号注册改走短信验证码;当 provider 为 2925 时,会在邮箱轮询前先确保当前 2925 账号已自动登录。 - `flows/openai/background/steps/fetch-signup-code.js`:步骤 4 实现,负责注册验证码阶段的页面准备与验证码流程入口;邮箱注册继续走邮箱验证码,手机号注册改走短信验证码;当 provider 为 2925 时,会在邮箱轮询前先确保当前 2925 账号已自动登录。
@@ -217,7 +217,7 @@
receive 模式把账号池开关一起隐藏;当前在 `邮箱生成` 区域新增 `自定义邮箱池` 选项和多行邮箱池输入框,并在 `邮箱服务 = 自定义邮箱` receive 模式把账号池开关一起隐藏;当前在 `邮箱生成` 区域新增 `自定义邮箱池` 选项和多行邮箱池输入框,并在 `邮箱服务 = 自定义邮箱`
额外显示 `自定义号池` 文本框;当邮箱服务为 iCloud 时,额外提供目标邮箱类型与转发邮箱 provider 配置,用于选择直接从 iCloud 收件箱收 额外显示 `自定义号池` 文本框;当邮箱服务为 iCloud 时,额外提供目标邮箱类型与转发邮箱 provider 配置,用于选择直接从 iCloud 收件箱收
码或从 QQ / 网易 / Gmail 转发目标邮箱收码;当邮箱服务或邮箱生成器为 Cloud Mail 时,额外显示 API 地址、管理员账号、接收邮箱和生成域名配置行;当邮箱服务为 YYDS Mail 时,额外显示 API Key 与 Base URL 配置行,并隐藏普通邮箱生成/转发邮箱相关配置;来源下拉框当前支持 `CPA / SUB2API / Codex2API`,其中 Codex2API 额外提供后台地址和管理密 码或从 QQ / 网易 / Gmail 转发目标邮箱收码;当邮箱服务或邮箱生成器为 Cloud Mail 时,额外显示 API 地址、管理员账号、接收邮箱和生成域名配置行;当邮箱服务为 YYDS Mail 时,额外显示 API Key 与 Base URL 配置行,并隐藏普通邮箱生成/转发邮箱相关配置;来源下拉框当前支持 `CPA / SUB2API / Codex2API`,其中 Codex2API 额外提供后台地址和管理密
钥配置行;设置卡片新增 `IP代理` 开关与代理配置折叠区,支持 711Proxy 账号密码模式、代理状态卡与出口检测按钮;接码卡内把注册方式、接码配置、接码订单运行态与“注册手机号”身份运行态分层展示,“注册手机号”位于订单运行状态下方,不嵌入当前分配/验证码网格;设置卡片新增 `Plus 模式` 开关,并在 `Plus 支付` 之上新增 `账号接入策略` 下拉;该下拉只显示 `OAuth / 使用会话 JSON 导入` 两种接入方式,会话导入目标由当前来源自动决定,不支持的来源会直接禁用并显示“当前来源仅支持 OAuth”;PayPal 账号下拉框继续使用公共表单弹窗添加账号;GPC Plus 配置额外提供只读 API 地址、API Key、专用手机号、OTP 渠道、本地 OTP helper 开关、helper URL 与 PIN,并提供 `购买卡密``转换 API Key` 入口;Hotmail / 2925 两个账号池当前都使用统一的头部“添加账号/取消添加”按钮和共享表单容器;设置卡片还新增“授权总超时”开关,用于控制 Step 7 后链 5 分钟总预算;注册邮箱下方新增 `执行范围` 行,目前只在 codex/openai flow 显示,用于配置允许执行的起止步骤。 钥配置行;设置卡片新增 `IP代理` 开关与代理配置折叠区,支持 711Proxy 账号密码模式、代理状态卡与出口检测按钮;接码卡内把注册方式、接码配置、接码订单运行态与“注册手机号”身份运行态分层展示,“注册手机号”位于订单运行状态下方,不嵌入当前分配/验证码网格;设置卡片新增 `Plus 模式` 开关,并在 `Plus 支付` 之上新增 `账号接入策略` 下拉;该下拉只显示 `OAuth / 使用会话 JSON 导入` 两种接入方式,会话导入目标由当前来源自动决定,不支持的来源会直接禁用并显示“当前来源仅支持 OAuth”;PayPal 账号下拉框继续使用公共表单弹窗添加账号;GPC Plus 配置额外提供只读 API 地址、API Key、专用手机号、OTP 渠道、本地 OTP helper 开关、helper URL 与 PIN,并提供 `购买卡密``转换 API Key` 入口;Hotmail / 2925 两个账号池当前都使用统一的头部“添加账号/取消添加”按钮和共享表单容器;注册邮箱下方新增 `执行范围` 行,目前只在 codex/openai flow 显示,用于配置允许执行的起止步骤。
- `sidepanel/paypal-manager.js`:侧边栏 PayPal 账号管理器,负责 Plus 模式下的账号下拉框渲染、添加账号弹窗、保存账号与切换当前账号。 - `sidepanel/paypal-manager.js`:侧边栏 PayPal 账号管理器,负责 Plus 模式下的账号下拉框渲染、添加账号弹窗、保存账号与切换当前账号。
- `sidepanel/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、共享验证码自动重发次数配置与广播接收,并装 - `sidepanel/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、共享验证码自动重发次数配置与广播接收,并装
配 Hotmail / 2925 / iCloud / LuckMail / 贡献模式 / 账号记录面板 / 贡献内容更新服务;当前贡献模式的“开始贡献”会直接复用主自动流程启 配 Hotmail / 2925 / iCloud / LuckMail / 贡献模式 / 账号记录面板 / 贡献内容更新服务;当前贡献模式的“开始贡献”会直接复用主自动流程启
@@ -231,7 +231,7 @@
会在 sidepanel 初始化时拉取并恢复到本地保存的国家/API 设置,接码开关关闭时会直接隐藏相关配置;账号记录快照同步改为默认自动模式,只 会在 sidepanel 初始化时拉取并恢复到本地保存的国家/API 设置,接码开关关闭时会直接隐藏相关配置;账号记录快照同步改为默认自动模式,只
要本地 helper 可用就会自动落盘;新来源 Codex2API 在这里仅补充来源配置接线、表单显隐和 Step 10 按钮文案,不承接协议业务逻辑; 要本地 helper 可用就会自动落盘;新来源 Codex2API 在这里仅补充来源配置接线、表单显隐和 Step 10 按钮文案,不承接协议业务逻辑;
Hotmail / 2925 的新增表单显隐统一接到 `sidepanel/account-pool-ui.js` 这一层共享 helper;IP 代理配置保存、服务配置快照、开关应用和运行态回显由这里接入消息路由,具体解析/状态渲染下沉到 `sidepanel/ip-proxy-panel.js`;侧边栏初始化与点击“自动”前会刷新一次贡献站 Hotmail / 2925 的新增表单显隐统一接到 `sidepanel/account-pool-ui.js` 这一层共享 helper;IP 代理配置保存、服务配置快照、开关应用和运行态回显由这里接入消息路由,具体解析/状态渲染下沉到 `sidepanel/ip-proxy-panel.js`;侧边栏初始化与点击“自动”前会刷新一次贡献站
公开内容摘要,并按本地关闭版本决定是否展示轻提示,同时在首次初始化后按现有规则决定是否弹出新手引导提示;当前会保存、回显并热更新 `oauthFlowTimeoutEnabled` 设置;日志渲染只读取结构化 `entry.step` 生成步骤标签,不再正则解析日志正文;注册手机号输入框只同步运行态身份,不写入持久配置。 公开内容摘要,并按本地关闭版本决定是否展示轻提示,同时在首次初始化后按现有规则决定是否弹出新手引导提示;日志渲染只读取结构化 `entry.step` 生成步骤标签,不再正则解析日志正文;注册手机号输入框只同步运行态身份,不写入持久配置。
- `sidepanel/update-service.js`:侧边栏更新检查服务,负责 GitHub Releases 查询、`Ultra` / 历史 `Pro` / legacy `v` 版本族排序、缓存读取与版本展示。 - `sidepanel/update-service.js`:侧边栏更新检查服务,负责 GitHub Releases 查询、`Ultra` / 历史 `Pro` / legacy `v` 版本族排序、缓存读取与版本展示。
- 补充:`sidepanel/sidepanel.html``sidepanel/sidepanel.js` 当前已改为 flow-aware 侧边栏主界面;顶部提供 `flow/target` 双层选择,`openai` flow 继续把 `panelMode` 归一为 integration target`kiro` flow 则使用独立 `targetId``kiro.rs URL / API Key` 专属字段,并只显示 Kiro 运行状态与共享邮箱/IP 代理配置。 - 补充:`sidepanel/sidepanel.html``sidepanel/sidepanel.js` 当前已改为 flow-aware 侧边栏主界面;顶部提供 `flow/target` 双层选择,`openai` flow 继续把 `panelMode` 归一为 integration target`kiro` flow 则使用独立 `targetId``kiro.rs URL / API Key` 专属字段,并只显示 Kiro 运行状态与共享邮箱/IP 代理配置。