feat: 增强认证页恢复逻辑,处理 405 错误页面,更新相关测试
This commit is contained in:
@@ -491,7 +491,7 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。
|
|||||||
- 使用第 2 步已经确定好的邮箱
|
- 使用第 2 步已经确定好的邮箱
|
||||||
- 使用自定义密码或自动生成密码
|
- 使用自定义密码或自动生成密码
|
||||||
- 在密码页填写密码并提交注册表单
|
- 在密码页填写密码并提交注册表单
|
||||||
- 后台会在真正把 Step 3 记为完成前,再确认页面是否已经推进;如果此时出现认证页 `重试` 页面,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再继续后续链路
|
- 后台会在真正把 Step 3 记为完成前,再确认页面是否已经推进;如果此时出现认证页 `重试` 页面,或 `/email-verification` 上的 `405 / Route Error` 重试页,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再继续后续链路
|
||||||
|
|
||||||
实际使用的密码会写入会话状态,并同步到侧边栏显示。
|
实际使用的密码会写入会话状态,并同步到侧边栏显示。
|
||||||
|
|
||||||
@@ -499,7 +499,7 @@ Cloudflare 模式下,插件不会再调用 Cloudflare API 创建路由。
|
|||||||
|
|
||||||
根据 `Mail` 配置,轮询邮箱并提取 6 位验证码。
|
根据 `Mail` 配置,轮询邮箱并提取 6 位验证码。
|
||||||
|
|
||||||
进入邮箱轮询前,脚本会先确认认证页是否已经进入验证码页面;如果密码页出现 `糟糕,出错了 / 操作超时(Operation timed out)` 并带有 `重试` 按钮,会先通过共享恢复逻辑最多自动点击 5 次 `重试`,回到密码页重新提交,再继续等待验证码页面。
|
进入邮箱轮询前,脚本会先确认认证页是否已经进入验证码页面;如果注册认证流程出现 `糟糕,出错了 / 操作超时(Operation timed out)`,或 `/email-verification` 上的 `405 / Route Error` 且带有 `重试` 按钮,会先通过共享恢复逻辑最多自动点击 5 次 `重试`,必要时回到密码页重新提交,再继续等待验证码页面。
|
||||||
|
|
||||||
在 `Auto` 模式下,如果 Step 4 当前轮失败,后台不会立刻丢弃这轮邮箱;而是沿用当前邮箱回到 Step 1 重新开始当前轮,避免刚拿到的邮箱被直接换掉。
|
在 `Auto` 模式下,如果 Step 4 当前轮失败,后台不会立刻丢弃这轮邮箱;而是沿用当前邮箱回到 Step 1 重新开始当前轮,避免刚拿到的邮箱被直接换掉。
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
isActionEnabled,
|
isActionEnabled,
|
||||||
isVisibleElement,
|
isVisibleElement,
|
||||||
log,
|
log,
|
||||||
|
routeErrorPattern = null,
|
||||||
simulateClick,
|
simulateClick,
|
||||||
sleep,
|
sleep,
|
||||||
throwIfStopped,
|
throwIfStopped,
|
||||||
@@ -65,9 +66,12 @@
|
|||||||
const detailMatched = detailPattern instanceof RegExp
|
const detailMatched = detailPattern instanceof RegExp
|
||||||
? detailPattern.test(text)
|
? detailPattern.test(text)
|
||||||
: false;
|
: false;
|
||||||
|
const routeErrorMatched = routeErrorPattern instanceof RegExp
|
||||||
|
? routeErrorPattern.test(text)
|
||||||
|
: false;
|
||||||
const maxCheckAttemptsBlocked = /max_check_attempts/i.test(text);
|
const maxCheckAttemptsBlocked = /max_check_attempts/i.test(text);
|
||||||
|
|
||||||
if (!titleMatched && !detailMatched && !maxCheckAttemptsBlocked) {
|
if (!titleMatched && !detailMatched && !routeErrorMatched && !maxCheckAttemptsBlocked) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +82,7 @@
|
|||||||
retryEnabled: isActionEnabled(retryButton),
|
retryEnabled: isActionEnabled(retryButton),
|
||||||
titleMatched,
|
titleMatched,
|
||||||
detailMatched,
|
detailMatched,
|
||||||
|
routeErrorMatched,
|
||||||
maxCheckAttemptsBlocked,
|
maxCheckAttemptsBlocked,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-41
@@ -253,39 +253,17 @@ async function resendVerificationCode(step, timeout = 45000) {
|
|||||||
|
|
||||||
function is405MethodNotAllowedPage() {
|
function is405MethodNotAllowedPage() {
|
||||||
const pageText = document.body?.textContent || '';
|
const pageText = document.body?.textContent || '';
|
||||||
return /405\s+Method\s+Not\s+Allowed/i.test(pageText)
|
return AUTH_ROUTE_ERROR_PATTERN.test(pageText);
|
||||||
|| /Route\s+Error.*405/i.test(pageText);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handle405ResendError(step, remainingTimeout = 30000) {
|
async function handle405ResendError(step, remainingTimeout = 30000) {
|
||||||
const start = Date.now();
|
await recoverCurrentAuthRetryPage({
|
||||||
let retryCount = 0;
|
logLabel: `步骤 ${step}:检测到 405 错误页面,正在点击“重试”恢复`,
|
||||||
|
pathPatterns: [],
|
||||||
while (Date.now() - start < remainingTimeout) {
|
step,
|
||||||
throwIfStopped();
|
timeoutMs: Math.max(1000, remainingTimeout),
|
||||||
|
});
|
||||||
if (!is405MethodNotAllowedPage()) {
|
log(`步骤 ${step}:405 错误已恢复,页面已返回验证码页面。`);
|
||||||
// Page recovered — back to verification page
|
|
||||||
log(`步骤 ${step}:405 错误已恢复,页面已返回验证码页面。`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const retryBtn = getAuthRetryButton();
|
|
||||||
if (retryBtn) {
|
|
||||||
retryCount++;
|
|
||||||
log(`步骤 ${step}:检测到 405 错误页面,正在点击"Try again"(第 ${retryCount} 次)...`, 'warn');
|
|
||||||
await humanPause(300, 800);
|
|
||||||
simulateClick(retryBtn);
|
|
||||||
|
|
||||||
// Wait 3 seconds before checking again
|
|
||||||
await sleep(3000);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await sleep(500);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`步骤 ${step}:405 错误恢复超时,无法返回验证码页面。URL: ${location.href}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -633,6 +611,7 @@ const ADD_PHONE_PAGE_PATTERN = /add[\s-]*phone|添加手机号|手机号码|手
|
|||||||
const STEP5_SUBMIT_ERROR_PATTERN = /无法根据该信息创建帐户|请重试|unable\s+to\s+create\s+(?:your\s+)?account|couldn'?t\s+create\s+(?:your\s+)?account|something\s+went\s+wrong|invalid\s+(?:birthday|birth|date)|生日|出生日期/i;
|
const STEP5_SUBMIT_ERROR_PATTERN = /无法根据该信息创建帐户|请重试|unable\s+to\s+create\s+(?:your\s+)?account|couldn'?t\s+create\s+(?:your\s+)?account|something\s+went\s+wrong|invalid\s+(?:birthday|birth|date)|生日|出生日期/i;
|
||||||
const AUTH_TIMEOUT_ERROR_TITLE_PATTERN = /糟糕,出错了|something\s+went\s+wrong|oops/i;
|
const AUTH_TIMEOUT_ERROR_TITLE_PATTERN = /糟糕,出错了|something\s+went\s+wrong|oops/i;
|
||||||
const AUTH_TIMEOUT_ERROR_DETAIL_PATTERN = /operation\s+timed\s+out|timed\s+out|请求超时|操作超时/i;
|
const AUTH_TIMEOUT_ERROR_DETAIL_PATTERN = /operation\s+timed\s+out|timed\s+out|请求超时|操作超时/i;
|
||||||
|
const AUTH_ROUTE_ERROR_PATTERN = /405\s+method\s+not\s+allowed|route\s+error.*405/i;
|
||||||
const SIGNUP_EMAIL_EXISTS_PATTERN = /与此电子邮件地址相关联的帐户已存在|account\s+associated\s+with\s+this\s+email\s+address\s+already\s+exists|email\s+address.*already\s+exists/i;
|
const SIGNUP_EMAIL_EXISTS_PATTERN = /与此电子邮件地址相关联的帐户已存在|account\s+associated\s+with\s+this\s+email\s+address\s+already\s+exists|email\s+address.*already\s+exists/i;
|
||||||
|
|
||||||
const authPageRecovery = self.MultiPageAuthPageRecovery?.createAuthPageRecovery?.({
|
const authPageRecovery = self.MultiPageAuthPageRecovery?.createAuthPageRecovery?.({
|
||||||
@@ -643,6 +622,7 @@ const authPageRecovery = self.MultiPageAuthPageRecovery?.createAuthPageRecovery?
|
|||||||
isActionEnabled,
|
isActionEnabled,
|
||||||
isVisibleElement,
|
isVisibleElement,
|
||||||
log,
|
log,
|
||||||
|
routeErrorPattern: AUTH_ROUTE_ERROR_PATTERN,
|
||||||
simulateClick,
|
simulateClick,
|
||||||
sleep,
|
sleep,
|
||||||
throwIfStopped,
|
throwIfStopped,
|
||||||
@@ -999,9 +979,10 @@ function getAuthTimeoutErrorPageState(options = {}) {
|
|||||||
const titleMatched = AUTH_TIMEOUT_ERROR_TITLE_PATTERN.test(text)
|
const titleMatched = AUTH_TIMEOUT_ERROR_TITLE_PATTERN.test(text)
|
||||||
|| AUTH_TIMEOUT_ERROR_TITLE_PATTERN.test(document.title || '');
|
|| AUTH_TIMEOUT_ERROR_TITLE_PATTERN.test(document.title || '');
|
||||||
const detailMatched = AUTH_TIMEOUT_ERROR_DETAIL_PATTERN.test(text);
|
const detailMatched = AUTH_TIMEOUT_ERROR_DETAIL_PATTERN.test(text);
|
||||||
|
const routeErrorMatched = AUTH_ROUTE_ERROR_PATTERN.test(text);
|
||||||
const maxCheckAttemptsBlocked = /max_check_attempts/i.test(text);
|
const maxCheckAttemptsBlocked = /max_check_attempts/i.test(text);
|
||||||
|
|
||||||
if (!titleMatched && !detailMatched && !maxCheckAttemptsBlocked) {
|
if (!titleMatched && !detailMatched && !routeErrorMatched && !maxCheckAttemptsBlocked) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1012,16 +993,32 @@ function getAuthTimeoutErrorPageState(options = {}) {
|
|||||||
retryEnabled: isActionEnabled(retryButton),
|
retryEnabled: isActionEnabled(retryButton),
|
||||||
titleMatched,
|
titleMatched,
|
||||||
detailMatched,
|
detailMatched,
|
||||||
|
routeErrorMatched,
|
||||||
maxCheckAttemptsBlocked,
|
maxCheckAttemptsBlocked,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSignupAuthRetryPathPatterns() {
|
||||||
|
return [
|
||||||
|
/\/create-account\/password(?:[/?#]|$)/i,
|
||||||
|
/\/email-verification(?:[/?#]|$)/i,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLoginAuthRetryPathPatterns() {
|
||||||
|
return [
|
||||||
|
/\/log-in(?:[/?#]|$)/i,
|
||||||
|
/\/email-verification(?:[/?#]|$)/i,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function getAuthRetryPathPatternsForFlow(flow = 'auth') {
|
function getAuthRetryPathPatternsForFlow(flow = 'auth') {
|
||||||
switch (flow) {
|
switch (flow) {
|
||||||
|
case 'signup':
|
||||||
case 'signup_password':
|
case 'signup_password':
|
||||||
return [/\/create-account\/password(?:[/?#]|$)/i];
|
return getSignupAuthRetryPathPatterns();
|
||||||
case 'login':
|
case 'login':
|
||||||
return [/\/log-in(?:[/?#]|$)/i];
|
return getLoginAuthRetryPathPatterns();
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -1038,16 +1035,19 @@ async function recoverCurrentAuthRetryPage(payload = {}) {
|
|||||||
flow = 'auth',
|
flow = 'auth',
|
||||||
logLabel = '',
|
logLabel = '',
|
||||||
maxClickAttempts = 5,
|
maxClickAttempts = 5,
|
||||||
|
pathPatterns = null,
|
||||||
step = null,
|
step = null,
|
||||||
timeoutMs = 12000,
|
timeoutMs = 12000,
|
||||||
waitAfterClickMs = 3000,
|
waitAfterClickMs = 3000,
|
||||||
} = payload;
|
} = payload;
|
||||||
const pathPatterns = getAuthRetryPathPatternsForFlow(flow);
|
const resolvedPathPatterns = Array.isArray(pathPatterns)
|
||||||
|
? pathPatterns
|
||||||
|
: getAuthRetryPathPatternsForFlow(flow);
|
||||||
if (authPageRecovery?.recoverAuthRetryPage) {
|
if (authPageRecovery?.recoverAuthRetryPage) {
|
||||||
return authPageRecovery.recoverAuthRetryPage({
|
return authPageRecovery.recoverAuthRetryPage({
|
||||||
logLabel,
|
logLabel,
|
||||||
maxClickAttempts,
|
maxClickAttempts,
|
||||||
pathPatterns,
|
pathPatterns: resolvedPathPatterns,
|
||||||
step,
|
step,
|
||||||
timeoutMs,
|
timeoutMs,
|
||||||
waitAfterClickMs,
|
waitAfterClickMs,
|
||||||
@@ -1061,7 +1061,7 @@ async function recoverCurrentAuthRetryPage(payload = {}) {
|
|||||||
let idlePollCount = 0;
|
let idlePollCount = 0;
|
||||||
while (clickCount < maxClickAttempts) {
|
while (clickCount < maxClickAttempts) {
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
const retryState = getCurrentAuthRetryPageState(flow);
|
const retryState = getAuthTimeoutErrorPageState({ pathPatterns: resolvedPathPatterns });
|
||||||
if (!retryState) {
|
if (!retryState) {
|
||||||
return {
|
return {
|
||||||
recovered: clickCount > 0,
|
recovered: clickCount > 0,
|
||||||
@@ -1082,7 +1082,7 @@ async function recoverCurrentAuthRetryPage(payload = {}) {
|
|||||||
const settleStart = Date.now();
|
const settleStart = Date.now();
|
||||||
while (Date.now() - settleStart < waitAfterClickMs) {
|
while (Date.now() - settleStart < waitAfterClickMs) {
|
||||||
throwIfStopped();
|
throwIfStopped();
|
||||||
if (!getCurrentAuthRetryPageState(flow)) {
|
if (!getAuthTimeoutErrorPageState({ pathPatterns: resolvedPathPatterns })) {
|
||||||
return {
|
return {
|
||||||
recovered: true,
|
recovered: true,
|
||||||
clickCount,
|
clickCount,
|
||||||
@@ -1102,7 +1102,7 @@ async function recoverCurrentAuthRetryPage(payload = {}) {
|
|||||||
await sleep(250);
|
await sleep(250);
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalRetryState = getCurrentAuthRetryPageState(flow);
|
const finalRetryState = getAuthTimeoutErrorPageState({ pathPatterns: resolvedPathPatterns });
|
||||||
if (!finalRetryState) {
|
if (!finalRetryState) {
|
||||||
return {
|
return {
|
||||||
recovered: clickCount > 0,
|
recovered: clickCount > 0,
|
||||||
@@ -1119,7 +1119,7 @@ async function recoverCurrentAuthRetryPage(payload = {}) {
|
|||||||
|
|
||||||
function getSignupPasswordTimeoutErrorPageState() {
|
function getSignupPasswordTimeoutErrorPageState() {
|
||||||
return getAuthTimeoutErrorPageState({
|
return getAuthTimeoutErrorPageState({
|
||||||
pathPatterns: [/\/create-account\/password(?:[/?#]|$)/i],
|
pathPatterns: getSignupAuthRetryPathPatterns(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1504,8 +1504,8 @@ async function prepareSignupVerificationFlow(payload = {}, timeout = 30000) {
|
|||||||
|
|
||||||
if (snapshot.state === 'error') {
|
if (snapshot.state === 'error') {
|
||||||
await recoverCurrentAuthRetryPage({
|
await recoverCurrentAuthRetryPage({
|
||||||
flow: 'signup_password',
|
flow: 'signup',
|
||||||
logLabel: `${prepareLogLabel}:检测到密码页超时报错,正在点击“重试”恢复(第 ${recoveryRound}/${maxRecoveryRounds} 次)`,
|
logLabel: `${prepareLogLabel}:检测到注册认证重试页,正在点击“重试”恢复(第 ${recoveryRound}/${maxRecoveryRounds} 次)`,
|
||||||
step: 4,
|
step: 4,
|
||||||
timeoutMs: 12000,
|
timeoutMs: 12000,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ function createRetryButton() {
|
|||||||
function createRecoveryApi(state) {
|
function createRecoveryApi(state) {
|
||||||
const retryButton = createRetryButton();
|
const retryButton = createRetryButton();
|
||||||
global.location = {
|
global.location = {
|
||||||
pathname: '/log-in',
|
pathname: state.pathname || '/log-in',
|
||||||
href: 'https://auth.openai.com/log-in',
|
href: state.href || `https://auth.openai.com${state.pathname || '/log-in'}`,
|
||||||
};
|
};
|
||||||
global.document = {
|
global.document = {
|
||||||
title: 'Something went wrong',
|
title: state.title ?? 'Something went wrong',
|
||||||
querySelector(selector) {
|
querySelector(selector) {
|
||||||
if (selector === 'button[data-dd-action-name="Try again"]' && state.retryVisible) {
|
if (selector === 'button[data-dd-action-name="Try again"]' && state.retryVisible) {
|
||||||
return retryButton;
|
return retryButton;
|
||||||
@@ -42,6 +42,7 @@ function createRecoveryApi(state) {
|
|||||||
isActionEnabled: (element) => Boolean(element) && !element.disabled && element.getAttribute('aria-disabled') !== 'true',
|
isActionEnabled: (element) => Boolean(element) && !element.disabled && element.getAttribute('aria-disabled') !== 'true',
|
||||||
isVisibleElement: () => true,
|
isVisibleElement: () => true,
|
||||||
log: () => {},
|
log: () => {},
|
||||||
|
routeErrorPattern: /405\s+method\s+not\s+allowed|route\s+error.*405/i,
|
||||||
simulateClick: () => {
|
simulateClick: () => {
|
||||||
state.clickCount += 1;
|
state.clickCount += 1;
|
||||||
if (typeof state.onClick === 'function') {
|
if (typeof state.onClick === 'function') {
|
||||||
@@ -78,9 +79,30 @@ test('auth page recovery detects retry page state', () => {
|
|||||||
assert.equal(snapshot.retryEnabled, true);
|
assert.equal(snapshot.retryEnabled, true);
|
||||||
assert.equal(snapshot.titleMatched, true);
|
assert.equal(snapshot.titleMatched, true);
|
||||||
assert.equal(snapshot.detailMatched, false);
|
assert.equal(snapshot.detailMatched, false);
|
||||||
|
assert.equal(snapshot.routeErrorMatched, false);
|
||||||
assert.equal(snapshot.maxCheckAttemptsBlocked, false);
|
assert.equal(snapshot.maxCheckAttemptsBlocked, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('auth page recovery detects route error retry page on email verification route', () => {
|
||||||
|
const state = {
|
||||||
|
clickCount: 0,
|
||||||
|
pageText: 'Route Error (405 Method Not Allowed): email-verification action missing.',
|
||||||
|
pathname: '/email-verification',
|
||||||
|
retryVisible: true,
|
||||||
|
title: '',
|
||||||
|
};
|
||||||
|
const api = createRecoveryApi(state);
|
||||||
|
|
||||||
|
const snapshot = api.getAuthTimeoutErrorPageState({
|
||||||
|
pathPatterns: [/\/email-verification(?:[/?#]|$)/i],
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(Boolean(snapshot), true);
|
||||||
|
assert.equal(snapshot.titleMatched, false);
|
||||||
|
assert.equal(snapshot.detailMatched, false);
|
||||||
|
assert.equal(snapshot.routeErrorMatched, true);
|
||||||
|
});
|
||||||
|
|
||||||
test('auth page recovery clicks retry and waits until page recovers', async () => {
|
test('auth page recovery clicks retry and waits until page recovers', async () => {
|
||||||
const state = {
|
const state = {
|
||||||
clickCount: 0,
|
clickCount: 0,
|
||||||
|
|||||||
@@ -140,3 +140,53 @@ return {
|
|||||||
retryButton: { textContent: 'Try again' },
|
retryButton: { textContent: 'Try again' },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('signup verification state treats email-verification retry page as error instead of verification', () => {
|
||||||
|
const api = new Function(`
|
||||||
|
const location = {
|
||||||
|
pathname: '/email-verification',
|
||||||
|
};
|
||||||
|
|
||||||
|
function getAuthTimeoutErrorPageState(options) {
|
||||||
|
return options.pathPatterns.some((pattern) => pattern.test(location.pathname))
|
||||||
|
? { retryButton: { textContent: 'Try again' } }
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStep5Ready() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isVerificationPageStillVisible() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSignupEmailAlreadyExistsPage() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSignupPasswordInput() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSignupPasswordSubmitButton() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
${extractFunction('getSignupAuthRetryPathPatterns')}
|
||||||
|
${extractFunction('getSignupPasswordTimeoutErrorPageState')}
|
||||||
|
${extractFunction('isSignupPasswordErrorPage')}
|
||||||
|
${extractFunction('inspectSignupVerificationState')}
|
||||||
|
|
||||||
|
return {
|
||||||
|
run() {
|
||||||
|
return inspectSignupVerificationState();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`)();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(api.run(), {
|
||||||
|
state: 'error',
|
||||||
|
retryButton: { textContent: 'Try again' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
+2
-2
@@ -283,7 +283,7 @@
|
|||||||
5. 内容脚本先上报 Step 3 完成信号
|
5. 内容脚本先上报 Step 3 完成信号
|
||||||
6. 上报完成后再异步点击提交,避免页面跳转打断响应通道
|
6. 上报完成后再异步点击提交,避免页面跳转打断响应通道
|
||||||
7. 延迟提交真正触发前会再次检查 Stop 状态,避免用户已停止时页面仍继续自动提交
|
7. 延迟提交真正触发前会再次检查 Stop 状态,避免用户已停止时页面仍继续自动提交
|
||||||
8. 后台在真正确认 Step 3 完成前,会额外检查提交后是否切换页面;如果出现认证页 `Try again / 重试` 页面,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再继续后续链路
|
8. 后台在真正确认 Step 3 完成前,会额外检查提交后是否切换页面;如果出现认证页 `Try again / 重试` 页面,或 `/email-verification` 上的 `405 / Route Error` 重试页,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再继续后续链路
|
||||||
|
|
||||||
### Step 4 / Step 8
|
### Step 4 / Step 8
|
||||||
|
|
||||||
@@ -584,7 +584,7 @@
|
|||||||
## 2026-04 链路补充:认证页共享恢复
|
## 2026-04 链路补充:认证页共享恢复
|
||||||
|
|
||||||
- 新增共享恢复层:`content/auth-page-recovery.js`。
|
- 新增共享恢复层:`content/auth-page-recovery.js`。
|
||||||
- Step 4 在等待注册验证码页时,如果命中认证页 `Try again / 重试` 页,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再继续回到密码页重提和验证码页确认流程。
|
- Step 4 在等待注册验证码页时,如果命中认证页 `Try again / 重试` 页,或 `/email-verification` 上的 `405 / Route Error` 重试页,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复,再继续回到密码页重提和验证码页确认流程。
|
||||||
- Step 7 在识别到登录超时报错页时,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复当前页面;若仍未恢复,则按原有可恢复失败逻辑重跑 Step 7。
|
- Step 7 在识别到登录超时报错页时,会先通过共享恢复逻辑最多自动点击 5 次 `重试` 尝试恢复当前页面;若仍未恢复,则按原有可恢复失败逻辑重跑 Step 7。
|
||||||
- Step 8 如果发现认证页已经进入登录超时报错/重试页,会直接报错并回到 Step 7 重新开始,而不是在 Step 8 内部点击 `重试`。
|
- Step 8 如果发现认证页已经进入登录超时报错/重试页,会直接报错并回到 Step 7 重新开始,而不是在 Step 8 内部点击 `重试`。
|
||||||
- 任意认证页重试页如果正文中出现 `max_check_attempts`,会被视为 Cloudflare 风控触发:后台立刻完全停止流程,侧边栏会复用现有确认弹窗提示等待 15~30 分钟后再试,避免继续刷新或反复重试加重风控,确认按钮显示为“我知道了”。
|
- 任意认证页重试页如果正文中出现 `max_check_attempts`,会被视为 Cloudflare 风控触发:后台立刻完全停止流程,侧边栏会复用现有确认弹窗提示等待 15~30 分钟后再试,避免继续刷新或反复重试加重风控,确认按钮显示为“我知道了”。
|
||||||
|
|||||||
Reference in New Issue
Block a user