feat: 添加浏览器切换所需错误处理,增强步骤 10 的诊断信息
This commit is contained in:
@@ -147,6 +147,7 @@ const HOTMAIL_MAILBOXES = ['INBOX', 'Junk'];
|
||||
const STOP_ERROR_MESSAGE = '流程已被用户停止。';
|
||||
const CLOUDFLARE_SECURITY_BLOCK_ERROR_PREFIX = 'CF_SECURITY_BLOCKED::';
|
||||
const CLOUDFLARE_SECURITY_BLOCK_USER_MESSAGE = '您已触发Cloudflare 安全防护系统,已完全停止流程,请不要短时间内多次进行重新发送验证码,连续刷新、反复点击重试会加重风控;请先关闭页面等待 15-30 分钟,让系统的临时限制自动解除。或者更换浏览器';
|
||||
const BROWSER_SWITCH_REQUIRED_ERROR_PREFIX = 'BROWSER_SWITCH_REQUIRED::';
|
||||
const HUMAN_STEP_DELAY_MIN = 700;
|
||||
const HUMAN_STEP_DELAY_MAX = 2200;
|
||||
const STEP6_MAX_ATTEMPTS = 3;
|
||||
@@ -4000,6 +4001,17 @@ function getTerminalSecurityBlockedTitle(error) {
|
||||
return 'Cloudflare 风控拦截';
|
||||
}
|
||||
|
||||
function isBrowserSwitchRequiredError(error) {
|
||||
return getErrorMessage(error).startsWith(BROWSER_SWITCH_REQUIRED_ERROR_PREFIX);
|
||||
}
|
||||
|
||||
function getBrowserSwitchRequiredMessage(error) {
|
||||
const message = getErrorMessage(error);
|
||||
return message.startsWith(BROWSER_SWITCH_REQUIRED_ERROR_PREFIX)
|
||||
? message.slice(BROWSER_SWITCH_REQUIRED_ERROR_PREFIX.length).trim()
|
||||
: message;
|
||||
}
|
||||
|
||||
function broadcastSecurityBlockedAlert(title = '流程已完全停止', message = CLOUDFLARE_SECURITY_BLOCK_USER_MESSAGE, alertText = '检测到 Cloudflare 风控,请暂停当前操作。') {
|
||||
chrome.runtime.sendMessage({
|
||||
type: 'SECURITY_BLOCKED_ALERT',
|
||||
@@ -4023,6 +4035,13 @@ async function handleCloudflareSecurityBlocked(error) {
|
||||
return message;
|
||||
}
|
||||
|
||||
async function handleBrowserSwitchRequired(error) {
|
||||
const message = getBrowserSwitchRequiredMessage(error)
|
||||
|| '检测到第 10 步的特殊冲突状态,请更换浏览器后重新进行注册登录。';
|
||||
await requestStop({ logMessage: message });
|
||||
return message;
|
||||
}
|
||||
|
||||
function isVerificationMailPollingError(error) {
|
||||
if (typeof loggingStatus !== 'undefined' && loggingStatus?.isVerificationMailPollingError) {
|
||||
return loggingStatus.isVerificationMailPollingError(error);
|
||||
@@ -5326,6 +5345,10 @@ async function executeStep(step, options = {}) {
|
||||
await handleCloudflareSecurityBlocked(err);
|
||||
throw new Error(STOP_ERROR_MESSAGE);
|
||||
}
|
||||
if (isBrowserSwitchRequiredError(err)) {
|
||||
await handleBrowserSwitchRequired(err);
|
||||
throw new Error(STOP_ERROR_MESSAGE);
|
||||
}
|
||||
if (!(deferRetryableTransportError && doesStepUseCompletionSignal(step) && isRetryableContentScriptTransportError(err))) {
|
||||
await setStepStatus(step, 'failed');
|
||||
await addLog(`步骤 ${step} 失败:${err.message}`, 'error');
|
||||
|
||||
@@ -438,6 +438,20 @@ function formatStep10StatusSummaryValue(text, emptyText = '无') {
|
||||
return text ? `"${getInlineTextSnippet(text, 80)}"` : emptyText;
|
||||
}
|
||||
|
||||
function isStep10BrowserSwitchRequiredConflict(diagnostics = {}) {
|
||||
return Boolean(diagnostics?.hasExactSuccessVisibleBadge)
|
||||
&& /请更新\s*cli\s*proxy\s*api\s*或检查连接/i.test(String(diagnostics?.callbackFailureText || ''));
|
||||
}
|
||||
|
||||
function getStep10BrowserSwitchRequiredMessage(diagnostics = {}) {
|
||||
const callbackFailureText = normalizeStep9StatusText(diagnostics?.callbackFailureText || '');
|
||||
return [
|
||||
'检测到 CPA 页面同时显示“认证成功”和“回调 URL 提交失败: 请更新CLI Proxy API或检查连接”。',
|
||||
'这类冲突状态通常通过更换浏览器可以解决,请更换浏览器后重新进行注册登录。',
|
||||
callbackFailureText ? `面板原文:${callbackFailureText}` : '',
|
||||
].filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
function buildStep9StatusDiagnostics(entries = [], pageErrorEntries = [], pageSnippet = '') {
|
||||
const visibleEntries = entries.filter((entry) => entry.visible);
|
||||
const callbackEntries = visibleEntries.filter((entry) => entry.location === 'callback');
|
||||
@@ -688,6 +702,13 @@ async function waitForExactSuccessBadge(timeout = STEP9_SUCCESS_BADGE_TIMEOUT_MS
|
||||
}
|
||||
}
|
||||
|
||||
if (isStep10BrowserSwitchRequiredConflict(diagnostics)) {
|
||||
const browserSwitchMessage = getStep10BrowserSwitchRequiredMessage(diagnostics);
|
||||
log(`步骤 10:${browserSwitchMessage}`, 'error');
|
||||
console.error(LOG_PREFIX, '[Step 9] browser-switch conflict detected', diagnostics);
|
||||
throw new Error(`BROWSER_SWITCH_REQUIRED::${browserSwitchMessage}`);
|
||||
}
|
||||
|
||||
if (diagnostics.hasExactSuccessVisibleBadge && diagnostics.hasFailureVisibleBadge) {
|
||||
const conflictSignature = JSON.stringify({
|
||||
exactSuccessSummary: diagnostics.exactSuccessSummary,
|
||||
|
||||
@@ -71,6 +71,7 @@ test('executeStep reuses the active top-level auth chain instead of starting a d
|
||||
const api = new Function(`
|
||||
const LOG_PREFIX = '[test]';
|
||||
const STOP_ERROR_MESSAGE = '流程已被用户停止。';
|
||||
const BROWSER_SWITCH_REQUIRED_ERROR_PREFIX = 'BROWSER_SWITCH_REQUIRED::';
|
||||
const AUTH_CHAIN_STEP_IDS = new Set([7, 8, 9, 10]);
|
||||
let activeTopLevelAuthChainExecution = null;
|
||||
let stopRequested = false;
|
||||
@@ -106,6 +107,10 @@ function isTerminalSecurityBlockedError() {
|
||||
return false;
|
||||
}
|
||||
async function handleCloudflareSecurityBlocked() {}
|
||||
function isBrowserSwitchRequiredError() {
|
||||
return false;
|
||||
}
|
||||
async function handleBrowserSwitchRequired() {}
|
||||
function doesStepUseCompletionSignal() {
|
||||
return false;
|
||||
}
|
||||
@@ -159,6 +164,95 @@ return {
|
||||
assert.ok(events.logs.some(({ message }) => /复用当前授权链/.test(message)));
|
||||
});
|
||||
|
||||
test('executeStep stops flow when browser-switch-required error is raised', async () => {
|
||||
const api = new Function(`
|
||||
const LOG_PREFIX = '[test]';
|
||||
const STOP_ERROR_MESSAGE = '流程已被用户停止。';
|
||||
const BROWSER_SWITCH_REQUIRED_ERROR_PREFIX = 'BROWSER_SWITCH_REQUIRED::';
|
||||
const AUTH_CHAIN_STEP_IDS = new Set([7, 8, 9, 10]);
|
||||
let activeTopLevelAuthChainExecution = null;
|
||||
let stopRequested = false;
|
||||
const events = {
|
||||
logs: [],
|
||||
statusCalls: [],
|
||||
stopRequests: [],
|
||||
appendRecords: [],
|
||||
};
|
||||
|
||||
async function addLog(message, level = 'info') {
|
||||
events.logs.push({ message, level });
|
||||
}
|
||||
async function setStepStatus(step, status) {
|
||||
events.statusCalls.push({ step, status });
|
||||
}
|
||||
async function humanStepDelay() {}
|
||||
async function getState() {
|
||||
return {
|
||||
flowStartTime: null,
|
||||
stepStatuses: {},
|
||||
};
|
||||
}
|
||||
function getErrorMessage(error) {
|
||||
return error?.message || String(error || '');
|
||||
}
|
||||
async function appendManualAccountRunRecordIfNeeded(status, _state, reason) {
|
||||
events.appendRecords.push({ status, reason });
|
||||
}
|
||||
function isTerminalSecurityBlockedError() {
|
||||
return false;
|
||||
}
|
||||
async function handleCloudflareSecurityBlocked() {}
|
||||
async function requestStop(options = {}) {
|
||||
events.stopRequests.push(options);
|
||||
}
|
||||
function doesStepUseCompletionSignal() {
|
||||
return false;
|
||||
}
|
||||
function isRetryableContentScriptTransportError() {
|
||||
return false;
|
||||
}
|
||||
const stepRegistry = {
|
||||
async executeStep() {
|
||||
throw new Error('BROWSER_SWITCH_REQUIRED::请更换浏览器进行注册登录。');
|
||||
},
|
||||
};
|
||||
|
||||
${extractFunction('isStopError')}
|
||||
${extractFunction('throwIfStopped')}
|
||||
${extractFunction('isAuthChainStep')}
|
||||
${extractFunction('acquireTopLevelAuthChainExecution')}
|
||||
${extractFunction('isBrowserSwitchRequiredError')}
|
||||
${extractFunction('getBrowserSwitchRequiredMessage')}
|
||||
${extractFunction('handleBrowserSwitchRequired')}
|
||||
${extractFunction('executeStep')}
|
||||
|
||||
return {
|
||||
executeStep,
|
||||
snapshot() {
|
||||
return events;
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
await assert.rejects(
|
||||
() => api.executeStep(10),
|
||||
/流程已被用户停止。/
|
||||
);
|
||||
|
||||
const events = api.snapshot();
|
||||
assert.deepStrictEqual(events.stopRequests, [
|
||||
{ logMessage: '请更换浏览器进行注册登录。' },
|
||||
]);
|
||||
assert.deepStrictEqual(events.statusCalls, [
|
||||
{ step: 10, status: 'running' },
|
||||
]);
|
||||
assert.equal(
|
||||
events.logs.some(({ message }) => /步骤 10 失败/.test(message)),
|
||||
false,
|
||||
'browser-switch-required error should stop the flow before generic failed logging'
|
||||
);
|
||||
});
|
||||
|
||||
test('oauth timeout budget ignores stale deadlines from an old oauth url', async () => {
|
||||
const api = new Function(`
|
||||
const LOG_PREFIX = '[test]';
|
||||
|
||||
@@ -66,6 +66,8 @@ const bundle = [
|
||||
extractFunction('isStep9SuccessStatus'),
|
||||
extractFunction('isStep9SuccessLikeStatus'),
|
||||
extractFunction('formatStep10StatusSummaryValue'),
|
||||
extractFunction('isStep10BrowserSwitchRequiredConflict'),
|
||||
extractFunction('getStep10BrowserSwitchRequiredMessage'),
|
||||
extractFunction('buildStep9StatusDiagnostics'),
|
||||
extractFunction('extractStep10FailureDetail'),
|
||||
extractFunction('explainStep10Failure'),
|
||||
@@ -84,6 +86,8 @@ ${bundle}
|
||||
return {
|
||||
buildStep9StatusDiagnostics,
|
||||
explainStep10Failure,
|
||||
isStep10BrowserSwitchRequiredConflict,
|
||||
getStep10BrowserSwitchRequiredMessage,
|
||||
};
|
||||
`)();
|
||||
}
|
||||
@@ -200,3 +204,32 @@ test('step 10 explains callback upgrade hint with user-friendly reason', () => {
|
||||
assert.match(explanation.userMessage, /CLI Proxy API 版本过旧|管理接口未启动|连接异常/);
|
||||
assert.match(explanation.userMessage, /回调提交阶段/);
|
||||
});
|
||||
|
||||
test('step 10 requests browser switch when success badge and callback upgrade failure coexist', () => {
|
||||
const api = createApi();
|
||||
const diagnostics = api.buildStep9StatusDiagnostics(
|
||||
[
|
||||
{
|
||||
visible: true,
|
||||
text: '认证成功',
|
||||
className: 'status-badge success',
|
||||
location: 'main',
|
||||
hasErrorVisualSignal: false,
|
||||
errorVisualSummary: '',
|
||||
},
|
||||
{
|
||||
visible: true,
|
||||
text: '回调 URL 提交失败: 请更新CLI Proxy API或检查连接',
|
||||
className: 'status-badge error',
|
||||
location: 'callback',
|
||||
hasErrorVisualSignal: true,
|
||||
errorVisualSummary: 'color=rgb(220, 38, 38)',
|
||||
},
|
||||
],
|
||||
[],
|
||||
'page'
|
||||
);
|
||||
|
||||
assert.equal(api.isStep10BrowserSwitchRequiredConflict(diagnostics), true);
|
||||
assert.match(api.getStep10BrowserSwitchRequiredMessage(diagnostics), /更换浏览器后重新进行注册登录/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user