Add OAuth flow timeout toggle
This commit is contained in:
@@ -490,6 +490,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
currentPayPalAccountId: '',
|
currentPayPalAccountId: '',
|
||||||
autoRunSkipFailures: false,
|
autoRunSkipFailures: false,
|
||||||
autoRunFallbackThreadIntervalMinutes: 0,
|
autoRunFallbackThreadIntervalMinutes: 0,
|
||||||
|
oauthFlowTimeoutEnabled: true,
|
||||||
autoRunDelayEnabled: false,
|
autoRunDelayEnabled: false,
|
||||||
autoRunDelayMinutes: 30,
|
autoRunDelayMinutes: 30,
|
||||||
autoStepDelaySeconds: null,
|
autoStepDelaySeconds: null,
|
||||||
@@ -1466,6 +1467,7 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
case 'plusPaymentMethod':
|
case 'plusPaymentMethod':
|
||||||
return String(value || '').trim().toLowerCase() === 'gopay' ? 'gopay' : 'paypal';
|
return String(value || '').trim().toLowerCase() === 'gopay' ? 'gopay' : 'paypal';
|
||||||
case 'autoRunSkipFailures':
|
case 'autoRunSkipFailures':
|
||||||
|
case 'oauthFlowTimeoutEnabled':
|
||||||
case 'autoRunDelayEnabled':
|
case 'autoRunDelayEnabled':
|
||||||
case 'phoneVerificationEnabled':
|
case 'phoneVerificationEnabled':
|
||||||
case 'plusModeEnabled':
|
case 'plusModeEnabled':
|
||||||
@@ -8915,6 +8917,7 @@ const phoneVerificationHelpers = self.MultiPageBackgroundPhoneVerification?.crea
|
|||||||
DEFAULT_PHONE_CODE_POLL_INTERVAL_SECONDS,
|
DEFAULT_PHONE_CODE_POLL_INTERVAL_SECONDS,
|
||||||
DEFAULT_PHONE_CODE_POLL_ROUNDS,
|
DEFAULT_PHONE_CODE_POLL_ROUNDS,
|
||||||
ensureStep8SignupPageReady,
|
ensureStep8SignupPageReady,
|
||||||
|
getOAuthFlowRemainingMs,
|
||||||
getOAuthFlowStepTimeoutMs,
|
getOAuthFlowStepTimeoutMs,
|
||||||
getState,
|
getState,
|
||||||
HERO_SMS_COUNTRY_ID,
|
HERO_SMS_COUNTRY_ID,
|
||||||
@@ -9670,6 +9673,16 @@ function normalizeOAuthFlowSourceUrl(value) {
|
|||||||
|
|
||||||
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();
|
||||||
|
if (state?.oauthFlowTimeoutEnabled === false) {
|
||||||
|
await setState({
|
||||||
|
oauthFlowDeadlineAt: null,
|
||||||
|
oauthFlowDeadlineSourceUrl: null,
|
||||||
|
});
|
||||||
|
await addLog(`步骤 ${step}:已拿到新的 OAuth 登录地址,授权后链总超时已关闭,仅保留各步骤本地等待超时。`, 'info');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const deadlineAt = Date.now() + OAUTH_FLOW_TIMEOUT_MS;
|
const deadlineAt = Date.now() + OAUTH_FLOW_TIMEOUT_MS;
|
||||||
await setState({
|
await setState({
|
||||||
oauthFlowDeadlineAt: deadlineAt,
|
oauthFlowDeadlineAt: deadlineAt,
|
||||||
@@ -9683,6 +9696,10 @@ 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) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const deadlineAt = normalizeOAuthFlowDeadlineAt(state?.oauthFlowDeadlineAt);
|
const deadlineAt = normalizeOAuthFlowDeadlineAt(state?.oauthFlowDeadlineAt);
|
||||||
const deadlineSourceUrl = normalizeOAuthFlowSourceUrl(state?.oauthFlowDeadlineSourceUrl);
|
const deadlineSourceUrl = normalizeOAuthFlowSourceUrl(state?.oauthFlowDeadlineSourceUrl);
|
||||||
const currentOauthUrl = normalizeOAuthFlowSourceUrl(options.oauthUrl !== undefined ? options.oauthUrl : state?.oauthUrl);
|
const currentOauthUrl = normalizeOAuthFlowSourceUrl(options.oauthUrl !== undefined ? options.oauthUrl : state?.oauthUrl);
|
||||||
|
|||||||
@@ -705,10 +705,16 @@
|
|||||||
? Boolean(updates.plusModeEnabled)
|
? Boolean(updates.plusModeEnabled)
|
||||||
: Boolean(currentState?.plusModeEnabled);
|
: Boolean(currentState?.plusModeEnabled);
|
||||||
const stepModeChanged = modeChanged || (nextPlusModeEnabled && plusPaymentChanged);
|
const stepModeChanged = modeChanged || (nextPlusModeEnabled && plusPaymentChanged);
|
||||||
|
const oauthFlowTimeoutDisabled = Object.prototype.hasOwnProperty.call(updates, 'oauthFlowTimeoutEnabled')
|
||||||
|
&& updates.oauthFlowTimeoutEnabled === false;
|
||||||
await setPersistentSettings(updates);
|
await setPersistentSettings(updates);
|
||||||
const stateUpdates = {
|
const stateUpdates = {
|
||||||
...updates,
|
...updates,
|
||||||
...sessionUpdates,
|
...sessionUpdates,
|
||||||
|
...(oauthFlowTimeoutDisabled ? {
|
||||||
|
oauthFlowDeadlineAt: null,
|
||||||
|
oauthFlowDeadlineSourceUrl: null,
|
||||||
|
} : {}),
|
||||||
};
|
};
|
||||||
if (stepModeChanged && typeof getStepIdsForState === 'function') {
|
if (stepModeChanged && typeof getStepIdsForState === 'function') {
|
||||||
const nextStateForSteps = { ...currentState, ...stateUpdates };
|
const nextStateForSteps = { ...currentState, ...stateUpdates };
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
clickWithDebugger,
|
clickWithDebugger,
|
||||||
completeStepFromBackground,
|
completeStepFromBackground,
|
||||||
ensureStep8SignupPageReady,
|
ensureStep8SignupPageReady,
|
||||||
|
getOAuthFlowRemainingMs,
|
||||||
getOAuthFlowStepTimeoutMs,
|
getOAuthFlowStepTimeoutMs,
|
||||||
getStep8CallbackUrlFromNavigation,
|
getStep8CallbackUrlFromNavigation,
|
||||||
getStep8CallbackUrlFromTabUpdate,
|
getStep8CallbackUrlFromTabUpdate,
|
||||||
@@ -34,6 +35,9 @@
|
|||||||
setStep8TabUpdatedListener,
|
setStep8TabUpdatedListener,
|
||||||
} = deps;
|
} = deps;
|
||||||
|
|
||||||
|
const LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS = 240000;
|
||||||
|
const CALLBACK_TIMEOUT_CHECK_INTERVAL_MS = 1000;
|
||||||
|
|
||||||
function getVisibleStep(state, fallback = 9) {
|
function getVisibleStep(state, fallback = 9) {
|
||||||
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
|
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
|
||||||
return visibleStep > 0 ? visibleStep : fallback;
|
return visibleStep > 0 ? visibleStep : fallback;
|
||||||
@@ -54,17 +58,17 @@
|
|||||||
|
|
||||||
await addLog(`步骤 ${visibleStep}:正在监听 localhost 回调地址...`);
|
await addLog(`步骤 ${visibleStep}:正在监听 localhost 回调地址...`);
|
||||||
|
|
||||||
let callbackTimeoutMs = 240000;
|
let callbackTimeoutMs = LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS;
|
||||||
let timeoutRecoveryAttempted = false;
|
let timeoutRecoveryAttempted = false;
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
callbackTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
|
callbackTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
|
||||||
? await getOAuthFlowStepTimeoutMs(240000, {
|
? await getOAuthFlowStepTimeoutMs(LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS, {
|
||||||
step: visibleStep,
|
step: visibleStep,
|
||||||
actionLabel: 'OAuth localhost 回调',
|
actionLabel: 'OAuth localhost 回调',
|
||||||
oauthUrl: activeState?.oauthUrl || '',
|
oauthUrl: activeState?.oauthUrl || '',
|
||||||
})
|
})
|
||||||
: 240000;
|
: LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS;
|
||||||
break;
|
break;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (timeoutRecoveryAttempted || typeof recoverOAuthLocalhostTimeout !== 'function') {
|
if (timeoutRecoveryAttempted || typeof recoverOAuthLocalhostTimeout !== 'function') {
|
||||||
@@ -86,8 +90,14 @@
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
let signupTabId = null;
|
let signupTabId = null;
|
||||||
|
const callbackWaitStartedAt = Date.now();
|
||||||
|
let timeoutCheckTimer = null;
|
||||||
|
|
||||||
const cleanupListener = () => {
|
const cleanupListener = () => {
|
||||||
|
if (timeoutCheckTimer) {
|
||||||
|
clearTimeout(timeoutCheckTimer);
|
||||||
|
timeoutCheckTimer = null;
|
||||||
|
}
|
||||||
cleanupStep8NavigationListeners();
|
cleanupStep8NavigationListeners();
|
||||||
setStep8PendingReject(null);
|
setStep8PendingReject(null);
|
||||||
};
|
};
|
||||||
@@ -95,7 +105,6 @@
|
|||||||
const rejectStep9 = (error) => {
|
const rejectStep9 = (error) => {
|
||||||
if (resolved) return;
|
if (resolved) return;
|
||||||
resolved = true;
|
resolved = true;
|
||||||
clearTimeout(timeout);
|
|
||||||
cleanupListener();
|
cleanupListener();
|
||||||
reject(error);
|
reject(error);
|
||||||
};
|
};
|
||||||
@@ -105,7 +114,6 @@
|
|||||||
|
|
||||||
resolved = true;
|
resolved = true;
|
||||||
cleanupListener();
|
cleanupListener();
|
||||||
clearTimeout(timeout);
|
|
||||||
|
|
||||||
addLog(`步骤 ${visibleStep}:已捕获 localhost 地址:${callbackUrl}`, 'ok').then(() => {
|
addLog(`步骤 ${visibleStep}:已捕获 localhost 地址:${callbackUrl}`, 'ok').then(() => {
|
||||||
return completeStepFromBackground(visibleStep, { localhostUrl: callbackUrl });
|
return completeStepFromBackground(visibleStep, { localhostUrl: callbackUrl });
|
||||||
@@ -116,9 +124,39 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
const checkCallbackTimeout = async () => {
|
||||||
|
if (resolved) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const elapsedMs = Date.now() - callbackWaitStartedAt;
|
||||||
|
if (elapsedMs >= LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS) {
|
||||||
|
rejectStep9(new Error(`${Math.round(LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS / 1000)} 秒内未捕获到 localhost 回调跳转,步骤 ${visibleStep} 的点击可能被拦截了。`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof getOAuthFlowRemainingMs === 'function') {
|
||||||
|
try {
|
||||||
|
await getOAuthFlowRemainingMs({
|
||||||
|
step: visibleStep,
|
||||||
|
actionLabel: 'OAuth localhost 回调',
|
||||||
|
oauthUrl: activeState?.oauthUrl || '',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
rejectStep9(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (elapsedMs >= callbackTimeoutMs) {
|
||||||
rejectStep9(new Error(`${Math.round(callbackTimeoutMs / 1000)} 秒内未捕获到 localhost 回调跳转,步骤 ${visibleStep} 的点击可能被拦截了。`));
|
rejectStep9(new Error(`${Math.round(callbackTimeoutMs / 1000)} 秒内未捕获到 localhost 回调跳转,步骤 ${visibleStep} 的点击可能被拦截了。`));
|
||||||
}, callbackTimeoutMs);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutCheckTimer = setTimeout(checkCallbackTimeout, CALLBACK_TIMEOUT_CHECK_INTERVAL_MS);
|
||||||
|
};
|
||||||
|
|
||||||
|
timeoutCheckTimer = setTimeout(
|
||||||
|
checkCallbackTimeout,
|
||||||
|
Math.min(CALLBACK_TIMEOUT_CHECK_INTERVAL_MS, Math.max(1, callbackTimeoutMs))
|
||||||
|
);
|
||||||
|
|
||||||
setStep8PendingReject((error) => {
|
setStep8PendingReject((error) => {
|
||||||
rejectStep9(error);
|
rejectStep9(error);
|
||||||
|
|||||||
@@ -394,6 +394,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="data-row" id="row-oauth-flow-timeout">
|
||||||
|
<span class="data-label">授权总超时</span>
|
||||||
|
<div class="data-inline">
|
||||||
|
<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">关闭后只取消 Step 7 后链总预算</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="data-row module-divider-start" id="row-oauth-display">
|
<div class="data-row module-divider-start" id="row-oauth-display">
|
||||||
<span class="data-label">OAuth</span>
|
<span class="data-label">OAuth</span>
|
||||||
<span id="display-oauth-url" class="data-value mono truncate">等待中...</span>
|
<span id="display-oauth-url" class="data-value mono truncate">等待中...</span>
|
||||||
|
|||||||
@@ -288,6 +288,7 @@ const inputAutoSkipFailuresThreadIntervalMinutes = document.getElementById('inpu
|
|||||||
const inputAutoDelayEnabled = document.getElementById('input-auto-delay-enabled');
|
const inputAutoDelayEnabled = document.getElementById('input-auto-delay-enabled');
|
||||||
const inputAutoDelayMinutes = document.getElementById('input-auto-delay-minutes');
|
const inputAutoDelayMinutes = document.getElementById('input-auto-delay-minutes');
|
||||||
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 inputVerificationResendCount = document.getElementById('input-verification-resend-count');
|
const inputVerificationResendCount = document.getElementById('input-verification-resend-count');
|
||||||
const rowPhoneVerificationEnabled = document.getElementById('row-phone-verification-enabled');
|
const rowPhoneVerificationEnabled = document.getElementById('row-phone-verification-enabled');
|
||||||
const btnTogglePhoneVerificationSection = document.getElementById('btn-toggle-phone-verification-section');
|
const btnTogglePhoneVerificationSection = document.getElementById('btn-toggle-phone-verification-section');
|
||||||
@@ -2650,6 +2651,9 @@ function collectSettingsPayload() {
|
|||||||
autoRunDelayEnabled: inputAutoDelayEnabled.checked,
|
autoRunDelayEnabled: inputAutoDelayEnabled.checked,
|
||||||
autoRunDelayMinutes: normalizeAutoDelayMinutes(inputAutoDelayMinutes.value),
|
autoRunDelayMinutes: normalizeAutoDelayMinutes(inputAutoDelayMinutes.value),
|
||||||
autoStepDelaySeconds: normalizeAutoStepDelaySeconds(inputAutoStepDelaySeconds.value),
|
autoStepDelaySeconds: normalizeAutoStepDelaySeconds(inputAutoStepDelaySeconds.value),
|
||||||
|
oauthFlowTimeoutEnabled: inputOAuthFlowTimeoutEnabled
|
||||||
|
? Boolean(inputOAuthFlowTimeoutEnabled.checked)
|
||||||
|
: true,
|
||||||
phoneVerificationEnabled: Boolean(inputPhoneVerificationEnabled?.checked),
|
phoneVerificationEnabled: Boolean(inputPhoneVerificationEnabled?.checked),
|
||||||
verificationResendCount: normalizeVerificationResendCount(
|
verificationResendCount: normalizeVerificationResendCount(
|
||||||
inputVerificationResendCount?.value,
|
inputVerificationResendCount?.value,
|
||||||
@@ -4202,6 +4206,11 @@ function applySettingsState(state) {
|
|||||||
inputAutoDelayEnabled.checked = Boolean(state?.autoRunDelayEnabled);
|
inputAutoDelayEnabled.checked = Boolean(state?.autoRunDelayEnabled);
|
||||||
inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(state?.autoRunDelayMinutes));
|
inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(state?.autoRunDelayMinutes));
|
||||||
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(state?.autoStepDelaySeconds);
|
inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(state?.autoStepDelaySeconds);
|
||||||
|
if (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
|
||||||
@@ -7765,6 +7774,11 @@ inputAutoDelayMinutes.addEventListener('blur', () => {
|
|||||||
saveSettings({ silent: true }).catch(() => { });
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
inputOAuthFlowTimeoutEnabled?.addEventListener('change', () => {
|
||||||
|
markSettingsDirty(true);
|
||||||
|
saveSettings({ silent: true }).catch(() => { });
|
||||||
|
});
|
||||||
|
|
||||||
inputPhoneVerificationEnabled?.addEventListener('change', () => {
|
inputPhoneVerificationEnabled?.addEventListener('change', () => {
|
||||||
if (inputPhoneVerificationEnabled.checked) {
|
if (inputPhoneVerificationEnabled.checked) {
|
||||||
setPhoneVerificationSectionExpanded(true);
|
setPhoneVerificationSectionExpanded(true);
|
||||||
@@ -8357,6 +8371,9 @@ 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 && inputOAuthFlowTimeoutEnabled) {
|
||||||
|
inputOAuthFlowTimeoutEnabled.checked = Boolean(message.payload.oauthFlowTimeoutEnabled);
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
(
|
(
|
||||||
message.payload.verificationResendCount !== undefined
|
message.payload.verificationResendCount !== undefined
|
||||||
|
|||||||
@@ -296,3 +296,99 @@ return {
|
|||||||
|
|
||||||
assert.equal(timeoutMs, 15000);
|
assert.equal(timeoutMs, 15000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
${extractFunction('buildOAuthFlowTimeoutError')}
|
||||||
|
${extractFunction('normalizeOAuthFlowDeadlineAt')}
|
||||||
|
${extractFunction('normalizeOAuthFlowSourceUrl')}
|
||||||
|
${extractFunction('getOAuthFlowRemainingMs')}
|
||||||
|
${extractFunction('getOAuthFlowStepTimeoutMs')}
|
||||||
|
return {
|
||||||
|
getOAuthFlowStepTimeoutMs,
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const timeoutMs = await api.getOAuthFlowStepTimeoutMs(15000, {
|
||||||
|
step: 8,
|
||||||
|
actionLabel: '登录验证码流程',
|
||||||
|
state: {
|
||||||
|
oauthUrl: 'https://oauth.example/current',
|
||||||
|
oauthFlowDeadlineAt: Date.now() + 1200,
|
||||||
|
oauthFlowDeadlineSourceUrl: 'https://oauth.example/current',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(timeoutMs <= 1200);
|
||||||
|
assert(timeoutMs >= 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('oauth timeout budget disabled mode ignores active deadlines', async () => {
|
||||||
|
const api = new Function(`
|
||||||
|
const LOG_PREFIX = '[test]';
|
||||||
|
const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000;
|
||||||
|
${extractFunction('buildOAuthFlowTimeoutError')}
|
||||||
|
${extractFunction('normalizeOAuthFlowDeadlineAt')}
|
||||||
|
${extractFunction('normalizeOAuthFlowSourceUrl')}
|
||||||
|
${extractFunction('getOAuthFlowRemainingMs')}
|
||||||
|
${extractFunction('getOAuthFlowStepTimeoutMs')}
|
||||||
|
return {
|
||||||
|
getOAuthFlowStepTimeoutMs,
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
const timeoutMs = await api.getOAuthFlowStepTimeoutMs(15000, {
|
||||||
|
step: 9,
|
||||||
|
actionLabel: 'OAuth localhost 回调',
|
||||||
|
state: {
|
||||||
|
oauthFlowTimeoutEnabled: false,
|
||||||
|
oauthUrl: 'https://oauth.example/current',
|
||||||
|
oauthFlowDeadlineAt: Date.now() - 1000,
|
||||||
|
oauthFlowDeadlineSourceUrl: 'https://oauth.example/current',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(timeoutMs, 15000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('startOAuthFlowTimeoutWindow clears stale deadline when timeout is disabled', async () => {
|
||||||
|
const events = {
|
||||||
|
stateUpdates: [],
|
||||||
|
logs: [],
|
||||||
|
};
|
||||||
|
const api = new Function('events', `
|
||||||
|
const OAUTH_FLOW_TIMEOUT_MS = 5 * 60 * 1000;
|
||||||
|
async function getState() {
|
||||||
|
return {
|
||||||
|
oauthFlowTimeoutEnabled: false,
|
||||||
|
oauthFlowDeadlineAt: Date.now() - 1000,
|
||||||
|
oauthFlowDeadlineSourceUrl: 'https://oauth.example/old',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async function setState(update) {
|
||||||
|
events.stateUpdates.push(update);
|
||||||
|
}
|
||||||
|
async function addLog(message, level) {
|
||||||
|
events.logs.push({ message, level });
|
||||||
|
}
|
||||||
|
${extractFunction('normalizeOAuthFlowSourceUrl')}
|
||||||
|
${extractFunction('startOAuthFlowTimeoutWindow')}
|
||||||
|
return {
|
||||||
|
startOAuthFlowTimeoutWindow,
|
||||||
|
};
|
||||||
|
`)(events);
|
||||||
|
|
||||||
|
const result = await api.startOAuthFlowTimeoutWindow({
|
||||||
|
step: 7,
|
||||||
|
oauthUrl: 'https://oauth.example/current',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result, null);
|
||||||
|
assert.deepStrictEqual(events.stateUpdates, [{
|
||||||
|
oauthFlowDeadlineAt: null,
|
||||||
|
oauthFlowDeadlineSourceUrl: null,
|
||||||
|
}]);
|
||||||
|
assert.match(events.logs[0].message, /授权后链总超时已关闭/);
|
||||||
|
});
|
||||||
|
|||||||
@@ -143,3 +143,81 @@ return {
|
|||||||
domains: ['mail.example.com'],
|
domains: ['mail.example.com'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('oauth flow timeout setting is persisted and normalized as boolean', () => {
|
||||||
|
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',
|
||||||
|
mail2925Mode: 'provide',
|
||||||
|
emailGenerator: 'duck',
|
||||||
|
autoDeleteUsedIcloudAlias: false,
|
||||||
|
accountRunHistoryTextEnabled: false,
|
||||||
|
cloudflareTempEmailUseRandomSubdomain: false,
|
||||||
|
cloudflareTempEmailDomain: '',
|
||||||
|
cloudflareTempEmailDomains: [],
|
||||||
|
};
|
||||||
|
const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
|
||||||
|
function normalizePanelMode(value) { return value === 'sub2api' ? 'sub2api' : 'cpa'; }
|
||||||
|
function normalizeLocalCpaStep9Mode(value) { return value === 'bypass' ? 'bypass' : 'submit'; }
|
||||||
|
function normalizeAutoRunFallbackThreadIntervalMinutes(value) { return Number(value) || 0; }
|
||||||
|
function normalizeAutoRunDelayMinutes(value) { return Number(value) || 30; }
|
||||||
|
function normalizeAutoStepDelaySeconds(value, fallback = null) { return value == null || value === '' ? fallback : Number(value); }
|
||||||
|
function normalizeVerificationResendCount(value, fallback) { return Number.isFinite(Number(value)) ? Number(value) : fallback; }
|
||||||
|
function normalizeMailProvider(value) { return String(value || '').trim().toLowerCase() || '163'; }
|
||||||
|
function normalizeMail2925Mode(value) { return String(value || '').trim().toLowerCase() === 'receive' ? 'receive' : 'provide'; }
|
||||||
|
function normalizeEmailGenerator(value) { return String(value || '').trim().toLowerCase() || 'duck'; }
|
||||||
|
function normalizeIcloudHost(value) { return ''; }
|
||||||
|
function normalizeIcloudTargetMailboxType(value) { return String(value || '').trim() || 'icloud-inbox'; }
|
||||||
|
function normalizeIcloudForwardMailProvider(value) { return String(value || '').trim() || 'qq'; }
|
||||||
|
function normalizeIcloudFetchMode(value) { return String(value || '').trim() === 'always_new' ? 'always_new' : 'reuse_existing'; }
|
||||||
|
function normalizeAccountRunHistoryHelperBaseUrl(value) { return String(value || '').trim(); }
|
||||||
|
function normalizeHotmailServiceMode(value) { return String(value || '').trim().toLowerCase() === 'remote' ? 'remote' : 'local'; }
|
||||||
|
function normalizeHotmailRemoteBaseUrl(value) { return String(value || '').trim(); }
|
||||||
|
function normalizeHotmailLocalBaseUrl(value) { return String(value || '').trim(); }
|
||||||
|
function normalizeLuckmailBaseUrl(value) { return String(value || '').trim(); }
|
||||||
|
function normalizeLuckmailEmailType(value) { return String(value || '').trim(); }
|
||||||
|
function normalizeCloudflareDomain(value) { return String(value || '').trim().toLowerCase(); }
|
||||||
|
function normalizeCloudflareDomains(value) { return Array.isArray(value) ? value.map((item) => String(item || '').trim().toLowerCase()).filter(Boolean) : []; }
|
||||||
|
function normalizeCustomEmailPool(value) { return Array.isArray(value) ? value : []; }
|
||||||
|
function normalizeCloudflareTempEmailBaseUrl(value) { return String(value || '').trim(); }
|
||||||
|
function normalizeCloudflareTempEmailAddress(value = '') { return String(value || '').trim().toLowerCase(); }
|
||||||
|
function normalizeCloudflareTempEmailReceiveMailbox(value = '') { return String(value || '').trim().toLowerCase(); }
|
||||||
|
function normalizeCloudflareTempEmailDomain(value) { return String(value || '').trim().toLowerCase(); }
|
||||||
|
function normalizeCloudflareTempEmailDomains(value) { return Array.isArray(value) ? value.map((item) => String(item || '').trim().toLowerCase()).filter(Boolean) : []; }
|
||||||
|
function normalizeHotmailAccounts(value) { return Array.isArray(value) ? value : []; }
|
||||||
|
function normalizeMail2925Accounts(value) { return Array.isArray(value) ? value : []; }
|
||||||
|
function normalizePayPalAccounts(value) { return Array.isArray(value) ? value : []; }
|
||||||
|
function normalizeHeroSmsAcquirePriority(value) { return String(value || '').trim() === 'price' ? 'price' : 'country'; }
|
||||||
|
function normalizeHeroSmsMaxPrice(value) { return String(value || '').trim(); }
|
||||||
|
function normalizeHeroSmsCountryFallback(value) { return Array.isArray(value) ? value : []; }
|
||||||
|
function normalizePhoneVerificationReplacementLimit(value) { return Number(value) || 3; }
|
||||||
|
function normalizePhoneCodeWaitSeconds(value) { return Number(value) || 60; }
|
||||||
|
function normalizePhoneCodeTimeoutWindows(value) { return Number(value) || 2; }
|
||||||
|
function normalizePhoneCodePollIntervalSeconds(value) { return Number(value) || 5; }
|
||||||
|
function normalizePhoneCodePollMaxRounds(value) { return Number(value) || 4; }
|
||||||
|
function resolveLegacyAutoStepDelaySeconds() { return undefined; }
|
||||||
|
${extractFunction('normalizePersistentSettingValue')}
|
||||||
|
${extractFunction('buildPersistentSettingsPayload')}
|
||||||
|
return {
|
||||||
|
buildPersistentSettingsPayload,
|
||||||
|
normalizePersistentSettingValue,
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('oauthFlowTimeoutEnabled', 0), false);
|
||||||
|
assert.equal(api.normalizePersistentSettingValue('oauthFlowTimeoutEnabled', 1), true);
|
||||||
|
|
||||||
|
assert.deepEqual(api.buildPersistentSettingsPayload({
|
||||||
|
oauthFlowTimeoutEnabled: false,
|
||||||
|
}), {
|
||||||
|
oauthFlowTimeoutEnabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaults = api.buildPersistentSettingsPayload({}, { fillDefaults: true });
|
||||||
|
assert.equal(defaults.oauthFlowTimeoutEnabled, true);
|
||||||
|
});
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ const inputAutoSkipFailuresThreadIntervalMinutes = { value: '5' };
|
|||||||
const inputAutoDelayEnabled = { checked: true };
|
const inputAutoDelayEnabled = { checked: true };
|
||||||
const inputAutoDelayMinutes = { value: '30' };
|
const inputAutoDelayMinutes = { value: '30' };
|
||||||
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 DEFAULT_PHONE_VERIFICATION_REPLACEMENT_LIMIT = 3;
|
const DEFAULT_PHONE_VERIFICATION_REPLACEMENT_LIMIT = 3;
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ const inputAutoSkipFailuresThreadIntervalMinutes = { value: '0' };
|
|||||||
const inputAutoDelayEnabled = { checked: false };
|
const inputAutoDelayEnabled = { checked: false };
|
||||||
const inputAutoDelayMinutes = { value: '30' };
|
const inputAutoDelayMinutes = { value: '30' };
|
||||||
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 DEFAULT_PHONE_VERIFICATION_REPLACEMENT_LIMIT = 3;
|
const DEFAULT_PHONE_VERIFICATION_REPLACEMENT_LIMIT = 3;
|
||||||
@@ -375,6 +376,7 @@ const inputAutoSkipFailuresThreadIntervalMinutes = { value: '' };
|
|||||||
const inputAutoDelayEnabled = { checked: false };
|
const inputAutoDelayEnabled = { checked: false };
|
||||||
const inputAutoDelayMinutes = { value: '' };
|
const inputAutoDelayMinutes = { 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 DEFAULT_PHONE_VERIFICATION_ENABLED = false;
|
const DEFAULT_PHONE_VERIFICATION_ENABLED = false;
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ const inputAutoSkipFailuresThreadIntervalMinutes = { value: '0' };
|
|||||||
const inputAutoDelayEnabled = { checked: false };
|
const inputAutoDelayEnabled = { checked: false };
|
||||||
const inputAutoDelayMinutes = { value: '30' };
|
const inputAutoDelayMinutes = { value: '30' };
|
||||||
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 DEFAULT_PHONE_VERIFICATION_REPLACEMENT_LIMIT = 3;
|
const DEFAULT_PHONE_VERIFICATION_REPLACEMENT_LIMIT = 3;
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ test('sidepanel html exposes phone verification toggle and dedicated HeroSMS row
|
|||||||
assert.match(html, /id="row-phone-code-timeout-windows"/);
|
assert.match(html, /id="row-phone-code-timeout-windows"/);
|
||||||
assert.match(html, /id="row-phone-code-poll-interval-seconds"/);
|
assert.match(html, /id="row-phone-code-poll-interval-seconds"/);
|
||||||
assert.match(html, /id="row-phone-code-poll-max-rounds"/);
|
assert.match(html, /id="row-phone-code-poll-max-rounds"/);
|
||||||
|
assert.match(html, /id="row-oauth-flow-timeout"/);
|
||||||
|
assert.match(html, /id="input-oauth-flow-timeout-enabled"/);
|
||||||
|
assert.match(html, /只取消 Step 7 后链总预算/);
|
||||||
assert.doesNotMatch(html, /id="input-account-run-history-text-enabled"/);
|
assert.doesNotMatch(html, /id="input-account-run-history-text-enabled"/);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -222,6 +225,7 @@ const inputAutoSkipFailuresThreadIntervalMinutes = { value: '0' };
|
|||||||
const inputAutoDelayEnabled = { checked: false };
|
const inputAutoDelayEnabled = { checked: false };
|
||||||
const inputAutoDelayMinutes = { value: '30' };
|
const inputAutoDelayMinutes = { value: '30' };
|
||||||
const inputAutoStepDelaySeconds = { value: '' };
|
const inputAutoStepDelaySeconds = { value: '' };
|
||||||
|
const inputOAuthFlowTimeoutEnabled = { checked: false };
|
||||||
const inputPhoneVerificationEnabled = { checked: true };
|
const inputPhoneVerificationEnabled = { checked: true };
|
||||||
const inputVerificationResendCount = { value: '4' };
|
const inputVerificationResendCount = { value: '4' };
|
||||||
const inputHeroSmsApiKey = { value: 'demo-key' };
|
const inputHeroSmsApiKey = { value: 'demo-key' };
|
||||||
@@ -299,6 +303,7 @@ return { collectSettingsPayload };
|
|||||||
const payload = api.collectSettingsPayload();
|
const payload = api.collectSettingsPayload();
|
||||||
|
|
||||||
assert.equal(payload.phoneVerificationEnabled, true);
|
assert.equal(payload.phoneVerificationEnabled, true);
|
||||||
|
assert.equal(payload.oauthFlowTimeoutEnabled, false);
|
||||||
assert.equal(payload.accountRunHistoryTextEnabled, true);
|
assert.equal(payload.accountRunHistoryTextEnabled, true);
|
||||||
assert.equal(payload.accountRunHistoryHelperBaseUrl, 'http://127.0.0.1:17373');
|
assert.equal(payload.accountRunHistoryHelperBaseUrl, 'http://127.0.0.1:17373');
|
||||||
assert.equal(payload.heroSmsApiKey, 'demo-key');
|
assert.equal(payload.heroSmsApiKey, 'demo-key');
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
|
||||||
|
const step9ModuleSource = fs.readFileSync('background/steps/confirm-oauth.js', 'utf8');
|
||||||
|
|
||||||
|
test('step9 observes disabled oauth timeout while waiting for localhost callback', async () => {
|
||||||
|
const api = new Function('step9ModuleSource', `
|
||||||
|
const self = {};
|
||||||
|
let webNavListener = null;
|
||||||
|
let webNavCommittedListener = null;
|
||||||
|
let step8TabUpdatedListener = null;
|
||||||
|
let step8PendingReject = null;
|
||||||
|
let cleanupCalls = 0;
|
||||||
|
let remainingCalls = 0;
|
||||||
|
let recoveryCalls = 0;
|
||||||
|
let completePayload = null;
|
||||||
|
const callbackUrl = 'http://localhost:1455/auth/callback?code=abc&state=xyz';
|
||||||
|
|
||||||
|
const chrome = {
|
||||||
|
webNavigation: {
|
||||||
|
onBeforeNavigate: {
|
||||||
|
addListener(listener) {
|
||||||
|
webNavListener = listener;
|
||||||
|
setTimeout(() => {
|
||||||
|
if (typeof webNavListener === 'function') {
|
||||||
|
webNavListener({ tabId: 123, url: callbackUrl });
|
||||||
|
}
|
||||||
|
}, 25);
|
||||||
|
},
|
||||||
|
removeListener() {},
|
||||||
|
},
|
||||||
|
onCommitted: {
|
||||||
|
addListener(listener) {
|
||||||
|
webNavCommittedListener = listener;
|
||||||
|
},
|
||||||
|
removeListener() {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
onUpdated: {
|
||||||
|
addListener(listener) {
|
||||||
|
step8TabUpdatedListener = listener;
|
||||||
|
},
|
||||||
|
removeListener() {},
|
||||||
|
},
|
||||||
|
async update() {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function addLog() {}
|
||||||
|
function cleanupStep8NavigationListeners() {
|
||||||
|
cleanupCalls += 1;
|
||||||
|
webNavListener = null;
|
||||||
|
webNavCommittedListener = null;
|
||||||
|
step8TabUpdatedListener = null;
|
||||||
|
}
|
||||||
|
function throwIfStep8SettledOrStopped(resolved) {
|
||||||
|
if (resolved) throw new Error('already resolved');
|
||||||
|
}
|
||||||
|
async function getTabId() { return 123; }
|
||||||
|
async function isTabAlive() { return true; }
|
||||||
|
async function ensureStep8SignupPageReady() {}
|
||||||
|
async function getOAuthFlowStepTimeoutMs(defaultTimeoutMs, options = {}) {
|
||||||
|
if (options.actionLabel === 'OAuth localhost 回调') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return defaultTimeoutMs;
|
||||||
|
}
|
||||||
|
async function getOAuthFlowRemainingMs() {
|
||||||
|
remainingCalls += 1;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
async function recoverOAuthLocalhostTimeout() {
|
||||||
|
recoveryCalls += 1;
|
||||||
|
throw new Error('should not recover when timeout has been disabled');
|
||||||
|
}
|
||||||
|
async function waitForStep8Ready() {
|
||||||
|
return {
|
||||||
|
consentReady: true,
|
||||||
|
url: 'https://auth.openai.com/sign-in-with-chatgpt/codex/consent',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async function triggerStep8ContentStrategy() {}
|
||||||
|
async function waitForStep8ClickEffect() {
|
||||||
|
return { progressed: true, reason: 'url_changed', url: 'https://chatgpt.com/' };
|
||||||
|
}
|
||||||
|
function getStep8CallbackUrlFromNavigation(details, signupTabId) {
|
||||||
|
return Number(details?.tabId) === Number(signupTabId) ? details.url : '';
|
||||||
|
}
|
||||||
|
function getStep8CallbackUrlFromTabUpdate() { return ''; }
|
||||||
|
function getStep8EffectLabel() { return 'URL 已变化'; }
|
||||||
|
async function prepareStep8DebuggerClick() { return { rect: { centerX: 10, centerY: 10 } }; }
|
||||||
|
async function clickWithDebugger() {}
|
||||||
|
async function reloadStep8ConsentPage() {}
|
||||||
|
async function reuseOrCreateTab() { return 123; }
|
||||||
|
async function sleepWithStop() {}
|
||||||
|
function setWebNavListener(listener) { webNavListener = listener; }
|
||||||
|
function getWebNavListener() { return webNavListener; }
|
||||||
|
function setWebNavCommittedListener(listener) { webNavCommittedListener = listener; }
|
||||||
|
function getWebNavCommittedListener() { return webNavCommittedListener; }
|
||||||
|
function setStep8TabUpdatedListener(listener) { step8TabUpdatedListener = listener; }
|
||||||
|
function getStep8TabUpdatedListener() { return step8TabUpdatedListener; }
|
||||||
|
function setStep8PendingReject(handler) { step8PendingReject = handler; }
|
||||||
|
async function completeStepFromBackground(step, payload) {
|
||||||
|
completePayload = { step, payload };
|
||||||
|
}
|
||||||
|
|
||||||
|
const STEP8_CLICK_RETRY_DELAY_MS = 1;
|
||||||
|
const STEP8_READY_WAIT_TIMEOUT_MS = 5;
|
||||||
|
const STEP8_MAX_ROUNDS = 1;
|
||||||
|
const STEP8_STRATEGIES = [
|
||||||
|
{ mode: 'content', strategy: 'requestSubmit', label: 'form.requestSubmit' },
|
||||||
|
];
|
||||||
|
|
||||||
|
${step9ModuleSource}
|
||||||
|
|
||||||
|
const executor = self.MultiPageBackgroundStep9.createStep9Executor({
|
||||||
|
addLog,
|
||||||
|
chrome,
|
||||||
|
cleanupStep8NavigationListeners,
|
||||||
|
clickWithDebugger,
|
||||||
|
completeStepFromBackground,
|
||||||
|
ensureStep8SignupPageReady,
|
||||||
|
getOAuthFlowRemainingMs,
|
||||||
|
getOAuthFlowStepTimeoutMs,
|
||||||
|
getStep8CallbackUrlFromNavigation,
|
||||||
|
getStep8CallbackUrlFromTabUpdate,
|
||||||
|
getStep8EffectLabel,
|
||||||
|
getTabId,
|
||||||
|
getWebNavCommittedListener,
|
||||||
|
getWebNavListener,
|
||||||
|
getStep8TabUpdatedListener,
|
||||||
|
isTabAlive,
|
||||||
|
prepareStep8DebuggerClick,
|
||||||
|
recoverOAuthLocalhostTimeout,
|
||||||
|
reloadStep8ConsentPage,
|
||||||
|
reuseOrCreateTab,
|
||||||
|
setStep8PendingReject,
|
||||||
|
setStep8TabUpdatedListener,
|
||||||
|
setWebNavCommittedListener,
|
||||||
|
setWebNavListener,
|
||||||
|
sleepWithStop,
|
||||||
|
STEP8_CLICK_RETRY_DELAY_MS,
|
||||||
|
STEP8_MAX_ROUNDS,
|
||||||
|
STEP8_READY_WAIT_TIMEOUT_MS,
|
||||||
|
STEP8_STRATEGIES,
|
||||||
|
throwIfStep8SettledOrStopped,
|
||||||
|
triggerStep8ContentStrategy,
|
||||||
|
waitForStep8ClickEffect,
|
||||||
|
waitForStep8Ready,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
executeStep9: executor.executeStep9,
|
||||||
|
snapshot() {
|
||||||
|
return {
|
||||||
|
cleanupCalls,
|
||||||
|
remainingCalls,
|
||||||
|
recoveryCalls,
|
||||||
|
completePayload,
|
||||||
|
hasPendingReject: Boolean(step8PendingReject),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)(step9ModuleSource);
|
||||||
|
|
||||||
|
await api.executeStep9({
|
||||||
|
oauthUrl: 'https://auth.openai.com/original-oauth',
|
||||||
|
visibleStep: 9,
|
||||||
|
});
|
||||||
|
|
||||||
|
const snapshot = api.snapshot();
|
||||||
|
assert.equal(snapshot.recoveryCalls, 0);
|
||||||
|
assert.equal(snapshot.remainingCalls >= 1, true);
|
||||||
|
assert.equal(snapshot.cleanupCalls >= 1, true);
|
||||||
|
assert.equal(snapshot.hasPendingReject, false);
|
||||||
|
assert.deepEqual(snapshot.completePayload, {
|
||||||
|
step: 9,
|
||||||
|
payload: {
|
||||||
|
localhostUrl: 'http://localhost:1455/auth/callback?code=abc&state=xyz',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -11,6 +11,7 @@ let step8TabUpdatedListener = null;
|
|||||||
let step8PendingReject = null;
|
let step8PendingReject = null;
|
||||||
let cleanupCalls = 0;
|
let cleanupCalls = 0;
|
||||||
let timeoutCalls = 0;
|
let timeoutCalls = 0;
|
||||||
|
let remainingCalls = 0;
|
||||||
let recoveryCalls = 0;
|
let recoveryCalls = 0;
|
||||||
let completePayload = null;
|
let completePayload = null;
|
||||||
const logs = [];
|
const logs = [];
|
||||||
@@ -25,7 +26,7 @@ const chrome = {
|
|||||||
if (typeof webNavListener === 'function') {
|
if (typeof webNavListener === 'function') {
|
||||||
webNavListener({ tabId: 123, url: callbackUrl });
|
webNavListener({ tabId: 123, url: callbackUrl });
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 25);
|
||||||
},
|
},
|
||||||
removeListener() {},
|
removeListener() {},
|
||||||
},
|
},
|
||||||
@@ -107,6 +108,11 @@ async function recoverOAuthLocalhostTimeout(details = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getOAuthFlowRemainingMs() {
|
||||||
|
remainingCalls += 1;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function getStep8CallbackUrlFromNavigation(details, signupTabId) {
|
function getStep8CallbackUrlFromNavigation(details, signupTabId) {
|
||||||
if (
|
if (
|
||||||
Number(signupTabId) === Number(details?.tabId)
|
Number(signupTabId) === Number(details?.tabId)
|
||||||
@@ -163,6 +169,7 @@ const executor = self.MultiPageBackgroundStep9.createStep9Executor({
|
|||||||
clickWithDebugger,
|
clickWithDebugger,
|
||||||
completeStepFromBackground,
|
completeStepFromBackground,
|
||||||
ensureStep8SignupPageReady,
|
ensureStep8SignupPageReady,
|
||||||
|
getOAuthFlowRemainingMs,
|
||||||
getOAuthFlowStepTimeoutMs,
|
getOAuthFlowStepTimeoutMs,
|
||||||
getStep8CallbackUrlFromNavigation,
|
getStep8CallbackUrlFromNavigation,
|
||||||
getStep8CallbackUrlFromTabUpdate,
|
getStep8CallbackUrlFromTabUpdate,
|
||||||
@@ -197,6 +204,7 @@ return {
|
|||||||
return {
|
return {
|
||||||
cleanupCalls,
|
cleanupCalls,
|
||||||
timeoutCalls,
|
timeoutCalls,
|
||||||
|
remainingCalls,
|
||||||
recoveryCalls,
|
recoveryCalls,
|
||||||
completePayload,
|
completePayload,
|
||||||
hasPendingReject: Boolean(step8PendingReject),
|
hasPendingReject: Boolean(step8PendingReject),
|
||||||
|
|||||||
Reference in New Issue
Block a user