305 lines
11 KiB
JavaScript
305 lines
11 KiB
JavaScript
(function attachBackgroundStep9(root, factory) {
|
||
root.MultiPageBackgroundStep9 = factory();
|
||
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep9Module() {
|
||
function createStep9Executor(deps = {}) {
|
||
const {
|
||
addLog,
|
||
chrome,
|
||
cleanupStep8NavigationListeners,
|
||
clickWithDebugger,
|
||
completeStepFromBackground,
|
||
ensureStep8SignupPageReady,
|
||
getOAuthFlowRemainingMs,
|
||
getOAuthFlowStepTimeoutMs,
|
||
getStep8CallbackUrlFromNavigation,
|
||
getStep8CallbackUrlFromTabUpdate,
|
||
getStep8EffectLabel,
|
||
getTabId,
|
||
isTabAlive,
|
||
prepareStep8DebuggerClick,
|
||
recoverOAuthLocalhostTimeout,
|
||
reloadStep8ConsentPage,
|
||
reuseOrCreateTab,
|
||
sleepWithStop,
|
||
STEP8_CLICK_RETRY_DELAY_MS,
|
||
STEP8_MAX_ROUNDS,
|
||
STEP8_READY_WAIT_TIMEOUT_MS,
|
||
STEP8_STRATEGIES,
|
||
throwIfStep8SettledOrStopped,
|
||
triggerStep8ContentStrategy,
|
||
waitForStep8ClickEffect,
|
||
waitForStep8Ready,
|
||
setWebNavListener,
|
||
setWebNavCommittedListener,
|
||
setStep8PendingReject,
|
||
setStep8TabUpdatedListener,
|
||
} = deps;
|
||
|
||
const LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS = 240000;
|
||
const CALLBACK_TIMEOUT_CHECK_INTERVAL_MS = 1000;
|
||
|
||
function getVisibleStep(state, fallback = 9) {
|
||
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
|
||
return visibleStep > 0 ? visibleStep : fallback;
|
||
}
|
||
|
||
function getAuthLoginStepForVisibleStep(visibleStep) {
|
||
return visibleStep >= 12 ? 10 : 7;
|
||
}
|
||
|
||
async function executeStep9(state) {
|
||
const visibleStep = getVisibleStep(state, 9);
|
||
let activeState = state;
|
||
|
||
if (!activeState.oauthUrl) {
|
||
const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep);
|
||
throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${authLoginStep}。`);
|
||
}
|
||
|
||
await addLog(`步骤 ${visibleStep}:正在监听 localhost 回调地址...`);
|
||
|
||
let callbackTimeoutMs = LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS;
|
||
let timeoutRecoveryAttempted = false;
|
||
while (true) {
|
||
try {
|
||
callbackTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
|
||
? await getOAuthFlowStepTimeoutMs(LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS, {
|
||
step: visibleStep,
|
||
actionLabel: 'OAuth localhost 回调',
|
||
oauthUrl: activeState?.oauthUrl || '',
|
||
})
|
||
: LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS;
|
||
break;
|
||
} catch (error) {
|
||
if (timeoutRecoveryAttempted || typeof recoverOAuthLocalhostTimeout !== 'function') {
|
||
throw error;
|
||
}
|
||
const recoveredState = await recoverOAuthLocalhostTimeout({
|
||
error,
|
||
state: activeState,
|
||
visibleStep,
|
||
});
|
||
if (!recoveredState) {
|
||
throw error;
|
||
}
|
||
activeState = recoveredState;
|
||
timeoutRecoveryAttempted = true;
|
||
}
|
||
}
|
||
|
||
return new Promise((resolve, reject) => {
|
||
let resolved = false;
|
||
let signupTabId = null;
|
||
const callbackWaitStartedAt = Date.now();
|
||
let timeoutCheckTimer = null;
|
||
|
||
const cleanupListener = () => {
|
||
if (timeoutCheckTimer) {
|
||
clearTimeout(timeoutCheckTimer);
|
||
timeoutCheckTimer = null;
|
||
}
|
||
cleanupStep8NavigationListeners();
|
||
setStep8PendingReject(null);
|
||
};
|
||
|
||
const rejectStep9 = (error) => {
|
||
if (resolved) return;
|
||
resolved = true;
|
||
cleanupListener();
|
||
reject(error);
|
||
};
|
||
|
||
const finalizeStep9Callback = (callbackUrl) => {
|
||
if (resolved || !callbackUrl) return;
|
||
|
||
resolved = true;
|
||
cleanupListener();
|
||
|
||
addLog(`步骤 ${visibleStep}:已捕获 localhost 地址:${callbackUrl}`, 'ok').then(() => {
|
||
return completeStepFromBackground(visibleStep, { localhostUrl: callbackUrl });
|
||
}).then(() => {
|
||
resolve();
|
||
}).catch((err) => {
|
||
reject(err);
|
||
});
|
||
};
|
||
|
||
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} 的点击可能被拦截了。`));
|
||
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) => {
|
||
rejectStep9(error);
|
||
});
|
||
|
||
setWebNavListener((details) => {
|
||
const callbackUrl = getStep8CallbackUrlFromNavigation(details, signupTabId);
|
||
finalizeStep9Callback(callbackUrl);
|
||
});
|
||
|
||
setWebNavCommittedListener((details) => {
|
||
const callbackUrl = getStep8CallbackUrlFromNavigation(details, signupTabId);
|
||
finalizeStep9Callback(callbackUrl);
|
||
});
|
||
|
||
setStep8TabUpdatedListener((tabId, changeInfo, tab) => {
|
||
const callbackUrl = getStep8CallbackUrlFromTabUpdate(tabId, changeInfo, tab, signupTabId);
|
||
finalizeStep9Callback(callbackUrl);
|
||
});
|
||
|
||
(async () => {
|
||
try {
|
||
throwIfStep8SettledOrStopped(resolved);
|
||
signupTabId = await getTabId('signup-page');
|
||
throwIfStep8SettledOrStopped(resolved);
|
||
|
||
if (signupTabId && await isTabAlive('signup-page')) {
|
||
await chrome.tabs.update(signupTabId, { active: true });
|
||
await addLog(`步骤 ${visibleStep}:已切回认证页,正在准备调试器点击...`);
|
||
} else {
|
||
signupTabId = await reuseOrCreateTab('signup-page', activeState.oauthUrl);
|
||
await addLog(`步骤 ${visibleStep}:已重新打开认证页,正在准备调试器点击...`);
|
||
}
|
||
|
||
throwIfStep8SettledOrStopped(resolved);
|
||
chrome.webNavigation.onBeforeNavigate.addListener(deps.getWebNavListener());
|
||
chrome.webNavigation.onCommitted.addListener(deps.getWebNavCommittedListener());
|
||
chrome.tabs.onUpdated.addListener(deps.getStep8TabUpdatedListener());
|
||
await ensureStep8SignupPageReady(signupTabId, {
|
||
timeoutMs: typeof getOAuthFlowStepTimeoutMs === 'function'
|
||
? await getOAuthFlowStepTimeoutMs(15000, {
|
||
step: visibleStep,
|
||
actionLabel: '等待 OAuth 同意页内容脚本就绪',
|
||
})
|
||
: 15000,
|
||
logMessage: `步骤 ${visibleStep}:认证页内容脚本尚未就绪,正在等待页面恢复...`,
|
||
});
|
||
|
||
for (let round = 1; round <= STEP8_MAX_ROUNDS && !resolved; round++) {
|
||
throwIfStep8SettledOrStopped(resolved);
|
||
const pageState = await waitForStep8Ready(
|
||
signupTabId,
|
||
typeof getOAuthFlowStepTimeoutMs === 'function'
|
||
? await getOAuthFlowStepTimeoutMs(STEP8_READY_WAIT_TIMEOUT_MS, {
|
||
step: visibleStep,
|
||
actionLabel: '等待 OAuth 同意页出现',
|
||
})
|
||
: STEP8_READY_WAIT_TIMEOUT_MS
|
||
);
|
||
if (!pageState?.consentReady) {
|
||
await sleepWithStop(STEP8_CLICK_RETRY_DELAY_MS);
|
||
continue;
|
||
}
|
||
|
||
const strategy = STEP8_STRATEGIES[Math.min(round - 1, STEP8_STRATEGIES.length - 1)];
|
||
|
||
await addLog(`步骤 ${visibleStep}:第 ${round}/${STEP8_MAX_ROUNDS} 轮尝试点击“继续”(${strategy.label})...`);
|
||
|
||
if (strategy.mode === 'debugger') {
|
||
const clickActionTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
|
||
? await getOAuthFlowStepTimeoutMs(15000, {
|
||
step: visibleStep,
|
||
actionLabel: '定位 OAuth 同意页继续按钮',
|
||
})
|
||
: 15000;
|
||
const clickTarget = await prepareStep8DebuggerClick(signupTabId, {
|
||
timeoutMs: clickActionTimeoutMs,
|
||
responseTimeoutMs: clickActionTimeoutMs,
|
||
});
|
||
throwIfStep8SettledOrStopped(resolved);
|
||
await clickWithDebugger(signupTabId, clickTarget?.rect);
|
||
} else {
|
||
const clickActionTimeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
|
||
? await getOAuthFlowStepTimeoutMs(15000, {
|
||
step: visibleStep,
|
||
actionLabel: '点击 OAuth 同意页继续按钮',
|
||
})
|
||
: 15000;
|
||
await triggerStep8ContentStrategy(signupTabId, strategy.strategy, {
|
||
timeoutMs: clickActionTimeoutMs,
|
||
responseTimeoutMs: clickActionTimeoutMs,
|
||
});
|
||
}
|
||
|
||
if (resolved) {
|
||
return;
|
||
}
|
||
|
||
const effect = await waitForStep8ClickEffect(
|
||
signupTabId,
|
||
pageState.url,
|
||
typeof getOAuthFlowStepTimeoutMs === 'function'
|
||
? await getOAuthFlowStepTimeoutMs(15000, {
|
||
step: visibleStep,
|
||
actionLabel: '等待 OAuth 同意页点击生效',
|
||
})
|
||
: 15000
|
||
);
|
||
if (resolved) {
|
||
return;
|
||
}
|
||
|
||
if (effect.progressed) {
|
||
await addLog(`步骤 ${visibleStep}:检测到本次点击已生效,${getStep8EffectLabel(effect)},继续等待 localhost 回调...`, 'info');
|
||
break;
|
||
}
|
||
|
||
if (round >= STEP8_MAX_ROUNDS) {
|
||
throw new Error(`步骤 ${visibleStep}:连续 ${STEP8_MAX_ROUNDS} 轮点击“继续”后页面仍无反应。`);
|
||
}
|
||
|
||
await addLog(`步骤 ${visibleStep}:${strategy.label} 本轮点击后页面无反应,正在刷新认证页后重试(下一轮 ${round + 1}/${STEP8_MAX_ROUNDS})...`, 'warn');
|
||
await reloadStep8ConsentPage(
|
||
signupTabId,
|
||
typeof getOAuthFlowStepTimeoutMs === 'function'
|
||
? await getOAuthFlowStepTimeoutMs(30000, {
|
||
step: visibleStep,
|
||
actionLabel: '刷新 OAuth 同意页',
|
||
})
|
||
: 30000
|
||
);
|
||
await sleepWithStop(STEP8_CLICK_RETRY_DELAY_MS);
|
||
}
|
||
} catch (err) {
|
||
rejectStep9(err);
|
||
}
|
||
})();
|
||
});
|
||
}
|
||
|
||
return { executeStep9 };
|
||
}
|
||
|
||
return { createStep9Executor };
|
||
});
|