重构 OAuth 总超时为 CPA 来源专属策略
This commit is contained in:
+34
-5
@@ -1350,7 +1350,6 @@ const PERSISTED_SETTING_DEFAULTS = {
|
||||
gopayHelperApiKeyStatus: '',
|
||||
autoRunSkipFailures: false,
|
||||
autoRunFallbackThreadIntervalMinutes: 0,
|
||||
oauthFlowTimeoutEnabled: true,
|
||||
operationDelayEnabled: true,
|
||||
autoStepDelaySeconds: null,
|
||||
step6CookieCleanupEnabled: false,
|
||||
@@ -3329,7 +3328,6 @@ function normalizePersistentSettingValue(key, value) {
|
||||
case 'gopayHelperRemainingUses':
|
||||
return Math.max(0, Number(value) || 0);
|
||||
case 'autoRunSkipFailures':
|
||||
case 'oauthFlowTimeoutEnabled':
|
||||
case 'gopayHelperLocalSmsHelperEnabled':
|
||||
case 'gopayHelperAutoModeEnabled':
|
||||
return Boolean(value);
|
||||
@@ -14441,15 +14439,46 @@ function normalizeOAuthFlowSourceUrl(value) {
|
||||
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 = {}) {
|
||||
const step = Number(options.step) || 7;
|
||||
const state = options.state || await getState();
|
||||
if (state?.oauthFlowTimeoutEnabled === false) {
|
||||
if (!shouldUseOAuthTimeoutBudget(state)) {
|
||||
await setState({
|
||||
oauthFlowDeadlineAt: null,
|
||||
oauthFlowDeadlineSourceUrl: null,
|
||||
});
|
||||
await addLog(`步骤 ${step}:已拿到新的 OAuth 登录地址,授权后链总超时已关闭,仅保留各步骤本地等待超时。`, 'info');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -14466,7 +14495,7 @@ async function getOAuthFlowRemainingMs(options = {}) {
|
||||
const step = Number(options.step) || 7;
|
||||
const actionLabel = String(options.actionLabel || '后续授权流程').trim() || '后续授权流程';
|
||||
const state = options.state || await getState();
|
||||
if (state?.oauthFlowTimeoutEnabled === false) {
|
||||
if (!shouldUseOAuthTimeoutBudget(state)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1423,16 +1423,10 @@
|
||||
|| (nextPlusModeEnabled && plusPaymentChanged)
|
||||
|| (nextPlusModeEnabled && plusAccountAccessStrategyChanged)
|
||||
|| phoneSignupReloginAfterBindEmailChanged;
|
||||
const oauthFlowTimeoutDisabled = Object.prototype.hasOwnProperty.call(updates, 'oauthFlowTimeoutEnabled')
|
||||
&& updates.oauthFlowTimeoutEnabled === false;
|
||||
const canonicalSettingsUpdates = await setPersistentSettings(updates);
|
||||
const stateUpdates = {
|
||||
...canonicalSettingsUpdates,
|
||||
...sessionUpdates,
|
||||
...(oauthFlowTimeoutDisabled ? {
|
||||
oauthFlowDeadlineAt: null,
|
||||
oauthFlowDeadlineSourceUrl: null,
|
||||
} : {}),
|
||||
};
|
||||
if (Object.prototype.hasOwnProperty.call(canonicalSettingsUpdates, 'activeFlowId')
|
||||
&& !Object.prototype.hasOwnProperty.call(stateUpdates, 'flowId')) {
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
contributionAdapterIds: [],
|
||||
supportedTargetIds: [],
|
||||
supportsLuckmail: false,
|
||||
supportsOauthTimeoutBudget: false,
|
||||
canSwitchFlow: true,
|
||||
stepDefinitionMode: 'default',
|
||||
targetSelectorLabel: '来源',
|
||||
@@ -59,6 +58,7 @@
|
||||
const DEFAULT_TARGET_CAPABILITIES = Object.freeze({
|
||||
supportsPhoneSignup: true,
|
||||
requiresPhoneSignupWarning: false,
|
||||
usesOauthTimeoutBudget: false,
|
||||
supportedPlusAccountAccessStrategies: Object.freeze([PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH]),
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
contributionAdapterIds: [],
|
||||
supportedTargetIds: [],
|
||||
supportsLuckmail: false,
|
||||
supportsOauthTimeoutBudget: false,
|
||||
canSwitchFlow: true,
|
||||
stepDefinitionMode: 'default',
|
||||
targetSelectorLabel: '\u6765\u6e90',
|
||||
|
||||
@@ -49,7 +49,6 @@ flowCapabilities.openai = {
|
||||
supportsContributionMode: true,
|
||||
supportsPlatformBinding: ['cpa', 'sub2api', 'codex2api'],
|
||||
supportsLuckmail: true,
|
||||
supportsOauthTimeoutBudget: true,
|
||||
stepDefinitionMode: 'openai-dynamic',
|
||||
};
|
||||
|
||||
@@ -61,7 +60,6 @@ flowCapabilities.siteA = {
|
||||
supportsContributionMode: false,
|
||||
supportsPlatformBinding: ['siteA-panel'],
|
||||
supportsLuckmail: false,
|
||||
supportsOauthTimeoutBudget: false,
|
||||
stepDefinitionMode: 'siteA',
|
||||
};
|
||||
```
|
||||
@@ -82,6 +80,7 @@ panelCapabilities = {
|
||||
cpa: {
|
||||
supportsPhoneSignup: true,
|
||||
requiresPhoneSignupWarning: true,
|
||||
usesOauthTimeoutBudget: true,
|
||||
},
|
||||
sub2api: {
|
||||
supportsPhoneSignup: true,
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
contributionAdapterIds: [],
|
||||
supportedTargetIds: ['webchat2api'],
|
||||
supportsLuckmail: false,
|
||||
supportsOauthTimeoutBudget: false,
|
||||
canSwitchFlow: true,
|
||||
stepDefinitionMode: 'grok',
|
||||
targetSelectorLabel: '来源',
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
"kiro-rs"
|
||||
],
|
||||
"supportsLuckmail": false,
|
||||
"supportsOauthTimeoutBudget": false,
|
||||
"canSwitchFlow": true,
|
||||
"stepDefinitionMode": "kiro",
|
||||
"targetSelectorLabel": "来源"
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
"codex2api"
|
||||
],
|
||||
"supportsLuckmail": true,
|
||||
"supportsOauthTimeoutBudget": true,
|
||||
"canSwitchFlow": true,
|
||||
"stepDefinitionMode": "openai-dynamic",
|
||||
"targetSelectorLabel": "来源"
|
||||
@@ -427,7 +426,6 @@
|
||||
"id": "openai-oauth",
|
||||
"label": "OAuth",
|
||||
"rowIds": [
|
||||
"row-oauth-flow-timeout",
|
||||
"row-oauth-display",
|
||||
"row-oauth-callback"
|
||||
]
|
||||
@@ -444,6 +442,7 @@
|
||||
"cpa": {
|
||||
"supportsPhoneSignup": true,
|
||||
"requiresPhoneSignupWarning": true,
|
||||
"usesOauthTimeoutBudget": true,
|
||||
"supportedPlusAccountAccessStrategies": [
|
||||
"oauth",
|
||||
"cpa_codex_session"
|
||||
|
||||
@@ -2231,17 +2231,6 @@ header {
|
||||
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 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -728,22 +728,6 @@
|
||||
</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">
|
||||
<span class="data-label">执行范围</span>
|
||||
<div class="data-inline setting-pair step-execution-range-setting">
|
||||
|
||||
@@ -429,7 +429,6 @@ const inputAutoSkipFailures = document.getElementById('input-auto-skip-failures'
|
||||
const inputAutoSkipFailuresThreadIntervalMinutes = document.getElementById('input-auto-skip-failures-thread-interval-minutes');
|
||||
const inputStep6CookieCleanupEnabled = document.getElementById('input-step6-cookie-cleanup-enabled');
|
||||
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 inputStepExecutionRangeEnabled = document.getElementById('input-step-execution-range-enabled');
|
||||
const inputStepExecutionRangeFrom = document.getElementById('input-step-execution-range-from');
|
||||
@@ -5119,9 +5118,6 @@ function collectSettingsPayload() {
|
||||
? buildStepExecutionRangeByFlowPayload(latestState?.stepExecutionRangeByFlow)
|
||||
: (latestState?.stepExecutionRangeByFlow || {}),
|
||||
autoStepDelaySeconds: normalizeAutoStepDelaySeconds(inputAutoStepDelaySeconds.value),
|
||||
oauthFlowTimeoutEnabled: typeof inputOAuthFlowTimeoutEnabled !== 'undefined' && inputOAuthFlowTimeoutEnabled
|
||||
? Boolean(inputOAuthFlowTimeoutEnabled.checked)
|
||||
: true,
|
||||
phoneVerificationEnabled: effectivePhoneVerificationEnabled,
|
||||
signupMethod: effectiveSignupMethod,
|
||||
phoneSignupReloginAfterBindEmailEnabled: typeof inputPhoneSignupReloginAfterBindEmail !== 'undefined' && inputPhoneSignupReloginAfterBindEmail
|
||||
@@ -11374,11 +11370,6 @@ function applySettingsState(state) {
|
||||
inputStep6CookieCleanupEnabled.checked = Boolean(state?.step6CookieCleanupEnabled);
|
||||
}
|
||||
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(state?.autoStepDelaySeconds);
|
||||
if (typeof inputOAuthFlowTimeoutEnabled !== 'undefined' && inputOAuthFlowTimeoutEnabled) {
|
||||
inputOAuthFlowTimeoutEnabled.checked = state?.oauthFlowTimeoutEnabled !== undefined
|
||||
? Boolean(state.oauthFlowTimeoutEnabled)
|
||||
: true;
|
||||
}
|
||||
if (inputVerificationResendCount) {
|
||||
const restoredVerificationResendCount = state?.verificationResendCount !== undefined
|
||||
? state.verificationResendCount
|
||||
@@ -17849,9 +17840,6 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||||
if (message.payload.autoStepDelaySeconds !== undefined) {
|
||||
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(message.payload.autoStepDelaySeconds);
|
||||
}
|
||||
if (message.payload.oauthFlowTimeoutEnabled !== undefined && typeof inputOAuthFlowTimeoutEnabled !== 'undefined' && inputOAuthFlowTimeoutEnabled) {
|
||||
inputOAuthFlowTimeoutEnabled.checked = Boolean(message.payload.oauthFlowTimeoutEnabled);
|
||||
}
|
||||
if (
|
||||
(
|
||||
message.payload.verificationResendCount !== undefined
|
||||
|
||||
@@ -48,6 +48,33 @@ function extractFunction(name) {
|
||||
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', () => {
|
||||
const authChainStart = source.indexOf('const AUTH_CHAIN_NODE_IDS = new Set([');
|
||||
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 LOG_PREFIX = '[test]';
|
||||
const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000;
|
||||
${OAUTH_TIMEOUT_BUDGET_TEST_REGISTRY}
|
||||
${extractFunction('resolveOAuthTimeoutBudgetScope')}
|
||||
${extractFunction('shouldUseOAuthTimeoutBudget')}
|
||||
${extractFunction('normalizeOAuthFlowDeadlineAt')}
|
||||
${extractFunction('normalizeOAuthFlowSourceUrl')}
|
||||
${extractFunction('getOAuthFlowRemainingMs')}
|
||||
@@ -444,6 +474,8 @@ return {
|
||||
step: 8,
|
||||
actionLabel: '登录验证码流程',
|
||||
state: {
|
||||
activeFlowId: 'openai',
|
||||
targetId: 'cpa',
|
||||
oauthUrl: 'https://oauth.example/current',
|
||||
oauthFlowDeadlineAt: Date.now() + 1200,
|
||||
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 LOG_PREFIX = '[test]';
|
||||
const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000;
|
||||
${OAUTH_TIMEOUT_BUDGET_TEST_REGISTRY}
|
||||
${extractFunction('buildOAuthFlowTimeoutError')}
|
||||
${extractFunction('resolveOAuthTimeoutBudgetScope')}
|
||||
${extractFunction('shouldUseOAuthTimeoutBudget')}
|
||||
${extractFunction('normalizeOAuthFlowDeadlineAt')}
|
||||
${extractFunction('normalizeOAuthFlowSourceUrl')}
|
||||
${extractFunction('getOAuthFlowRemainingMs')}
|
||||
@@ -471,6 +506,8 @@ return {
|
||||
step: 8,
|
||||
actionLabel: '登录验证码流程',
|
||||
state: {
|
||||
activeFlowId: 'openai',
|
||||
targetId: 'cpa',
|
||||
oauthUrl: 'https://oauth.example/current',
|
||||
oauthFlowDeadlineAt: Date.now() + 1200,
|
||||
oauthFlowDeadlineSourceUrl: 'https://oauth.example/current',
|
||||
@@ -481,11 +518,14 @@ return {
|
||||
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 LOG_PREFIX = '[test]';
|
||||
const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000;
|
||||
${OAUTH_TIMEOUT_BUDGET_TEST_REGISTRY}
|
||||
${extractFunction('buildOAuthFlowTimeoutError')}
|
||||
${extractFunction('resolveOAuthTimeoutBudgetScope')}
|
||||
${extractFunction('shouldUseOAuthTimeoutBudget')}
|
||||
${extractFunction('normalizeOAuthFlowDeadlineAt')}
|
||||
${extractFunction('normalizeOAuthFlowSourceUrl')}
|
||||
${extractFunction('getOAuthFlowRemainingMs')}
|
||||
@@ -499,7 +539,8 @@ return {
|
||||
step: 9,
|
||||
actionLabel: 'OAuth localhost 回调',
|
||||
state: {
|
||||
oauthFlowTimeoutEnabled: false,
|
||||
activeFlowId: 'openai',
|
||||
targetId: 'sub2api',
|
||||
oauthUrl: 'https://oauth.example/current',
|
||||
oauthFlowDeadlineAt: Date.now() - 1000,
|
||||
oauthFlowDeadlineSourceUrl: 'https://oauth.example/current',
|
||||
@@ -509,16 +550,18 @@ return {
|
||||
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 = {
|
||||
stateUpdates: [],
|
||||
logs: [],
|
||||
};
|
||||
const api = new Function('events', `
|
||||
const api = new Function('events', `
|
||||
const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000;
|
||||
${OAUTH_TIMEOUT_BUDGET_TEST_REGISTRY}
|
||||
async function getState() {
|
||||
return {
|
||||
oauthFlowTimeoutEnabled: false,
|
||||
activeFlowId: 'openai',
|
||||
targetId: 'sub2api',
|
||||
oauthFlowDeadlineAt: Date.now() - 1000,
|
||||
oauthFlowDeadlineSourceUrl: 'https://oauth.example/old',
|
||||
};
|
||||
@@ -529,6 +572,8 @@ async function setState(update) {
|
||||
async function addLog(message, level) {
|
||||
events.logs.push({ message, level });
|
||||
}
|
||||
${extractFunction('resolveOAuthTimeoutBudgetScope')}
|
||||
${extractFunction('shouldUseOAuthTimeoutBudget')}
|
||||
${extractFunction('normalizeOAuthFlowSourceUrl')}
|
||||
${extractFunction('startOAuthFlowTimeoutWindow')}
|
||||
return {
|
||||
@@ -546,7 +591,7 @@ return {
|
||||
oauthFlowDeadlineAt: 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 () => {
|
||||
|
||||
@@ -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 DEFAULT_VERIFICATION_RESEND_COUNT = 4;
|
||||
const PERSISTED_SETTING_DEFAULTS = {
|
||||
panelMode: 'cpa',
|
||||
oauthFlowTimeoutEnabled: true,
|
||||
autoStepDelaySeconds: null,
|
||||
verificationResendCount: DEFAULT_VERIFICATION_RESEND_COUNT,
|
||||
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({
|
||||
oauthFlowTimeoutEnabled: false,
|
||||
}), {
|
||||
oauthFlowTimeoutEnabled: false,
|
||||
});
|
||||
removedUiOnlyFlag: false,
|
||||
}), {});
|
||||
|
||||
const defaults = api.buildPersistentSettingsPayload({}, { fillDefaults: true });
|
||||
assert.equal(defaults.oauthFlowTimeoutEnabled, true);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(defaults, 'removedUiOnlyFlag'), false);
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
|
||||
assert.equal(enabledState.canUsePhoneSignup, true);
|
||||
assert.equal(enabledState.effectiveSignupMethod, 'phone');
|
||||
assert.equal(enabledState.shouldWarnCpaPhoneSignup, true);
|
||||
assert.equal(enabledState.targetCapabilities.usesOauthTimeoutBudget, true);
|
||||
assert.deepEqual(enabledState.effectiveSignupMethods, ['email', 'phone']);
|
||||
|
||||
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.effectiveSignupMethod, 'email');
|
||||
assert.equal(plusLockedState.shouldWarnCpaPhoneSignup, false);
|
||||
assert.equal(plusLockedState.targetCapabilities.usesOauthTimeoutBudget, false);
|
||||
assert.deepEqual(plusLockedState.effectiveSignupMethods, ['email']);
|
||||
});
|
||||
|
||||
|
||||
@@ -29,6 +29,14 @@ test('flow registry exposes canonical flow and target metadata', () => {
|
||||
flowRegistry.getFlowDefinition('openai')?.targets?.cpa?.defaultState,
|
||||
{ vpsUrl: '', vpsPassword: '', localCpaStep9Mode: 'submit' }
|
||||
);
|
||||
assert.equal(
|
||||
flowRegistry.getTargetCapabilities('openai', 'cpa')?.usesOauthTimeoutBudget,
|
||||
true
|
||||
);
|
||||
assert.equal(
|
||||
flowRegistry.getTargetCapabilities('openai', 'sub2api')?.usesOauthTimeoutBudget,
|
||||
undefined
|
||||
);
|
||||
assert.deepEqual(
|
||||
flowRegistry.getFlowDefinition('kiro')?.targets?.['kiro-rs']?.defaultState,
|
||||
{ baseUrl: '', apiKey: '' }
|
||||
|
||||
@@ -248,7 +248,6 @@ const inputTempEmailUseRandomSubdomain = { checked: true };
|
||||
const inputAutoSkipFailures = { checked: false };
|
||||
const inputAutoSkipFailuresThreadIntervalMinutes = { value: '5' };
|
||||
const inputAutoStepDelaySeconds = { value: '10' };
|
||||
const inputOAuthFlowTimeoutEnabled = { checked: true };
|
||||
const inputVerificationResendCount = { value: '6' };
|
||||
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
|
||||
const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
|
||||
|
||||
@@ -133,7 +133,6 @@ const inputTempEmailUseRandomSubdomain = { checked: false };
|
||||
const inputAutoSkipFailures = { checked: false };
|
||||
const inputAutoSkipFailuresThreadIntervalMinutes = { value: '0' };
|
||||
const inputAutoStepDelaySeconds = { value: '' };
|
||||
const inputOAuthFlowTimeoutEnabled = { checked: true };
|
||||
const inputVerificationResendCount = { value: '4' };
|
||||
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
|
||||
const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
|
||||
@@ -406,7 +405,6 @@ const inputLuckmailDomain = { value: '' };
|
||||
const inputAutoSkipFailures = { checked: false };
|
||||
const inputAutoSkipFailuresThreadIntervalMinutes = { value: '' };
|
||||
const inputAutoStepDelaySeconds = { value: '' };
|
||||
const inputOAuthFlowTimeoutEnabled = { checked: true };
|
||||
const inputVerificationResendCount = { value: '' };
|
||||
const inputPhoneVerificationEnabled = { checked: false };
|
||||
const selectPhoneSmsProvider = { value: 'hero-sms' };
|
||||
|
||||
@@ -204,7 +204,6 @@ const inputTempEmailUseRandomSubdomain = { checked: false };
|
||||
const inputAutoSkipFailures = { checked: false };
|
||||
const inputAutoSkipFailuresThreadIntervalMinutes = { value: '0' };
|
||||
const inputAutoStepDelaySeconds = { value: '' };
|
||||
const inputOAuthFlowTimeoutEnabled = { checked: true };
|
||||
const inputVerificationResendCount = { value: '4' };
|
||||
const DEFAULT_VERIFICATION_RESEND_COUNT = 4;
|
||||
const PHONE_SMS_PROVIDER_HERO_SMS = 'hero-sms';
|
||||
|
||||
@@ -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 sharedAutoRunIndex = html.indexOf('id="row-shared-auto-run"');
|
||||
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 oauthDisplayIndex = html.indexOf('id="row-oauth-display"');
|
||||
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(sharedAutoRunIndex, -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(oauthDisplayIndex, -1);
|
||||
assert.notEqual(oauthCallbackIndex, -1);
|
||||
assert.notEqual(settingsActionsIndex, -1);
|
||||
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 < oauthTimeoutIndex, 'thread interval should stay outside openai oauth controls');
|
||||
assert.ok(stepRangeIndex > oauthTimeoutIndex, 'step execution range should render below oauth timeout');
|
||||
assert.ok(stepRangeIndex > threadIntervalIndex, 'step execution range should render below shared thread interval');
|
||||
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(settingsActionsIndex > oauthCallbackIndex, 'save settings action should live outside the callback row');
|
||||
|
||||
+1
-1
@@ -281,7 +281,7 @@
|
||||
- Kiro 目标配置:`flows.kiro.targetId` 与 `flows.kiro.targets["kiro-rs"] = { baseUrl, apiKey }`
|
||||
- 自动运行默认配置
|
||||
- 操作间延迟兼容字段 `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。
|
||||
- 账号运行历史 `accountRunHistory`(以统一账号标识为主键;邮箱账号保留邮箱主键,手机号注册保留手机号主键;同一轮中后续绑定的邮箱或手机号会写入同一条记录的副身份字段,保存最近一次状态:成功/失败/停止)
|
||||
- 账号运行历史本地同步 helper 地址
|
||||
|
||||
+5
-5
@@ -21,7 +21,7 @@
|
||||
- `.gitignore`:定义仓库忽略规则,当前忽略 `docs/md/`、`.github/`、`_metadata/`、`.vscode/` 等目录。
|
||||
- `LICENSE`:项目许可证文件。
|
||||
- `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 地址、域名、鉴权头、邮件列表响应与邮件正文归一化。
|
||||
- `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 与余额响应解析。
|
||||
@@ -68,7 +68,7 @@
|
||||
- `background/mail-2925-session.js`:2925 会话模块,负责 2925 账号池持久化、当前账号切换、cookie 清理登出、自动登录、命中“子邮箱已达上限邮箱”后的 24 小时禁用与自动切号。
|
||||
- `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/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` 写入邮箱时按需保留手机号身份”的统一工具。
|
||||
- `core/flow-kernel/runtime-state.js`:运行态视图与 patch 构造模块,负责把会话字段归一到 `runtimeState / sharedState / serviceState / flowState` 结构,并维护 flow-aware 的 session patch、节点状态默认值与兼容视图输出。
|
||||
- `background/navigation-utils.js`:导航与 URL 判断工具层,负责 callback、入口页、CPA / SUB2API / Codex2API 地址归一化、来源标签页家族判断与步骤跳转相关判断。
|
||||
@@ -84,7 +84,7 @@
|
||||
## `background/steps/`
|
||||
|
||||
- `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/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 账号已自动登录。
|
||||
@@ -217,7 +217,7 @@
|
||||
receive 模式把账号池开关一起隐藏;当前在 `邮箱生成` 区域新增 `自定义邮箱池` 选项和多行邮箱池输入框,并在 `邮箱服务 = 自定义邮箱` 时
|
||||
额外显示 `自定义号池` 文本框;当邮箱服务为 iCloud 时,额外提供目标邮箱类型与转发邮箱 provider 配置,用于选择直接从 iCloud 收件箱收
|
||||
码或从 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/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、共享验证码自动重发次数配置与广播接收,并装
|
||||
配 Hotmail / 2925 / iCloud / LuckMail / 贡献模式 / 账号记录面板 / 贡献内容更新服务;当前贡献模式的“开始贡献”会直接复用主自动流程启
|
||||
@@ -231,7 +231,7 @@
|
||||
会在 sidepanel 初始化时拉取并恢复到本地保存的国家/API 设置,接码开关关闭时会直接隐藏相关配置;账号记录快照同步改为默认自动模式,只
|
||||
要本地 helper 可用就会自动落盘;新来源 Codex2API 在这里仅补充来源配置接线、表单显隐和 Step 10 按钮文案,不承接协议业务逻辑;
|
||||
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/sidepanel.html` 与 `sidepanel/sidepanel.js` 当前已改为 flow-aware 侧边栏主界面;顶部提供 `flow/target` 双层选择,`openai` flow 继续把 `panelMode` 归一为 integration target,`kiro` flow 则使用独立 `targetId` 与 `kiro.rs URL / API Key` 专属字段,并只显示 Kiro 运行状态与共享邮箱/IP 代理配置。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user