Files
FlowPilot/content/auth-page-recovery.js
T
2026-05-08 20:06:14 -04:00

246 lines
8.3 KiB
JavaScript

(function authPageRecoveryModule(root, factory) {
if (typeof module !== 'undefined' && module.exports) {
module.exports = factory();
return;
}
root.MultiPageAuthPageRecovery = factory();
})(typeof self !== 'undefined' ? self : globalThis, function createAuthPageRecoveryModule() {
function createAuthPageRecovery(deps = {}) {
const {
detailPattern = null,
getActionText,
getPageTextSnapshot,
humanPause,
isActionEnabled,
isVisibleElement,
log,
performOperationWithDelay: injectedPerformOperationWithDelay,
routeErrorPattern = null,
simulateClick,
sleep,
throwIfStopped,
titlePattern = null,
} = deps;
function matchesPathPatterns(pathname, pathPatterns = []) {
if (!Array.isArray(pathPatterns) || !pathPatterns.length) {
return true;
}
return pathPatterns.some((pattern) => pattern instanceof RegExp && pattern.test(pathname));
}
function getAuthRetryButton(options = {}) {
const { allowDisabled = false } = options;
const direct = document.querySelector('button[data-dd-action-name="Try again"]');
if (direct && isVisibleElement(direct) && (allowDisabled || isActionEnabled(direct))) {
return direct;
}
const candidates = document.querySelectorAll('button, [role="button"]');
return Array.from(candidates).find((element) => {
if (!isVisibleElement(element) || (!allowDisabled && !isActionEnabled(element))) {
return false;
}
const text = typeof getActionText === 'function' ? getActionText(element) : '';
return /重试|try\s+again/i.test(text);
}) || null;
}
function getAuthTimeoutErrorPageState(options = {}) {
const { pathPatterns = [] } = options;
const pathname = location.pathname || '';
if (!matchesPathPatterns(pathname, pathPatterns)) {
return null;
}
const retryButton = getAuthRetryButton({ allowDisabled: true });
if (!retryButton) {
return null;
}
const text = typeof getPageTextSnapshot === 'function' ? getPageTextSnapshot() : '';
const title = typeof document !== 'undefined' ? String(document.title || '') : '';
const titleMatched = titlePattern instanceof RegExp
? titlePattern.test(text) || titlePattern.test(title)
: false;
const detailMatched = detailPattern instanceof RegExp
? detailPattern.test(text)
: false;
const routeErrorMatched = routeErrorPattern instanceof RegExp
? routeErrorPattern.test(text)
: false;
const maxCheckAttemptsBlocked = /max_check_attempts/i.test(text);
const userAlreadyExistsBlocked = /user_already_exists/i.test(text);
const fetchFailedMatched = /failed\s+to\s+fetch|network\s+error|fetch\s+failed/i.test(text);
if (!titleMatched && !detailMatched && !routeErrorMatched && !fetchFailedMatched && !maxCheckAttemptsBlocked && !userAlreadyExistsBlocked) {
return null;
}
return {
path: pathname,
url: location.href,
retryButton,
retryEnabled: isActionEnabled(retryButton),
titleMatched,
detailMatched,
routeErrorMatched,
fetchFailedMatched,
maxCheckAttemptsBlocked,
userAlreadyExistsBlocked,
};
}
async function waitForRetryPageRecoveryAfterClick(options = {}) {
const {
pathPatterns = [],
pollIntervalMs = 250,
settleAfterClickMs = 3000,
} = options;
const startedAt = Date.now();
while (Date.now() - startedAt < settleAfterClickMs) {
if (typeof throwIfStopped === 'function') {
throwIfStopped();
}
const retryState = getAuthTimeoutErrorPageState({ pathPatterns });
if (!retryState) {
return {
recovered: true,
elapsedMs: Date.now() - startedAt,
};
}
await sleep(pollIntervalMs);
}
return {
recovered: false,
elapsedMs: Date.now() - startedAt,
};
}
async function recoverAuthRetryPage(options = {}) {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
const performOperationWithDelay = injectedPerformOperationWithDelay
|| rootScope.CodexOperationDelay?.performOperationWithDelay
|| (async (_metadata, operation) => operation());
const {
logLabel = '',
maxClickAttempts = 5,
pathPatterns = [],
pollIntervalMs = 250,
step = null,
timeoutMs = 12000,
waitAfterClickMs = 3000,
} = options;
const maxIdlePolls = timeoutMs > 0
? Math.max(1, Math.ceil(timeoutMs / Math.max(1, pollIntervalMs)))
: Number.POSITIVE_INFINITY;
let clickCount = 0;
let idlePollCount = 0;
while (clickCount < maxClickAttempts) {
if (typeof throwIfStopped === 'function') {
throwIfStopped();
}
const retryState = getAuthTimeoutErrorPageState({ pathPatterns });
if (!retryState) {
return {
recovered: clickCount > 0,
clickCount,
url: location.href,
};
}
if (retryState.maxCheckAttemptsBlocked) {
throw new Error(
'CF_SECURITY_BLOCKED::您已触发Cloudflare 安全防护系统,已完全停止流程,请不要短时间内多次进行重新发送验证码,连续刷新、反复点击重试会加重风控;请先关闭页面等待 15-30 分钟,让系统的临时限制自动解除。或者更换浏览器'
);
}
if (retryState.userAlreadyExistsBlocked) {
throw new Error(
'SIGNUP_USER_ALREADY_EXISTS::步骤 4:检测到 user_already_exists,说明当前用户已存在,当前轮将直接停止。'
);
}
if (retryState.retryButton && retryState.retryEnabled) {
idlePollCount = 0;
clickCount += 1;
if (typeof log === 'function') {
const prefix = logLabel || `步骤 ${step || '?'}:检测到重试页,正在点击“重试”恢复`;
log(`${prefix}(第 ${clickCount} 次)...`, 'warn');
}
if (typeof humanPause === 'function') {
await humanPause(300, 800);
}
await performOperationWithDelay({ stepKey: options.stepKey || '', kind: 'click', label: 'auth-retry-click' }, async () => {
simulateClick(retryState.retryButton);
});
const recoveryResult = await waitForRetryPageRecoveryAfterClick({
pathPatterns,
pollIntervalMs,
settleAfterClickMs: waitAfterClickMs,
});
if (recoveryResult.recovered) {
return {
recovered: true,
clickCount,
url: location.href,
};
}
continue;
}
idlePollCount += 1;
if (idlePollCount >= maxIdlePolls) {
throw new Error(
`${logLabel || `步骤 ${step || '?'}:重试页恢复`}超时:重试按钮长时间不可点击。URL: ${location.href}`
);
}
await sleep(pollIntervalMs);
}
const finalRetryState = getAuthTimeoutErrorPageState({ pathPatterns });
if (!finalRetryState) {
return {
recovered: clickCount > 0,
clickCount,
url: location.href,
};
}
if (finalRetryState.maxCheckAttemptsBlocked) {
throw new Error(
'CF_SECURITY_BLOCKED::您已触发Cloudflare 安全防护系统,已完全停止流程,请不要短时间内多次进行重新发送验证码,连续刷新、反复点击重试会加重风控;请先关闭页面等待 15-30 分钟,让系统的临时限制自动解除。或者更换浏览器'
);
}
if (finalRetryState.userAlreadyExistsBlocked) {
throw new Error(
'SIGNUP_USER_ALREADY_EXISTS::步骤 4:检测到 user_already_exists,说明当前用户已存在,当前轮将直接停止。'
);
}
throw new Error(
`${logLabel || `步骤 ${step || '?'}:重试页恢复`}失败:已连续点击“重试” ${maxClickAttempts} 次,页面仍未恢复。URL: ${location.href}`
);
}
return {
getAuthRetryButton,
getAuthTimeoutErrorPageState,
recoverAuthRetryPage,
};
}
return {
createAuthPageRecovery,
};
});