Merge origin/dev into PR 225 review fix

This commit is contained in:
QLHazyCoder
2026-05-10 06:02:53 +08:00
42 changed files with 3710 additions and 175 deletions
+90 -8
View File
@@ -39,7 +39,7 @@
return '';
}
function extractRecordStep(status = '', detail = '') {
function extractRecordStepFromStatus(status = '') {
const normalizedStatus = String(status || '').trim().toLowerCase();
const statusMatch = normalizedStatus.match(/^step(\d+)_(?:failed|stopped)$/);
if (statusMatch) {
@@ -47,6 +47,21 @@
return Number.isInteger(step) && step > 0 ? step : null;
}
return null;
}
function extractRecordStepFromDetailPrefix(detail = '') {
const text = String(detail || '').trim();
const detailMatch = text.match(/^(?:Step\s+(\d+)|步骤\s*(\d+))\s*(?::||失败|停止|已|\b)/i);
if (!detailMatch) {
return null;
}
const step = Number(detailMatch[1] || detailMatch[2]);
return Number.isInteger(step) && step > 0 ? step : null;
}
function extractAnyRecordStepFromDetail(detail = '') {
const text = String(detail || '').trim();
const detailMatch = text.match(/(?:Step\s+(\d+)|步骤\s*(\d+))/i);
if (!detailMatch) {
@@ -57,6 +72,76 @@
return Number.isInteger(step) && step > 0 ? step : null;
}
function extractRecordStep(status = '', detail = '') {
return extractRecordStepFromStatus(status) || extractRecordStepFromDetailPrefix(detail);
}
function parseFailureLabelStep(label = '') {
const match = String(label || '').trim().match(/^步骤\s*(\d+)\s*(?:失败|停止)$/);
if (!match) {
return null;
}
const step = Number(match[1]);
return Number.isInteger(step) && step > 0 ? step : null;
}
function shouldIgnorePersistedFailedStepCandidate(candidate, failureDetail = '') {
if (!Number.isInteger(candidate) || candidate <= 0) {
return true;
}
const text = String(failureDetail || '').trim();
if (!text) {
return false;
}
const leadingStep = extractRecordStepFromDetailPrefix(text);
if (Number.isInteger(leadingStep) && leadingStep > 0) {
return leadingStep !== candidate;
}
const incidentalStep = extractAnyRecordStepFromDetail(text);
return incidentalStep === candidate;
}
function resolveNormalizedFailedStep(record = {}, failureDetail = '') {
const explicitStatusStep = extractRecordStepFromStatus(record.finalStatus || record.status || '');
if (Number.isInteger(explicitStatusStep) && explicitStatusStep > 0) {
return explicitStatusStep;
}
const detailStep = extractRecordStepFromDetailPrefix(failureDetail);
if (Number.isInteger(detailStep) && detailStep > 0) {
return detailStep;
}
const failedStepCandidate = Number(record.failedStep);
if (Number.isInteger(failedStepCandidate)
&& failedStepCandidate > 0
&& !shouldIgnorePersistedFailedStepCandidate(failedStepCandidate, failureDetail)) {
return failedStepCandidate;
}
return null;
}
function resolveFailureLabel(finalStatus, rawFailureLabel = '', computedFailureLabel = '', failedStep = null) {
const rawLabel = String(rawFailureLabel || '').trim();
if (finalStatus === 'stopped') {
return computedFailureLabel;
}
if (finalStatus !== 'failed') {
return rawLabel || computedFailureLabel;
}
const rawStep = parseFailureLabelStep(rawLabel);
if (Number.isInteger(rawStep) && rawStep > 0) {
return rawStep === failedStep ? rawLabel : computedFailureLabel;
}
return rawLabel || computedFailureLabel;
}
function isPhoneVerificationFailure(detail = '') {
const text = String(detail || '').trim();
if (!text) {
@@ -247,10 +332,9 @@
const failureDetail = finalStatus === 'failed' || finalStatus === 'stopped'
? String(record.failureDetail || record.reason || '').trim()
: '';
const failedStepCandidate = Number(record.failedStep);
const failedStep = Number.isInteger(failedStepCandidate) && failedStepCandidate > 0
? failedStepCandidate
: extractRecordStep(record.finalStatus || record.status || '', failureDetail);
const failedStep = finalStatus === 'failed' || finalStatus === 'stopped'
? resolveNormalizedFailedStep(record, failureDetail)
: null;
const autoRunContext = normalizeAutoRunContext(record.autoRunContext);
const retryCount = normalizeRetryCount(
record.retryCount !== undefined
@@ -271,9 +355,7 @@
finalStatus,
finishedAt,
retryCount,
failureLabel: finalStatus === 'stopped'
? computedFailureLabel
: (rawFailureLabel || computedFailureLabel),
failureLabel: resolveFailureLabel(finalStatus, rawFailureLabel, computedFailureLabel, failedStep),
failureDetail,
failedStep: Number.isInteger(failedStep) && failedStep > 0 ? failedStep : null,
source,
+104 -12
View File
@@ -85,6 +85,95 @@
return Math.max(0, Number(summary?.attempts || 0) - 1);
}
function normalizeRecordStep(value) {
const step = Math.floor(Number(value) || 0);
return step > 0 ? step : null;
}
function extractStepFromRecordStatus(status = '') {
const match = String(status || '').trim().toLowerCase().match(/^step(\d+)_(?:failed|stopped)$/);
if (!match) {
return null;
}
return normalizeRecordStep(match[1]);
}
function getKnownStepIdsFromState(state = {}) {
const ids = new Set();
for (const key of Object.keys(state?.stepStatuses || {})) {
const step = normalizeRecordStep(key);
if (step) {
ids.add(step);
}
}
const currentStep = normalizeRecordStep(state?.currentStep);
if (currentStep) {
ids.add(currentStep);
}
return Array.from(ids).sort((left, right) => left - right);
}
function inferRecordStepFromState(state = {}, preferredStatuses = []) {
const statuses = state?.stepStatuses || {};
const preferredStatusSet = new Set(preferredStatuses.map((item) => String(item || '').trim()).filter(Boolean));
const stepIds = getKnownStepIdsFromState(state);
const currentStep = normalizeRecordStep(state?.currentStep);
if (currentStep && preferredStatusSet.has(String(statuses[currentStep] || '').trim())) {
return currentStep;
}
const matchingSteps = stepIds
.filter((step) => preferredStatusSet.has(String(statuses[step] || '').trim()))
.sort((left, right) => right - left);
if (matchingSteps.length) {
return matchingSteps[0];
}
if (currentStep) {
const currentStatus = String(statuses[currentStep] || '').trim();
if (!['', 'pending', 'completed', 'manual_completed', 'skipped'].includes(currentStatus)) {
return currentStep;
}
}
return null;
}
function inferRecordStepFromError(errorLike = null) {
if (!errorLike || typeof errorLike !== 'object') {
return null;
}
return normalizeRecordStep(errorLike.failedStep)
|| normalizeRecordStep(errorLike.step)
|| normalizeRecordStep(errorLike.currentStep);
}
function resolveAutoRunAccountRecordStatus(status, state = {}, errorLike = null) {
const normalizedStatus = String(status || '').trim().toLowerCase();
const explicitStep = extractStepFromRecordStatus(normalizedStatus);
if (explicitStep) {
return normalizedStatus;
}
if (normalizedStatus === 'failed') {
const failedStep = inferRecordStepFromError(errorLike)
|| inferRecordStepFromState(state, ['failed', 'running']);
return failedStep ? `step${failedStep}_failed` : status;
}
if (normalizedStatus === 'stopped') {
const stoppedStep = inferRecordStepFromError(errorLike)
|| inferRecordStepFromState(state, ['stopped', 'running']);
return stoppedStep ? `step${stoppedStep}_stopped` : status;
}
return status;
}
function formatAutoRunFailureReasons(reasons = []) {
if (!Array.isArray(reasons) || !reasons.length) {
return '未知错误';
@@ -456,7 +545,7 @@
forceFreshTabsNextRun = false;
}
const appendRoundRecordIfNeeded = async (status, reason = '') => {
const appendRoundRecordIfNeeded = async (status, reason = '', errorLike = null) => {
if (roundRecordAppended) {
return;
}
@@ -465,7 +554,9 @@
return;
}
const record = await appendAccountRunRecord(status, null, reason);
const recordState = await getState();
const recordStatus = resolveAutoRunAccountRecordStatus(status, recordState, errorLike);
const record = await appendAccountRunRecord(recordStatus, recordState, reason);
if (record) {
roundRecordAppended = true;
}
@@ -507,7 +598,7 @@
} catch (err) {
if (isStopError(err)) {
stoppedEarly = true;
await appendRoundRecordIfNeeded('stopped', getErrorMessage(err));
await appendRoundRecordIfNeeded('stopped', getErrorMessage(err), err);
await addLog(`${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
await broadcastAutoRunStatus('stopped', {
currentRun: targetRun,
@@ -556,7 +647,7 @@
await setState({
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
});
await appendRoundRecordIfNeeded('failed', reason);
await appendRoundRecordIfNeeded('failed', reason, err);
cancelPendingCommands('当前轮因认证流程进入 add-phone 已终止。');
await broadcastStopToContentScripts();
if (!autoRunSkipFailures) {
@@ -591,7 +682,7 @@
await setState({
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
});
await appendRoundRecordIfNeeded('failed', reason);
await appendRoundRecordIfNeeded('failed', reason, err);
cancelPendingCommands('当前轮因接码号池暂无可用号码已终止。');
await broadcastStopToContentScripts();
if (!autoRunSkipFailures) {
@@ -626,7 +717,7 @@
await setState({
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
});
await appendRoundRecordIfNeeded('failed', reason);
await appendRoundRecordIfNeeded('failed', reason, err);
cancelPendingCommands('当前轮因 Plus 免费试用资格不可用已终止。');
await broadcastStopToContentScripts();
if (!autoRunSkipFailures) {
@@ -661,7 +752,7 @@
await setState({
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
});
await appendRoundRecordIfNeeded('failed', reason);
await appendRoundRecordIfNeeded('failed', reason, err);
cancelPendingCommands('当前轮因 GPC 任务已结束。');
await broadcastStopToContentScripts();
if (!autoRunSkipFailures) {
@@ -696,7 +787,7 @@
await setState({
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
});
await appendRoundRecordIfNeeded('failed', reason);
await appendRoundRecordIfNeeded('failed', reason, err);
cancelPendingCommands('当前轮因 user_already_exists 已终止。');
await broadcastStopToContentScripts();
if (!autoRunSkipFailures) {
@@ -731,7 +822,7 @@
await setState({
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
});
await appendRoundRecordIfNeeded('failed', reason);
await appendRoundRecordIfNeeded('failed', reason, err);
cancelPendingCommands('当前轮因步骤 4 连续 405 错误已终止。');
await broadcastStopToContentScripts();
if (!autoRunSkipFailures) {
@@ -787,7 +878,7 @@
} catch (sleepError) {
if (isStopError(sleepError)) {
stoppedEarly = true;
await appendRoundRecordIfNeeded('stopped', getErrorMessage(sleepError));
await appendRoundRecordIfNeeded('stopped', getErrorMessage(sleepError), sleepError);
await addLog(`${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
await broadcastAutoRunStatus('stopped', {
currentRun: targetRun,
@@ -811,7 +902,7 @@
} catch (sleepError) {
if (isStopError(sleepError)) {
stoppedEarly = true;
await appendRoundRecordIfNeeded('stopped', getErrorMessage(sleepError));
await appendRoundRecordIfNeeded('stopped', getErrorMessage(sleepError), sleepError);
await addLog(`${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
await broadcastAutoRunStatus('stopped', {
currentRun: targetRun,
@@ -833,7 +924,7 @@
await setState({
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
});
await appendRoundRecordIfNeeded('failed', reason);
await appendRoundRecordIfNeeded('failed', reason, err);
if (!autoRunSkipFailures) {
cancelPendingCommands('当前轮执行失败。');
await broadcastStopToContentScripts();
@@ -948,6 +1039,7 @@
handleAutoRunLoopUnhandledError,
logAutoRunFinalSummary,
normalizeAutoRunRoundSummary,
resolveAutoRunAccountRecordStatus,
serializeAutoRunRoundSummaries,
skipAutoRunCountdown,
startAutoRunLoop,
+29
View File
@@ -167,6 +167,25 @@
return '';
}
function isStaleAutoRunStepMessage(step, state = {}) {
if (typeof isAutoRunLockedState !== 'function' || !isAutoRunLockedState(state)) {
return false;
}
const normalizedStep = Number(step);
if (!Number.isInteger(normalizedStep) || normalizedStep <= 0) {
return false;
}
const currentStatus = String(state?.stepStatuses?.[normalizedStep] || '').trim();
if (currentStatus === 'running') {
return false;
}
const currentStep = Number(state?.currentStep) || 0;
if (currentStep > 0 && normalizedStep !== currentStep) {
return true;
}
return ['completed', 'manual_completed', 'skipped', 'failed', 'stopped'].includes(currentStatus);
}
function resolveSignupPhonePayload(payload = {}) {
const directPhone = String(
payload?.signupPhoneNumber
@@ -540,6 +559,11 @@
}
case 'STEP_COMPLETE': {
const currentState = await getState();
if (isStaleAutoRunStepMessage(message.step, currentState)) {
await addLog(`自动运行:忽略过期的步骤 ${message.step} 完成消息,当前流程已在步骤 ${currentState.currentStep || '未知'}`, 'warn', { step: message.step });
return { ok: true, ignored: true };
}
if (getStopRequested()) {
await setStepStatus(message.step, 'stopped');
await appendManualAccountRunRecordIfNeeded(`step${message.step}_stopped`, null, '流程已被用户停止。');
@@ -582,6 +606,11 @@
}
case 'STEP_ERROR': {
const staleCheckState = await getState();
if (isStaleAutoRunStepMessage(message.step, staleCheckState)) {
await addLog(`自动运行:忽略过期的步骤 ${message.step} 失败消息,当前流程已在步骤 ${staleCheckState.currentStep || '未知'}。原始错误:${message.error || '未知错误'}`, 'warn', { step: message.step });
return { ok: true, ignored: true };
}
if (typeof isCloudflareSecurityBlockedError === 'function' && isCloudflareSecurityBlockedError(message.error)) {
const userMessage = typeof handleCloudflareSecurityBlocked === 'function'
? await handleCloudflareSecurityBlocked(message.error)
+66
View File
@@ -94,6 +94,7 @@
const PHONE_RESTART_STEP7_ERROR_PREFIX = 'PHONE_RESTART_STEP7::';
const PHONE_RESEND_THROTTLED_ERROR_PREFIX = 'PHONE_RESEND_THROTTLED::';
const PHONE_RESEND_BANNED_NUMBER_ERROR_PREFIX = 'PHONE_RESEND_BANNED_NUMBER::';
const PHONE_RESEND_SERVER_ERROR_PREFIX = 'PHONE_RESEND_SERVER_ERROR::';
const PHONE_ROUTE_405_RECOVERY_FAILED_ERROR_PREFIX = 'PHONE_ROUTE_405_RECOVERY_FAILED::';
const PHONE_MANUAL_FREE_REUSE_ERROR_PREFIX = 'PHONE_MANUAL_FREE_REUSE::';
const PHONE_AUTO_FREE_REUSE_PREPARE_ERROR_PREFIX = 'PHONE_AUTO_FREE_REUSE_PREPARE::';
@@ -1387,6 +1388,25 @@
return /无法向此电话号码发送短信|无法向此手机号发送短信|无法发送短信到此电话号码|无法发送短信到此手机号|can(?:not|'t)\s+send\s+(?:an?\s+)?(?:sms|text(?:\s+message)?)\s+to\s+(?:this|that)\s+(?:phone\s+)?number|unable\s+to\s+send\s+(?:an?\s+)?(?:sms|text(?:\s+message)?)\s+to\s+(?:this|that)\s+(?:phone\s+)?number/i.test(message);
}
function isPhoneResendServerError(error) {
const message = String(error?.message || error || '').trim();
if (!message) {
return false;
}
if (message.startsWith(PHONE_RESEND_SERVER_ERROR_PREFIX)) {
return true;
}
return /this\s+page\s+isn[']?t\s+working|currently\s+unable\s+to\s+handle\s+this\s+request|http\s+error\s+500|500\s+internal\s+server\s+error/i.test(message);
}
function buildPhoneResendServerError(error) {
const message = String(error?.message || error || '').trim();
if (message.startsWith(PHONE_RESEND_SERVER_ERROR_PREFIX)) {
return new Error(message);
}
return new Error(`${PHONE_RESEND_SERVER_ERROR_PREFIX}${message || 'OpenAI contact-verification page returned HTTP ERROR 500 after resend.'}`);
}
function shouldTreatResendThrottledAsBanned(state = {}) {
return Boolean(state?.phoneResendThrottledAsBannedEnabled);
}
@@ -4300,6 +4320,13 @@
message: error.message,
};
}
if (isPhoneResendServerError(error)) {
return {
hasError: true,
reason: 'resend_server_error',
message: error.message,
};
}
if (isPhoneMaxUsageExceededFlowError(error)) {
return {
hasError: true,
@@ -4971,6 +4998,9 @@
if (pageError?.reason === 'phone_max_usage_exceeded') {
throw buildPhoneMaxUsageExceededError(pageError.message);
}
if (pageError?.reason === 'resend_server_error') {
throw buildPhoneResendServerError(pageError.message);
}
if (pageError?.reason === 'resend_throttled') {
if (shouldTreatResendThrottledAsBanned(state)) {
throw buildHighRiskResendThrottledError(pageError.message);
@@ -5028,6 +5058,18 @@
reason: 'phone_max_usage_exceeded',
};
}
if (isPhoneResendServerError(error)) {
await addLog(
`步骤 9:重发短信后进入 contact-verification 500 页面,立即更换号码。${error.message}`,
'warn'
);
await clearPhoneRuntimeCountdown();
return {
code: '',
replaceNumber: true,
reason: 'resend_server_error',
};
}
if (isPhoneResendThrottledError(error)) {
if (shouldTreatResendThrottledAsBanned(state)) {
await addLog(
@@ -5144,6 +5186,18 @@
: 'resend_throttled',
};
}
if (isPhoneResendServerError(resendError)) {
await addLog(
`步骤 9:重发短信后进入 contact-verification 500 页面,立即更换号码。${resendError.message}`,
'warn'
);
await clearPhoneRuntimeCountdown();
return {
code: '',
replaceNumber: true,
reason: 'resend_server_error',
};
}
await addLog(`步骤 9:点击手机验证码页面重发按钮失败。${resendError.message}`, 'warn');
}
continue;
@@ -5382,6 +5436,9 @@
if (isStopRequestedError(resendError)) {
throw resendError;
}
if (isPhoneResendServerError(resendError)) {
throw buildPhoneResendServerError(resendError);
}
await addLog(`步骤 4:注册手机验证码页面重发失败,将继续轮询短信。${resendError.message}`, 'warn', {
step: 4,
stepKey: 'fetch-signup-code',
@@ -5419,6 +5476,9 @@
if (isStopRequestedError(resendError)) {
throw resendError;
}
if (isPhoneResendServerError(resendError)) {
throw buildPhoneResendServerError(resendError);
}
await addLog(`步骤 4:验证码被拒后点击重发失败。${resendError.message}`, 'warn', {
step: 4,
stepKey: 'fetch-signup-code',
@@ -5621,6 +5681,9 @@
if (isStopRequestedError(resendError)) {
throw resendError;
}
if (isPhoneResendServerError(resendError)) {
throw buildPhoneResendServerError(resendError);
}
await addLog(`步骤 ${visibleStep}:登录手机验证码页面重发失败,将继续轮询短信。${resendError.message}`, 'warn', {
step: visibleStep,
stepKey: 'fetch-login-code',
@@ -5656,6 +5719,9 @@
if (isStopRequestedError(resendError)) {
throw resendError;
}
if (isPhoneResendServerError(resendError)) {
throw buildPhoneResendServerError(resendError);
}
await addLog(`步骤 ${visibleStep}:登录手机验证码被拒后点击重发失败。${resendError.message}`, 'warn', {
step: visibleStep,
stepKey: 'fetch-login-code',
+24 -1
View File
@@ -30,12 +30,35 @@
waitForTabUrlMatch,
} = deps;
async function waitForSignupEntryTabToSettle(tabId, step = 1) {
if (step !== 2 || !Number.isInteger(tabId) || typeof waitForTabStableComplete !== 'function') {
return null;
}
if (typeof addLog === 'function') {
await addLog(
`步骤 ${step}:注册页已打开,正在等待页面加载完成并额外稳定 3 秒...`,
'info',
{ step, stepKey: 'signup-entry' }
);
}
return waitForTabStableComplete(tabId, {
timeoutMs: 45000,
retryDelayMs: 300,
stableMs: 3000,
initialDelayMs: 300,
});
}
async function openSignupEntryTab(step = 1) {
const tabId = await reuseOrCreateTab('signup-page', SIGNUP_ENTRY_URL, {
inject: SIGNUP_PAGE_INJECT_FILES,
injectSource: 'signup-page',
});
await waitForSignupEntryTabToSettle(tabId, step);
await ensureContentScriptReadyOnTab('signup-page', tabId, {
inject: SIGNUP_PAGE_INJECT_FILES,
injectSource: 'signup-page',
@@ -85,7 +108,7 @@
function fallbackSignupProfilePageUrl(rawUrl) {
const parsed = parseUrlSafely(rawUrl);
if (!parsed) return false;
return /\/create-account\/profile(?:[/?#]|$)/i.test(parsed.pathname || '');
return /\/(?:create-account\/profile|u\/signup\/profile|signup\/profile)(?:[/?#]|$)/i.test(parsed.pathname || '');
}
function resolveSignupPostIdentityState(rawUrl) {
+37
View File
@@ -33,6 +33,7 @@
setWebNavCommittedListener,
setStep8PendingReject,
setStep8TabUpdatedListener,
shouldDeferStep9CallbackTimeout,
} = deps;
const LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS = 240000;
@@ -96,6 +97,7 @@
let signupTabId = null;
const callbackWaitStartedAt = Date.now();
let timeoutCheckTimer = null;
let timeoutDeferredLogged = false;
const cleanupListener = () => {
if (timeoutCheckTimer) {
@@ -128,11 +130,46 @@
});
};
const isCallbackTimeoutDeferred = async (elapsedMs) => {
if (typeof shouldDeferStep9CallbackTimeout !== 'function') {
return false;
}
try {
const deferred = await shouldDeferStep9CallbackTimeout({
tabId: signupTabId,
visibleStep,
elapsedMs,
oauthUrl: activeState?.oauthUrl || '',
});
if (deferred && !timeoutDeferredLogged) {
timeoutDeferredLogged = true;
await addStepLog(
visibleStep,
'检测到认证页仍在安全验证/授权跳转中,暂停本地回调超时判定,继续等待 localhost 回调...',
'info'
);
}
return Boolean(deferred);
} catch (error) {
await addStepLog(
visibleStep,
`复核认证页跳转状态失败(${error?.message || error}),继续按原超时规则等待回调。`,
'warn'
);
return false;
}
};
const checkCallbackTimeout = async () => {
if (resolved) {
return;
}
const elapsedMs = Date.now() - callbackWaitStartedAt;
if (await isCallbackTimeoutDeferred(elapsedMs)) {
timeoutCheckTimer = setTimeout(checkCallbackTimeout, CALLBACK_TIMEOUT_CHECK_INTERVAL_MS);
return;
}
if (elapsedMs >= LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS) {
rejectStep9(new Error(`${Math.round(LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS / 1000)} 秒内未捕获到 localhost 回调跳转,步骤 ${visibleStep} 的点击可能被拦截了。`));
return;
+116 -12
View File
@@ -8,6 +8,8 @@
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top';
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
function createPlusCheckoutCreateExecutor(deps = {}) {
const {
@@ -86,6 +88,17 @@
return cleaned;
}
function normalizeGpcHelperPhoneMode(value = '') {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
if (rootScope.GoPayUtils?.normalizeGpcHelperPhoneMode) {
return rootScope.GoPayUtils.normalizeGpcHelperPhoneMode(value);
}
const normalized = String(value || '').trim().toLowerCase();
return normalized === GPC_HELPER_PHONE_MODE_AUTO || normalized === 'builtin'
? GPC_HELPER_PHONE_MODE_AUTO
: GPC_HELPER_PHONE_MODE_MANUAL;
}
function normalizeGpcOtpChannel(value = '') {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
if (rootScope.GoPayUtils?.normalizeGpcOtpChannel) {
@@ -143,6 +156,17 @@
return buildGpcHelperApiUrl(apiUrl, '/api/gp/tasks');
}
function buildGpcBalanceUrl(apiUrl = '') {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
if (rootScope.GoPayUtils?.buildGpcApiKeyBalanceUrl) {
return rootScope.GoPayUtils.buildGpcApiKeyBalanceUrl(apiUrl);
}
if (rootScope.GoPayUtils?.buildGpcCardBalanceUrl) {
return rootScope.GoPayUtils.buildGpcCardBalanceUrl(apiUrl);
}
return buildGpcHelperApiUrl(apiUrl, '/api/gp/balance');
}
function unwrapGpcResponse(payload = {}) {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
if (rootScope.GoPayUtils?.unwrapGpcResponse) {
@@ -176,6 +200,55 @@
return payload?.data?.detail || payload?.detail || payload?.message || payload?.error || `HTTP ${status || 0}`;
}
function getGpcRemainingUses(payload = {}) {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
if (rootScope.GoPayUtils?.getGpcBalanceRemainingUses) {
return rootScope.GoPayUtils.getGpcBalanceRemainingUses(payload);
}
const data = unwrapGpcResponse(payload);
const numeric = Number(data?.remaining_uses ?? data?.remainingUses ?? data?.balance ?? data?.remaining);
return Number.isFinite(numeric) ? Math.max(0, Math.floor(numeric)) : null;
}
function isGpcAutoModeEnabled(payload = {}) {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
if (rootScope.GoPayUtils?.isGpcAutoModeEnabled) {
return rootScope.GoPayUtils.isGpcAutoModeEnabled(payload);
}
const data = unwrapGpcResponse(payload);
return data?.auto_mode_enabled === true || data?.autoModeEnabled === true;
}
async function assertGpcApiKeyReadyForCreate(state = {}, phoneMode = GPC_HELPER_PHONE_MODE_MANUAL, apiKey = '') {
const apiUrl = buildGpcBalanceUrl(state?.gopayHelperApiUrl);
if (!apiUrl) {
throw new Error('创建 GPC 订单失败:缺少 API 地址。');
}
const { response, data } = await fetchJsonWithTimeout(apiUrl, {
method: 'GET',
headers: {
Accept: 'application/json',
'X-API-Key': apiKey,
},
}, 30000);
if (!response?.ok || !isGpcUnifiedResponseOk(data)) {
const detail = getGpcResponseErrorDetail(data, response?.status || 0);
throw new Error(`创建 GPC 订单失败:API Key 校验失败:${detail}`);
}
const balanceData = unwrapGpcResponse(data);
const remainingUses = getGpcRemainingUses(balanceData);
const status = String(balanceData?.status || balanceData?.card_status || balanceData?.cardStatus || '').trim().toLowerCase();
if (status && status !== 'active') {
throw new Error(`创建 GPC 订单失败:API Key 状态不可用(${status})。`);
}
if (remainingUses !== null && remainingUses <= 0) {
throw new Error('创建 GPC 订单失败:API Key 剩余次数不足。');
}
if (phoneMode === GPC_HELPER_PHONE_MODE_AUTO && !isGpcAutoModeEnabled(balanceData)) {
throw new Error('创建 GPC 订单失败:当前 GPC API Key 未开通自动模式。');
}
}
async function fetchJsonWithTimeout(url, options = {}, timeoutMs = 30000) {
const fetcher = typeof fetchImpl === 'function'
? fetchImpl
@@ -184,11 +257,34 @@
throw new Error('当前运行环境不支持 fetch,无法调用 GPC API。');
}
const controller = typeof AbortController === 'function' ? new AbortController() : null;
const timer = controller ? setTimeout(() => controller.abort(), Math.max(1000, Number(timeoutMs) || 30000)) : null;
const effectiveTimeoutMs = Math.max(1000, Number(timeoutMs) || 30000);
let didTimeout = false;
let timer = null;
const buildTimeoutError = () => new Error(`GPC API 请求超时(>${Math.round(effectiveTimeoutMs / 1000)} 秒):${url}`);
const timeoutPromise = new Promise((_, reject) => {
timer = setTimeout(() => {
didTimeout = true;
reject(buildTimeoutError());
if (controller) {
controller.abort();
}
}, effectiveTimeoutMs);
});
try {
const response = await fetcher(url, { ...options, ...(controller ? { signal: controller.signal } : {}) });
const data = await response.json().catch(() => ({}));
const response = await Promise.race([
fetcher(url, { ...options, ...(controller ? { signal: controller.signal } : {}) }),
timeoutPromise,
]);
const data = await Promise.race([
response.json().catch(() => ({})),
timeoutPromise,
]);
return { response, data };
} catch (error) {
if (didTimeout || error?.name === 'AbortError') {
throw buildTimeoutError();
}
throw error;
} finally {
if (timer) clearTimeout(timer);
}
@@ -226,25 +322,31 @@
if (!apiUrl) {
throw new Error('创建 GPC 订单失败:缺少 API 地址。');
}
const phoneMode = normalizeGpcHelperPhoneMode(state?.gopayHelperPhoneMode || state?.phoneMode);
const isAutoMode = phoneMode === GPC_HELPER_PHONE_MODE_AUTO;
const phoneNumber = String(state?.gopayHelperPhoneNumber || '').trim();
const countryCode = normalizeHelperCountryCode(state?.gopayHelperCountryCode || '86');
const pin = String(state?.gopayHelperPin || '').trim();
const apiKey = resolveGpcHelperApiKey(state);
if (!phoneNumber) {
throw new Error('创建 GPC 订单失败:缺少手机号。');
if (!isAutoMode && !phoneNumber) {
throw new Error('创建 GPC 订单失败:手动模式缺少手机号。');
}
if (!pin) {
throw new Error('创建 GPC 订单失败:缺少 PIN。');
if (!isAutoMode && !pin) {
throw new Error('创建 GPC 订单失败:手动模式缺少 PIN。');
}
throwIfStopped();
await assertGpcApiKeyReadyForCreate(state, phoneMode, apiKey);
throwIfStopped();
const payload = {
access_token: token,
phone_mode: 'manual',
country_code: countryCode,
phone_number: normalizeHelperPhoneNumber(phoneNumber, countryCode),
otp_channel: normalizeGpcOtpChannel(state?.gopayHelperOtpChannel),
phone_mode: phoneMode,
};
if (!isAutoMode) {
payload.country_code = countryCode;
payload.phone_number = normalizeHelperPhoneNumber(phoneNumber, countryCode);
payload.otp_channel = normalizeGpcOtpChannel(state?.gopayHelperOtpChannel);
}
const orderCreatedAt = Date.now();
const { response, data } = await fetchJsonWithTimeout(apiUrl, {
@@ -273,6 +375,7 @@
remoteStage: String(taskData?.remote_stage || taskData?.remoteStage || '').trim(),
orderCreatedAt,
responsePayload: taskData && typeof taskData === 'object' && !Array.isArray(taskData) ? taskData : null,
phoneMode: normalizeGpcHelperPhoneMode(taskData?.phone_mode || taskData?.phoneMode || phoneMode),
country: 'ID',
currency: 'IDR',
checkoutSource: PLUS_PAYMENT_METHOD_GPC_HELPER,
@@ -308,6 +411,7 @@
gopayHelperTaskStatus: result.taskStatus,
gopayHelperStatusText: result.statusText,
gopayHelperRemoteStage: result.remoteStage,
gopayHelperPhoneMode: result.phoneMode || normalizeGpcHelperPhoneMode(state?.gopayHelperPhoneMode || state?.phoneMode),
gopayHelperTaskPayload: result.responsePayload,
gopayHelperReferenceId: '',
gopayHelperGoPayGuid: '',
@@ -318,7 +422,7 @@
gopayHelperStartPayload: null,
gopayHelperOrderCreatedAt: result.orderCreatedAt || Date.now(),
});
await addLog(`步骤 6GPC 任务已创建(task_id: ${result.taskId}),准备继续下一步。`, 'info');
await addLog(`步骤 6GPC ${result.phoneMode === GPC_HELPER_PHONE_MODE_AUTO ? '自动' : '手动'}模式任务已创建(task_id: ${result.taskId}),准备继续下一步。`, 'info');
await completeStepFromBackground(6, {
plusCheckoutCountry: result.country || 'ID',
plusCheckoutCurrency: result.currency || 'IDR',
+174 -17
View File
@@ -11,7 +11,28 @@
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
const DEFAULT_GPC_HELPER_API_URL = 'https://gpc.qlhazycoder.top';
const GPC_HELPER_PHONE_MODE_AUTO = 'auto';
const GPC_HELPER_PHONE_MODE_MANUAL = 'manual';
const GPC_TASK_POLL_INTERVAL_MS = 3000;
const GPC_TASK_STALE_STATUS_TIMEOUT_MS = 60000;
const GPC_REMOTE_STAGE_LABELS = {
auto_otp_wait: '等待自动 OTP',
checkout_order_start: '创建订单',
checkout_start: '创建订单',
completed: '充值完成',
gopay_validate_pin: '校验 PIN',
otp_ready: '等待 PIN',
otp_submitted_local: 'OTP 已提交',
payment_processing: '支付处理中',
pin_submitted_local: 'PIN 已提交',
sms_otp_wait: '等待短信 OTP',
whatsapp_otp_wait: '等待 WhatsApp OTP',
};
const GPC_WAITING_FOR_LABELS = {
auto_otp: '自动 OTP',
otp: 'OTP',
pin: 'PIN',
};
const PAYMENT_METHOD_CONFIGS = {
[PLUS_PAYMENT_METHOD_PAYPAL]: {
id: PLUS_PAYMENT_METHOD_PAYPAL,
@@ -112,6 +133,56 @@
return normalized === PLUS_PAYMENT_METHOD_GOPAY ? PLUS_PAYMENT_METHOD_GOPAY : PLUS_PAYMENT_METHOD_PAYPAL;
}
function normalizeGpcHelperPhoneMode(value = '') {
const rootScope = typeof self !== 'undefined' ? self : globalThis;
if (rootScope.GoPayUtils?.normalizeGpcHelperPhoneMode) {
return rootScope.GoPayUtils.normalizeGpcHelperPhoneMode(value);
}
const normalized = String(value || '').trim().toLowerCase();
return normalized === GPC_HELPER_PHONE_MODE_AUTO || normalized === 'builtin'
? GPC_HELPER_PHONE_MODE_AUTO
: GPC_HELPER_PHONE_MODE_MANUAL;
}
function formatGpcRemoteStageLabel(stage = '') {
const normalized = String(stage || '').trim().toLowerCase();
if (!normalized) {
return '';
}
return GPC_REMOTE_STAGE_LABELS[normalized] || normalized;
}
function formatGpcWaitingForLabel(waitingFor = '') {
const normalized = String(waitingFor || '').trim().toLowerCase();
if (!normalized) {
return '';
}
return GPC_WAITING_FOR_LABELS[normalized] || normalized.toUpperCase();
}
function formatGpcTaskStatusLog(task = {}) {
const statusText = String(task?.status_text || task?.statusText || '').trim();
const status = String(task?.status || '').trim();
const remoteStage = String(task?.remote_stage || task?.remoteStage || '').trim();
const stageText = formatGpcRemoteStageLabel(remoteStage);
const waitingForText = formatGpcWaitingForLabel(task?.api_waiting_for || task?.apiWaitingFor || '');
const mainText = stageText || statusText || status || '处理中';
const parts = [`步骤 7GPC 任务状态:${mainText}`];
if (waitingForText && !mainText.includes(waitingForText)) {
parts.push(`,等待 ${waitingForText}`);
}
return parts.join('');
}
function getGpcHelperPhoneMode(state = {}, task = null) {
return normalizeGpcHelperPhoneMode(
task?.phone_mode
|| task?.phoneMode
|| state?.gopayHelperPhoneMode
|| state?.phoneMode
);
}
function getPaymentMethodConfig(method = PLUS_PAYMENT_METHOD_PAYPAL) {
return PAYMENT_METHOD_CONFIGS[normalizePlusPaymentMethod(method)] || PAYMENT_METHOD_CONFIGS[PLUS_PAYMENT_METHOD_PAYPAL];
}
@@ -139,11 +210,34 @@
throw new Error('当前运行环境不支持 fetch,无法调用 GPC API。');
}
const controller = typeof AbortController === 'function' ? new AbortController() : null;
const timer = controller ? setTimeout(() => controller.abort(), Math.max(1000, Number(timeoutMs) || 30000)) : null;
const effectiveTimeoutMs = Math.max(1000, Number(timeoutMs) || 30000);
let didTimeout = false;
let timer = null;
const buildTimeoutError = () => new Error(`GPC API 请求超时(>${Math.round(effectiveTimeoutMs / 1000)} 秒):${url}`);
const timeoutPromise = new Promise((_, reject) => {
timer = setTimeout(() => {
didTimeout = true;
reject(buildTimeoutError());
if (controller) {
controller.abort();
}
}, effectiveTimeoutMs);
});
try {
const response = await fetcher(url, { ...options, ...(controller ? { signal: controller.signal } : {}) });
const data = await response.json().catch(() => ({}));
const response = await Promise.race([
fetcher(url, { ...options, ...(controller ? { signal: controller.signal } : {}) }),
timeoutPromise,
]);
const data = await Promise.race([
response.json().catch(() => ({})),
timeoutPromise,
]);
return { response, data };
} catch (error) {
if (didTimeout || error?.name === 'AbortError') {
throw buildTimeoutError();
}
throw error;
} finally {
if (timer) clearTimeout(timer);
}
@@ -299,7 +393,7 @@
task.task_id = String(task.task_id || task.taskId || '').trim();
task.status = String(task.status || '').trim().toLowerCase();
task.status_text = String(task.status_text || task.statusText || '').trim();
task.phone_mode = String(task.phone_mode || task.phoneMode || '').trim().toLowerCase();
task.phone_mode = normalizeGpcHelperPhoneMode(task.phone_mode || task.phoneMode || '');
task.remote_stage = String(task.remote_stage || task.remoteStage || '').trim().toLowerCase();
task.api_waiting_for = String(task.api_waiting_for || task.apiWaitingFor || '').trim().toLowerCase();
task.api_input_deadline_at = String(task.api_input_deadline_at || task.apiInputDeadlineAt || '').trim();
@@ -318,6 +412,7 @@
gopayHelperTaskId: task.task_id,
gopayHelperTaskStatus: task.status,
gopayHelperStatusText: task.status_text,
gopayHelperPhoneMode: task.phone_mode,
gopayHelperRemoteStage: task.remote_stage,
gopayHelperApiWaitingFor: task.api_waiting_for,
gopayHelperApiInputDeadlineAt: task.api_input_deadline_at,
@@ -675,12 +770,16 @@
});
}
function isGpcTaskOtpWait(task = {}) {
return task?.phone_mode === 'manual' && task?.api_waiting_for === 'otp';
function isGpcTaskManualMode(task = {}, state = {}) {
return getGpcHelperPhoneMode(state, task) === GPC_HELPER_PHONE_MODE_MANUAL;
}
function isGpcTaskPinWait(task = {}) {
return task?.phone_mode === 'manual'
function isGpcTaskOtpWait(task = {}, state = {}) {
return isGpcTaskManualMode(task, state) && task?.api_waiting_for === 'otp';
}
function isGpcTaskPinWait(task = {}, state = {}) {
return isGpcTaskManualMode(task, state)
&& (task?.api_waiting_for === 'pin' || task?.status === 'otp_ready');
}
@@ -688,6 +787,49 @@
return ['completed', 'failed', 'expired', 'discarded'].includes(String(status || '').trim().toLowerCase());
}
function buildGpcTaskProgressSignature(task = {}) {
return [
task?.status,
task?.status_text,
task?.remote_stage,
task?.api_waiting_for,
task?.last_input_error,
task?.otp_invalid_count,
task?.reference_id || task?.referenceId,
task?.redirect_url || task?.redirectUrl,
task?.flow_id || task?.flowId,
task?.challenge_id || task?.challengeId,
task?.gopay_guid || task?.gopayGuid,
].map((value) => String(value ?? '').trim()).join('|');
}
function shouldWatchGpcTaskProgress(task = {}, state = {}) {
if (!task || isGpcTaskTerminal(task.status)) {
return false;
}
if (isGpcTaskOtpWait(task, state) || isGpcTaskPinWait(task, state)) {
return false;
}
return true;
}
function getGpcTaskStaleStatusTimeoutMs(state = {}) {
const configuredSeconds = Number(state?.gopayHelperTaskStaleSeconds);
if (Number.isFinite(configuredSeconds) && configuredSeconds > 0) {
return Math.max(15000, Math.min(600000, Math.floor(configuredSeconds * 1000)));
}
return GPC_TASK_STALE_STATUS_TIMEOUT_MS;
}
function buildGpcTaskStaleStatusError(task = {}, staleTimeoutMs = GPC_TASK_STALE_STATUS_TIMEOUT_MS) {
const seconds = Math.max(1, Math.round(staleTimeoutMs / 1000));
const label = formatGpcRemoteStageLabel(task?.remote_stage)
|| task?.status_text
|| task?.status
|| '未知状态';
return new Error(`GPC_TASK_ENDED::GPC 任务状态超过 ${seconds} 秒无进展(${label}),请重新创建任务。`);
}
function buildGpcTaskTerminalError(task = {}) {
const status = String(task?.status || '').trim().toLowerCase();
const remoteStage = String(task?.remote_stage || task?.remoteStage || '').trim();
@@ -829,6 +971,9 @@
let lastSubmittedOtp = '';
let pinSubmitted = false;
let terminalReached = false;
let lastProgressSignature = '';
let lastProgressAt = Date.now();
const staleStatusTimeoutMs = getGpcTaskStaleStatusTimeoutMs(state);
if (!taskId) {
throw new Error('步骤 7GPC 模式缺少 task_id,请先执行步骤 6。');
@@ -840,27 +985,25 @@
throw new Error('步骤 7GPC 模式缺少 API Key。');
}
const configuredPhoneMode = normalizeGpcHelperPhoneMode(state?.gopayHelperPhoneMode || state?.phoneMode || GPC_HELPER_PHONE_MODE_MANUAL);
const rawPin = String(state?.gopayHelperPin || '').trim();
const pinDigits = rawPin.replace(/[^\d]/g, '');
const pin = normalizeSixDigitPin(rawPin);
if (!pin) {
if (configuredPhoneMode === GPC_HELPER_PHONE_MODE_MANUAL && !pin) {
if (taskId && apiUrl && apiKey) {
await stopGpcTaskBestEffort(apiUrl, taskId, apiKey, 'PIN 配置错误');
}
throw new Error(pinDigits
? '步骤 7GPC PIN 必须是 6 位数字,请检查侧边栏配置。'
: '步骤 7GPC 模式缺少 PIN 配置。');
: '步骤 7GPC 手动模式缺少 PIN 配置。');
}
await addLog(`步骤 7:GPC 模式开始轮询任务(task_id: ${taskId}...`, 'info');
await addLog(`步骤 7GPC ${configuredPhoneMode === GPC_HELPER_PHONE_MODE_AUTO ? '自动' : '手动'}模式开始轮询任务(task_id: ${taskId}...`, 'info');
try {
while (Date.now() <= deadline) {
throwIfStopped();
const task = await fetchGpcTaskStatus(apiUrl, taskId, apiKey);
const statusText = task?.status_text || task?.status || '处理中';
const remoteStage = task?.remote_stage || '';
const waitingFor = task?.api_waiting_for || '';
await addLog(`步骤 7GPC 任务状态:${statusText}${remoteStage ? `${remoteStage}` : ''}${waitingFor ? `,等待 ${waitingFor.toUpperCase()}` : ''}`, 'info');
await addLog(formatGpcTaskStatusLog(task), 'info');
if (task.status === 'completed') {
terminalReached = true;
@@ -879,7 +1022,21 @@
throw buildGpcTaskEndedError(task, 'GPC 任务已结束,请重新创建任务。');
}
if (isGpcTaskOtpWait(task)) {
if (shouldWatchGpcTaskProgress(task, state)) {
const progressSignature = buildGpcTaskProgressSignature(task);
const now = Date.now();
if (progressSignature && progressSignature !== lastProgressSignature) {
lastProgressSignature = progressSignature;
lastProgressAt = now;
} else if (progressSignature && now - lastProgressAt >= staleStatusTimeoutMs) {
throw buildGpcTaskStaleStatusError(task, staleStatusTimeoutMs);
}
} else {
lastProgressSignature = '';
lastProgressAt = Date.now();
}
if (isGpcTaskOtpWait(task, state)) {
if (isGpcTaskInputDeadlineExpired(task)) {
throw buildGpcInputDeadlineError(task, 'OTP');
}
@@ -940,7 +1097,7 @@
otpLastSubmittedAt = Date.now();
lastSubmittedOtp = normalizedOtp;
await addLog('步骤 7:OTP 已提交,继续等待 GPC 任务状态更新。', 'ok');
} else if (isGpcTaskPinWait(task) && !pinSubmitted) {
} else if (isGpcTaskPinWait(task, state) && !pinSubmitted) {
if (isGpcTaskInputDeadlineExpired(task)) {
throw buildGpcInputDeadlineError(task, 'PIN');
}
+23
View File
@@ -58,7 +58,30 @@
&& !Boolean(state?.contributionMode);
}
function hasStep7PhoneSignupIdentity(state = {}) {
return Boolean(
String(state?.signupPhoneNumber || '').trim()
|| String(state?.signupPhoneCompletedActivation?.phoneNumber || '').trim()
|| String(state?.signupPhoneActivation?.phoneNumber || '').trim()
|| (
normalizeStep7IdentifierType(state?.accountIdentifierType) === 'phone'
&& String(state?.accountIdentifier || '').trim()
)
);
}
function shouldPreferStep7PhoneSignupIdentity(state = {}) {
const frozenSignupMethod = normalizeStep7IdentifierType(state?.resolvedSignupMethod);
return canUseConfiguredPhoneSignup(state)
&& frozenSignupMethod !== 'email'
&& hasStep7PhoneSignupIdentity(state);
}
function resolveStep7LoginIdentifierType(state = {}, fallbackType = '') {
if (shouldPreferStep7PhoneSignupIdentity(state)) {
return 'phone';
}
const explicitIdentifierType = normalizeStep7IdentifierType(state?.accountIdentifierType);
if (explicitIdentifierType) {
return explicitIdentifierType;
+24
View File
@@ -18,6 +18,7 @@
resolveSignupEmailForFlow,
sendToContentScriptResilient,
SIGNUP_PAGE_INJECT_FILES,
waitForTabStableComplete = null,
} = deps;
function getErrorMessage(error) {
@@ -165,6 +166,25 @@
}
}
async function waitForStep2SignupTabToSettle(tabId, logMessage) {
if (!Number.isInteger(tabId) || typeof waitForTabStableComplete !== 'function') {
return null;
}
await addLog(
logMessage || '步骤 2:注册页标签已切换,正在等待页面加载完成并额外稳定 3 秒...',
'info',
{ step: 2, stepKey: 'signup-entry' }
);
return waitForTabStableComplete(tabId, {
timeoutMs: 45000,
retryDelayMs: 300,
stableMs: 3000,
initialDelayMs: 300,
});
}
async function ensureSignupPhoneEntryReady(tabId) {
if (!Number.isInteger(tabId)) {
throw new Error('步骤 2:未找到可用的注册页标签,无法切换到手机号注册入口。');
@@ -210,6 +230,10 @@
signupTabId = (await ensureSignupEntryPageReady(2)).tabId;
} else {
await chrome.tabs.update(signupTabId, { active: true });
await waitForStep2SignupTabToSettle(
signupTabId,
'步骤 2:已切换到注册页标签,正在等待页面加载完成并额外稳定 3 秒...'
);
await ensureContentScriptReadyOnTab('signup-page', signupTabId, {
inject: SIGNUP_PAGE_INJECT_FILES,
injectSource: 'signup-page',
+64 -15
View File
@@ -159,7 +159,7 @@
if (!['auth.openai.com', 'auth0.openai.com', 'accounts.openai.com'].includes(host)) {
return false;
}
return /\/create-account\/profile(?:[/?#]|$)/i.test(String(parsed.pathname || ''));
return /\/(?:create-account\/profile|u\/signup\/profile|signup\/profile)(?:[/?#]|$)/i.test(String(parsed.pathname || ''));
} catch {
return false;
}
@@ -249,11 +249,21 @@
const authState = String(result?.state || '').trim();
const authUrl = String(result?.url || '').trim();
const verificationErrorText = String(result?.verificationErrorText || '').trim();
lastSnapshot = {
state: authState || 'unknown',
url: authUrl,
};
if (authState === 'verification_page' && verificationErrorText) {
return {
success: false,
reason: 'invalid_code',
invalidCode: true,
errorText: verificationErrorText,
url: authUrl,
};
}
if (authState === 'oauth_consent_page') {
return {
success: true,
@@ -1080,7 +1090,8 @@
},
};
let result;
if (typeof sendToContentScriptResilient === 'function') {
const shouldAvoidReplaySubmit = step === 8;
if (typeof sendToContentScriptResilient === 'function' && !shouldAvoidReplaySubmit) {
try {
result = await sendToContentScriptResilient('signup-page', message, {
timeoutMs: Math.max(baseResponseTimeoutMs + 15000, 30000),
@@ -1143,6 +1154,56 @@
}
throw err;
}
} else if (shouldAvoidReplaySubmit) {
try {
result = await sendToContentScript('signup-page', message, {
responseTimeoutMs: baseResponseTimeoutMs,
});
} catch (err) {
if (isRetryableVerificationTransportError(err)) {
await addLog('认证页正在切换,等待页面重新就绪后继续确认验证码提交结果...', 'warn', {
step: completionStep,
stepKey: 'fetch-login-code',
});
const fallback = await detectStep8PostSubmitFallback({
step,
timeoutMs: 9000,
pollIntervalMs: 300,
});
if (fallback.invalidCode) {
return {
invalidCode: true,
errorText: fallback.errorText || '验证码被拒绝。',
url: fallback.url || '',
};
}
if (fallback.success) {
if (fallback.addPhonePage) {
await addLog('验证码提交后通信中断,但页面已进入手机号验证页,按提交成功继续。', 'warn', {
step: completionStep,
stepKey: 'fetch-login-code',
});
} else {
await addLog('验证码提交后通信中断,但页面已进入 OAuth 授权页,按提交成功继续。', 'warn', {
step: completionStep,
stepKey: 'fetch-login-code',
});
}
return {
success: true,
assumed: true,
transportRecovered: true,
addPhonePage: Boolean(fallback.addPhonePage),
url: fallback.url || '',
};
}
if (fallback.restartStep7) {
const urlPart = fallback.url ? ` URL: ${fallback.url}` : '';
throw new Error(`STEP8_RESTART_STEP7::步骤 ${completionStep}:验证码提交后认证页进入登录超时报错页,请回到步骤 ${authLoginStep} 重新开始。${urlPart}`.trim());
}
}
throw err;
}
} else {
result = await sendToContentScript('signup-page', message, {
responseTimeoutMs: baseResponseTimeoutMs,
@@ -1287,20 +1348,8 @@
continue;
}
const remainingBeforeResendMs = resendIntervalMs > 0 && lastResendAt > 0
? Math.max(0, resendIntervalMs - (Date.now() - lastResendAt))
: 0;
if (remainingBeforeResendMs > 0) {
await addLog(
`步骤 ${step}:提交失败后距离下次重新发送验证码还差 ${Math.ceil(remainingBeforeResendMs / 1000)} 秒,先继续刷新邮箱(${attempt + 1}/${maxSubmitAttempts}...`,
'warn'
);
await sleepWithStop(Math.min(remainingBeforeResendMs, 2000));
continue;
}
if (remainingAutomaticResendCount <= 0) {
await addLog(`步骤 ${step}:已达到自动重新发送验证码次数上限,将直接使用当前时间窗口继续重试`, 'warn');
await addLog(`步骤 ${step}:已达到自动重新发送验证码次数上限,将排除已拒绝验证码并继续轮询新邮件`, 'warn');
continue;
}