拆分 OAuth 后置验证流程
This commit is contained in:
+53
-31
@@ -9596,6 +9596,9 @@ const AUTO_RUN_BACKGROUND_COMPLETED_STEP_KEYS = new Set([
|
|||||||
'plus-checkout-return',
|
'plus-checkout-return',
|
||||||
'oauth-login',
|
'oauth-login',
|
||||||
'fetch-login-code',
|
'fetch-login-code',
|
||||||
|
'post-login-phone-verification',
|
||||||
|
'bind-email',
|
||||||
|
'fetch-bind-email-code',
|
||||||
'confirm-oauth',
|
'confirm-oauth',
|
||||||
]);
|
]);
|
||||||
const STEP_COMPLETION_SIGNAL_STEP_KEYS = new Set([
|
const STEP_COMPLETION_SIGNAL_STEP_KEYS = new Set([
|
||||||
@@ -10047,6 +10050,9 @@ async function waitForRunningStepsToFinish(payload = {}) {
|
|||||||
const AUTH_CHAIN_NODE_IDS = new Set([
|
const AUTH_CHAIN_NODE_IDS = new Set([
|
||||||
'oauth-login',
|
'oauth-login',
|
||||||
'fetch-login-code',
|
'fetch-login-code',
|
||||||
|
'post-login-phone-verification',
|
||||||
|
'bind-email',
|
||||||
|
'fetch-bind-email-code',
|
||||||
'confirm-oauth',
|
'confirm-oauth',
|
||||||
'platform-verify',
|
'platform-verify',
|
||||||
]);
|
]);
|
||||||
@@ -12243,7 +12249,6 @@ const step8Executor = self.MultiPageBackgroundStep8?.createStep8Executor({
|
|||||||
resolveSignupMethod,
|
resolveSignupMethod,
|
||||||
reuseOrCreateTab,
|
reuseOrCreateTab,
|
||||||
sendToContentScriptResilient,
|
sendToContentScriptResilient,
|
||||||
buildRegistrationEmailStateUpdates,
|
|
||||||
setState,
|
setState,
|
||||||
shouldUseCustomRegistrationEmail,
|
shouldUseCustomRegistrationEmail,
|
||||||
sleepWithStop,
|
sleepWithStop,
|
||||||
@@ -12376,6 +12381,9 @@ const stepExecutorsByKey = {
|
|||||||
'plus-checkout-return': (state) => plusReturnConfirmExecutor.executePlusReturnConfirm(state),
|
'plus-checkout-return': (state) => plusReturnConfirmExecutor.executePlusReturnConfirm(state),
|
||||||
'oauth-login': (state) => step7Executor.executeStep7(state),
|
'oauth-login': (state) => step7Executor.executeStep7(state),
|
||||||
'fetch-login-code': (state) => step8Executor.executeStep8(state),
|
'fetch-login-code': (state) => step8Executor.executeStep8(state),
|
||||||
|
'post-login-phone-verification': (state) => step8Executor.executePostLoginPhoneVerification(state),
|
||||||
|
'bind-email': (state) => step8Executor.executeBindEmail(state),
|
||||||
|
'fetch-bind-email-code': (state) => step8Executor.executeFetchBindEmailCode(state),
|
||||||
'confirm-oauth': (state) => step9Executor.executeStep9(state),
|
'confirm-oauth': (state) => step9Executor.executeStep9(state),
|
||||||
'platform-verify': (state) => executeStep10(state),
|
'platform-verify': (state) => executeStep10(state),
|
||||||
};
|
};
|
||||||
@@ -13610,22 +13618,12 @@ async function waitForStep8Ready(tabId, timeoutMs = STEP8_READY_WAIT_TIMEOUT_MS,
|
|||||||
throw new Error(`${CLOUDFLARE_SECURITY_BLOCK_ERROR_PREFIX}${CLOUDFLARE_SECURITY_BLOCK_USER_MESSAGE}`);
|
throw new Error(`${CLOUDFLARE_SECURITY_BLOCK_ERROR_PREFIX}${CLOUDFLARE_SECURITY_BLOCK_USER_MESSAGE}`);
|
||||||
}
|
}
|
||||||
if (pageState?.addPhonePage || pageState?.phoneVerificationPage) {
|
if (pageState?.addPhonePage || pageState?.phoneVerificationPage) {
|
||||||
const latestState = await getState();
|
const urlPart = pageState?.url ? ` URL: ${pageState.url}` : '';
|
||||||
if (!Boolean(latestState?.phoneVerificationEnabled)) {
|
throw new Error(
|
||||||
const urlPart = pageState?.url ? ` URL: ${pageState.url}` : '';
|
pageState?.phoneVerificationPage
|
||||||
throw new Error(
|
? `步骤 ${visibleStep}:自动确认 OAuth 只处理 OAuth 授权页,当前仍在手机验证码页。${urlPart}`.trim()
|
||||||
pageState?.phoneVerificationPage
|
: `步骤 ${visibleStep}:自动确认 OAuth 只处理 OAuth 授权页,当前仍在添加手机号页。${urlPart}`.trim()
|
||||||
? `步骤 ${visibleStep}:当前认证页进入手机验证码页,但未开启接码功能,无法继续自动授权。${urlPart}`.trim()
|
);
|
||||||
: `步骤 ${visibleStep}:当前认证页进入手机号页面,但未开启接码功能,无法继续自动授权。${urlPart}`.trim()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await phoneVerificationHelpers.completePhoneVerificationFlow(tabId, pageState, { visibleStep });
|
|
||||||
recovered = false;
|
|
||||||
await sleepWithStop(250);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (pageState?.addPhonePage) {
|
|
||||||
throw new Error(`步骤 ${visibleStep}:认证页进入了手机号页面,当前不是 OAuth 同意页,无法继续自动授权。`);
|
|
||||||
}
|
}
|
||||||
if (pageState?.retryPage) {
|
if (pageState?.retryPage) {
|
||||||
const retryUrl = String(pageState?.url || '').trim();
|
const retryUrl = String(pageState?.url || '').trim();
|
||||||
@@ -13913,7 +13911,8 @@ async function recoverOAuthLocalhostTimeout(details = {}) {
|
|||||||
const authLoginStep = typeof getAuthChainStartStepId === 'function'
|
const authLoginStep = typeof getAuthChainStartStepId === 'function'
|
||||||
? getAuthChainStartStepId(state || {})
|
? getAuthChainStartStepId(state || {})
|
||||||
: FINAL_OAUTH_CHAIN_START_STEP;
|
: FINAL_OAUTH_CHAIN_START_STEP;
|
||||||
const loginCodeStep = Number(visibleStep) >= 12 ? 11 : 8;
|
const authLoginNodeId = String(getNodeIdByStepForState(authLoginStep, state || {}) || 'oauth-login').trim();
|
||||||
|
const confirmNodeId = String(getNodeIdByStepForState(visibleStep, state || {}) || 'confirm-oauth').trim();
|
||||||
|
|
||||||
await addLog(
|
await addLog(
|
||||||
`检测到 OAuth localhost 回调等待窗口已过期,正在复核认证页并回到步骤 ${authLoginStep} 重拉授权链路。`,
|
`检测到 OAuth localhost 回调等待窗口已过期,正在复核认证页并回到步骤 ${authLoginStep} 重拉授权链路。`,
|
||||||
@@ -13932,7 +13931,7 @@ async function recoverOAuthLocalhostTimeout(details = {}) {
|
|||||||
});
|
});
|
||||||
} catch (inspectError) {
|
} catch (inspectError) {
|
||||||
await addLog(
|
await addLog(
|
||||||
`复核认证页状态失败(${getErrorMessage(inspectError)}),将先尝试按步骤 ${loginCodeStep} 收尾恢复。`,
|
`复核认证页状态失败(${getErrorMessage(inspectError)}),将按当前 OAuth 流程图重新执行授权前置节点。`,
|
||||||
'warn',
|
'warn',
|
||||||
{ step: visibleStep, stepKey: 'confirm-oauth' }
|
{ step: visibleStep, stepKey: 'confirm-oauth' }
|
||||||
);
|
);
|
||||||
@@ -13958,22 +13957,45 @@ async function recoverOAuthLocalhostTimeout(details = {}) {
|
|||||||
if (!step7Executor?.executeStep7 || !step8Executor?.executeStep8) {
|
if (!step7Executor?.executeStep7 || !step8Executor?.executeStep8) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const workflowNodeIds = getAutoRunWorkflowNodeIds(latestState);
|
||||||
|
const authStartIndex = workflowNodeIds.indexOf(authLoginNodeId);
|
||||||
|
const confirmIndex = workflowNodeIds.indexOf(confirmNodeId);
|
||||||
|
if (authStartIndex < 0 || confirmIndex < 0 || authStartIndex >= confirmIndex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const recoveryNodeIds = workflowNodeIds.slice(authStartIndex, confirmIndex);
|
||||||
|
const runRecoveryNode = async (nodeId) => {
|
||||||
|
const recoveryState = await getState();
|
||||||
|
const recoveryStep = getStepIdByNodeIdForState(nodeId, recoveryState);
|
||||||
|
const payload = {
|
||||||
|
...recoveryState,
|
||||||
|
visibleStep: recoveryStep,
|
||||||
|
nodeId,
|
||||||
|
};
|
||||||
|
switch (nodeId) {
|
||||||
|
case 'oauth-login':
|
||||||
|
return step7Executor.executeStep7(payload);
|
||||||
|
case 'fetch-login-code':
|
||||||
|
return step8Executor.executeStep8(payload);
|
||||||
|
case 'post-login-phone-verification':
|
||||||
|
return step8Executor.executePostLoginPhoneVerification(payload);
|
||||||
|
case 'bind-email':
|
||||||
|
return step8Executor.executeBindEmail(payload);
|
||||||
|
case 'fetch-bind-email-code':
|
||||||
|
return step8Executor.executeFetchBindEmailCode(payload);
|
||||||
|
default:
|
||||||
|
throw new Error(`OAuth localhost 恢复不支持节点 ${nodeId}。`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
await addLog(
|
await addLog(
|
||||||
`正在自动重开步骤 ${authLoginStep} -> ${loginCodeStep},恢复到可继续捕获 localhost 回调的状态。`,
|
`正在自动重开 OAuth 前置节点:${recoveryNodeIds.join(' -> ')}。`,
|
||||||
'warn',
|
'warn',
|
||||||
{ step: visibleStep, stepKey: 'confirm-oauth' }
|
{ step: visibleStep, stepKey: 'confirm-oauth' }
|
||||||
);
|
);
|
||||||
await step7Executor.executeStep7({
|
for (const nodeId of recoveryNodeIds) {
|
||||||
...latestState,
|
await runRecoveryNode(nodeId);
|
||||||
visibleStep: authLoginStep,
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const stateAfterStep7 = await getState();
|
|
||||||
await step8Executor.executeStep8({
|
|
||||||
...stateAfterStep7,
|
|
||||||
visibleStep: loginCodeStep,
|
|
||||||
});
|
|
||||||
|
|
||||||
const recoveredState = await getState();
|
const recoveredState = await getState();
|
||||||
const oauthUrl = String(recoveredState?.oauthUrl || state?.oauthUrl || '').trim();
|
const oauthUrl = String(recoveredState?.oauthUrl || state?.oauthUrl || '').trim();
|
||||||
@@ -13989,7 +14011,7 @@ async function recoverOAuthLocalhostTimeout(details = {}) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await addLog(
|
await addLog(
|
||||||
`已恢复到步骤 ${authLoginStep} -> ${loginCodeStep} 收尾状态,并刷新 OAuth localhost 回调等待窗口,准备重试当前步骤。`,
|
`已恢复到自动确认 OAuth 前置状态,并刷新 OAuth localhost 回调等待窗口,准备重试当前步骤。`,
|
||||||
'warn',
|
'warn',
|
||||||
{ step: visibleStep, stepKey: 'confirm-oauth' }
|
{ step: visibleStep, stepKey: 'confirm-oauth' }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
|
|
||||||
function isVerificationMailPollingError(error) {
|
function isVerificationMailPollingError(error) {
|
||||||
const message = getErrorMessage(error);
|
const message = getErrorMessage(error);
|
||||||
return /未在 .*邮箱中找到新的匹配邮件|未在 Hotmail 收件箱中找到新的匹配验证码|邮箱轮询结束,但未获取到验证码|无法获取新的(?:注册|登录)验证码|页面未能重新就绪|页面通信异常|did not respond in \d+s|405\s+method\s+not\s+allowed|route\s+error.*405|did\s+not\s+provide\s+an?\s+[`'"]?action|post\s+request\s+to\s+["']?\/(?:email|phone)-verification/i.test(message);
|
return /未在 .*邮箱中找到新的匹配邮件|未在 Hotmail 收件箱中找到新的匹配验证码|邮箱轮询结束,但未获取到验证码|无法获取新的(?:注册|登录)验证码|页面未能重新就绪|页面通信异常|内容脚本\s+\d+(?:\.\d+)?\s*秒内未响应|did not respond in \d+s|405\s+method\s+not\s+allowed|route\s+error.*405|did\s+not\s+provide\s+an?\s+[`'"]?action|post\s+request\s+to\s+["']?\/(?:email|phone)-verification/i.test(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAddPhoneAuthFailure(error) {
|
function isAddPhoneAuthFailure(error) {
|
||||||
|
|||||||
@@ -263,11 +263,13 @@
|
|||||||
6: 'wait-registration-success',
|
6: 'wait-registration-success',
|
||||||
7: 'oauth-login',
|
7: 'oauth-login',
|
||||||
8: 'fetch-login-code',
|
8: 'fetch-login-code',
|
||||||
9: 'confirm-oauth',
|
9: 'post-login-phone-verification',
|
||||||
10: 'platform-verify',
|
10: 'confirm-oauth',
|
||||||
11: 'fetch-login-code',
|
11: 'fetch-login-code',
|
||||||
12: 'confirm-oauth',
|
12: 'post-login-phone-verification',
|
||||||
13: 'platform-verify',
|
13: 'confirm-oauth',
|
||||||
|
14: 'platform-verify',
|
||||||
|
15: 'platform-verify',
|
||||||
});
|
});
|
||||||
|
|
||||||
function getStepKeyForState(step, state = {}) {
|
function getStepKeyForState(step, state = {}) {
|
||||||
@@ -647,6 +649,42 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stepKey === 'post-login-phone-verification') {
|
||||||
|
await setState({
|
||||||
|
currentPhoneVerificationCode: '',
|
||||||
|
signupPhoneVerificationRequestedAt: null,
|
||||||
|
signupPhoneVerificationPurpose: '',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stepKey === 'bind-email') {
|
||||||
|
const updates = {};
|
||||||
|
if (payload.bindEmailSubmitted !== undefined) {
|
||||||
|
updates.bindEmailSubmitted = Boolean(payload.bindEmailSubmitted);
|
||||||
|
}
|
||||||
|
if (payload.email !== undefined) {
|
||||||
|
updates.email = payload.email || null;
|
||||||
|
}
|
||||||
|
if (payload.step8VerificationTargetEmail !== undefined) {
|
||||||
|
updates.step8VerificationTargetEmail = payload.step8VerificationTargetEmail || '';
|
||||||
|
}
|
||||||
|
if (Object.keys(updates).length) {
|
||||||
|
await setState(updates);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stepKey === 'fetch-bind-email-code') {
|
||||||
|
await setState({
|
||||||
|
lastEmailTimestamp: payload.emailTimestamp || null,
|
||||||
|
loginVerificationRequestedAt: null,
|
||||||
|
step8VerificationTargetEmail: '',
|
||||||
|
bindEmailSubmitted: false,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (stepKey === 'confirm-oauth') {
|
if (stepKey === 'confirm-oauth') {
|
||||||
if (payload.localhostUrl) {
|
if (payload.localhostUrl) {
|
||||||
if (!isLocalhostOAuthCallbackUrl(payload.localhostUrl)) {
|
if (!isLocalhostOAuthCallbackUrl(payload.localhostUrl)) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,6 @@
|
|||||||
root.MultiPageBackgroundStep8 = factory();
|
root.MultiPageBackgroundStep8 = factory();
|
||||||
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep8Module() {
|
})(typeof self !== 'undefined' ? self : globalThis, function createBackgroundStep8Module() {
|
||||||
const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000;
|
const MAIL_2925_FILTER_LOOKBACK_MS = 10 * 60 * 1000;
|
||||||
const STEP8_ADD_EMAIL_URL = 'https://auth.openai.com/add-email';
|
|
||||||
const STEP8_CURRENT_STEP_RECOVERY_MAX_ATTEMPTS = 3;
|
|
||||||
|
|
||||||
function createStep8Executor(deps = {}) {
|
function createStep8Executor(deps = {}) {
|
||||||
const {
|
const {
|
||||||
@@ -30,7 +28,6 @@
|
|||||||
rerunStep7ForStep8Recovery,
|
rerunStep7ForStep8Recovery,
|
||||||
reuseOrCreateTab,
|
reuseOrCreateTab,
|
||||||
sendToContentScriptResilient,
|
sendToContentScriptResilient,
|
||||||
buildRegistrationEmailStateUpdates = null,
|
|
||||||
persistRegistrationEmailState = null,
|
persistRegistrationEmailState = null,
|
||||||
phoneVerificationHelpers = null,
|
phoneVerificationHelpers = null,
|
||||||
setState,
|
setState,
|
||||||
@@ -41,6 +38,7 @@
|
|||||||
throwIfStopped,
|
throwIfStopped,
|
||||||
} = deps;
|
} = deps;
|
||||||
let activeFetchLoginCodeStep = null;
|
let activeFetchLoginCodeStep = null;
|
||||||
|
let activeFetchLoginCodeStepKey = 'fetch-login-code';
|
||||||
|
|
||||||
function normalizeLogStep(value) {
|
function normalizeLogStep(value) {
|
||||||
const step = Math.floor(Number(value) || 0);
|
const step = Math.floor(Number(value) || 0);
|
||||||
@@ -61,7 +59,7 @@
|
|||||||
if (step) {
|
if (step) {
|
||||||
normalizedOptions.step = step;
|
normalizedOptions.step = step;
|
||||||
if (!normalizedOptions.stepKey) {
|
if (!normalizedOptions.stepKey) {
|
||||||
normalizedOptions.stepKey = 'fetch-login-code';
|
normalizedOptions.stepKey = activeFetchLoginCodeStepKey || 'fetch-login-code';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete normalizedOptions.visibleStep;
|
delete normalizedOptions.visibleStep;
|
||||||
@@ -73,6 +71,27 @@
|
|||||||
return visibleStep > 0 ? visibleStep : fallback;
|
return visibleStep > 0 ? visibleStep : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeSignupMethod(value = '') {
|
||||||
|
return String(value || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeIdentifierType(value = '') {
|
||||||
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
|
return normalized === 'phone' || normalized === 'email' ? normalized : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPhoneLoginCodeMode(state = {}) {
|
||||||
|
if (normalizeIdentifierType(state?.accountIdentifierType) === 'phone') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return normalizeSignupMethod(state?.resolvedSignupMethod || state?.signupMethod) === 'phone'
|
||||||
|
&& Boolean(
|
||||||
|
String(state?.signupPhoneNumber || '').trim()
|
||||||
|
|| String(state?.signupPhoneCompletedActivation?.phoneNumber || '').trim()
|
||||||
|
|| String(state?.signupPhoneActivation?.phoneNumber || '').trim()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function getAuthLoginStepForVisibleStep(visibleStep) {
|
function getAuthLoginStepForVisibleStep(visibleStep) {
|
||||||
return visibleStep >= 11 ? 10 : 7;
|
return visibleStep >= 11 ? 10 : 7;
|
||||||
}
|
}
|
||||||
@@ -173,7 +192,7 @@
|
|||||||
retryDelayMs: 700,
|
retryDelayMs: 700,
|
||||||
logMessage: `步骤 ${visibleStep}:添加邮箱页面正在切换,等待邮箱验证码页就绪...`,
|
logMessage: `步骤 ${visibleStep}:添加邮箱页面正在切换,等待邮箱验证码页就绪...`,
|
||||||
logStep: visibleStep,
|
logStep: visibleStep,
|
||||||
logStepKey: 'fetch-login-code',
|
logStepKey: activeFetchLoginCodeStepKey || 'fetch-login-code',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -185,7 +204,7 @@
|
|||||||
let persistedState = latestState;
|
let persistedState = latestState;
|
||||||
if (typeof persistRegistrationEmailState === 'function') {
|
if (typeof persistRegistrationEmailState === 'function') {
|
||||||
await persistRegistrationEmailState(latestState, resolvedEmail, {
|
await persistRegistrationEmailState(latestState, resolvedEmail, {
|
||||||
source: 'step8_add_email',
|
source: activeFetchLoginCodeStepKey === 'bind-email' ? 'bind_email' : 'step8_add_email',
|
||||||
preserveAccountIdentity: true,
|
preserveAccountIdentity: true,
|
||||||
});
|
});
|
||||||
persistedState = typeof getState === 'function' ? await getState() : latestState;
|
persistedState = typeof getState === 'function' ? await getState() : latestState;
|
||||||
@@ -234,73 +253,48 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function completeStep8WhenDeferredToPostLoginPhone(visibleStep, pageState = {}, options = {}) {
|
||||||
|
await setState({
|
||||||
|
step8VerificationTargetEmail: '',
|
||||||
|
loginVerificationRequestedAt: null,
|
||||||
|
});
|
||||||
|
await addLog(
|
||||||
|
`步骤 ${visibleStep}:当前认证页已进入手机号验证流程,跳过登录邮箱验证码,交给后续“手机号验证”步骤处理。`,
|
||||||
|
'warn'
|
||||||
|
);
|
||||||
|
if (typeof completeNodeFromBackground === 'function') {
|
||||||
|
await completeNodeFromBackground(options.nodeId || 'fetch-login-code', {
|
||||||
|
loginVerificationRequestedAt: null,
|
||||||
|
skipLoginVerificationStep: true,
|
||||||
|
addPhonePage: pageState?.state === 'add_phone_page' || Boolean(pageState?.addPhonePage),
|
||||||
|
phoneVerificationPage: pageState?.state === 'phone_verification_page' || Boolean(pageState?.phoneVerificationPage),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function completeStep8WhenDeferredToBindEmail(visibleStep, options = {}) {
|
||||||
|
await setState({
|
||||||
|
step8VerificationTargetEmail: '',
|
||||||
|
loginVerificationRequestedAt: null,
|
||||||
|
});
|
||||||
|
await addLog(
|
||||||
|
`步骤 ${visibleStep}:当前认证页已进入添加邮箱页,跳过登录短信验证码,交给后续“绑定邮箱”步骤处理。`,
|
||||||
|
'warn'
|
||||||
|
);
|
||||||
|
if (typeof completeNodeFromBackground === 'function') {
|
||||||
|
await completeNodeFromBackground(options.nodeId || 'fetch-login-code', {
|
||||||
|
loginVerificationRequestedAt: null,
|
||||||
|
skipLoginVerificationStep: true,
|
||||||
|
addEmailPage: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isStep8AddPhoneStateError(error) {
|
function isStep8AddPhoneStateError(error) {
|
||||||
const message = String(error?.message || error || '');
|
const message = String(error?.message || error || '');
|
||||||
return /add-phone|手机号页面|手机号验证页|phone[\s-_]verification|phone\s+number/i.test(message);
|
return /add-phone|手机号页面|手机号验证页|phone[\s-_]verification|phone\s+number/i.test(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isStep8EmailInUseError(error) {
|
|
||||||
const message = String(error?.message || error || '');
|
|
||||||
return /STEP8_EMAIL_IN_USE::|email_in_use|email\s+(?:address\s+)?already\s+exists|already\s+associated\s+with\s+this\s+email/i.test(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isStep8MaxCheckAttemptsError(error) {
|
|
||||||
const message = String(error?.message || error || '');
|
|
||||||
return /AUTH_MAX_CHECK_ATTEMPTS::|max_check_attempts/i.test(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openStep8AddEmailPage(state, visibleStep, reasonLabel = '') {
|
|
||||||
const tabId = typeof getTabId === 'function' ? await getTabId('signup-page') : 0;
|
|
||||||
const url = STEP8_ADD_EMAIL_URL;
|
|
||||||
if (tabId && chrome?.tabs?.update) {
|
|
||||||
await chrome.tabs.update(tabId, { url, active: true });
|
|
||||||
} else if (typeof reuseOrCreateTab === 'function') {
|
|
||||||
await reuseOrCreateTab('signup-page', url);
|
|
||||||
} else {
|
|
||||||
throw new Error(`Step ${visibleStep}: cannot reopen add-email page for Step 8 recovery.`);
|
|
||||||
}
|
|
||||||
if (typeof sleepWithStop === 'function') {
|
|
||||||
await sleepWithStop(1000);
|
|
||||||
}
|
|
||||||
await addLog(
|
|
||||||
`步骤 ${visibleStep}:重新打开添加邮箱页面${reasonLabel ? `(${reasonLabel})` : ''}。`,
|
|
||||||
'warn'
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
...(state || {}),
|
|
||||||
oauthUrl: state?.oauthUrl || url,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resetStep8AfterEmailInUse(state, visibleStep) {
|
|
||||||
const currentEmail = String(state?.email || '').trim();
|
|
||||||
const registrationEmailUpdates = typeof buildRegistrationEmailStateUpdates === 'function'
|
|
||||||
? buildRegistrationEmailStateUpdates(state, {
|
|
||||||
currentEmail: null,
|
|
||||||
preservePrevious: true,
|
|
||||||
source: 'step8_recovery',
|
|
||||||
})
|
|
||||||
: { email: null };
|
|
||||||
await setState({
|
|
||||||
...registrationEmailUpdates,
|
|
||||||
step8VerificationTargetEmail: '',
|
|
||||||
loginVerificationRequestedAt: null,
|
|
||||||
});
|
|
||||||
if (currentEmail) {
|
|
||||||
await addLog(`步骤 ${visibleStep}:检测到邮箱 ${currentEmail} 已被占用,已清理运行态并准备重新获取新邮箱。`, 'warn');
|
|
||||||
} else {
|
|
||||||
await addLog(`步骤 ${visibleStep}:检测到邮箱已被占用,已清理运行态并准备重新获取新邮箱。`, 'warn');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resetStep8AfterMaxCheckAttempts(visibleStep) {
|
|
||||||
await setState({
|
|
||||||
step8VerificationTargetEmail: '',
|
|
||||||
loginVerificationRequestedAt: null,
|
|
||||||
});
|
|
||||||
await addLog(`步骤 ${visibleStep}:检测到 max_check_attempts,将重新开始当前添加邮箱步骤,不继续点击重试。`, 'warn');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function recoverStep8PollingFailure(currentState, visibleStep) {
|
async function recoverStep8PollingFailure(currentState, visibleStep) {
|
||||||
const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep);
|
const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep);
|
||||||
try {
|
try {
|
||||||
@@ -409,79 +403,142 @@
|
|||||||
return result || {};
|
return result || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runStep8Attempt(state, runtime = {}) {
|
async function ensureAuthTabForPostLoginStep(state, visibleStep) {
|
||||||
const visibleStep = getVisibleStep(state, 8);
|
|
||||||
activeFetchLoginCodeStep = visibleStep;
|
|
||||||
const authTabId = await getTabId('signup-page');
|
const authTabId = await getTabId('signup-page');
|
||||||
|
|
||||||
if (authTabId) {
|
if (authTabId) {
|
||||||
await chrome.tabs.update(authTabId, { active: true });
|
await chrome.tabs.update(authTabId, { active: true });
|
||||||
} else {
|
return authTabId;
|
||||||
if (!state.oauthUrl) {
|
}
|
||||||
throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${getAuthLoginStepForVisibleStep(visibleStep)}。`);
|
if (!state?.oauthUrl) {
|
||||||
}
|
throw new Error(`步骤 ${visibleStep}:缺少登录用 OAuth 链接,请先完成刷新 OAuth 并登录。`);
|
||||||
await reuseOrCreateTab('signup-page', state.oauthUrl);
|
}
|
||||||
|
return reuseOrCreateTab('signup-page', state.oauthUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function completePostLoginPhoneVerificationSkippedOnOauth(visibleStep, options = {}) {
|
||||||
|
await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过手机号验证步骤。`, 'warn', {
|
||||||
|
step: visibleStep,
|
||||||
|
stepKey: 'post-login-phone-verification',
|
||||||
|
});
|
||||||
|
if (typeof completeNodeFromBackground === 'function') {
|
||||||
|
await completeNodeFromBackground(options.nodeId || 'post-login-phone-verification', {
|
||||||
|
directOAuthConsentPage: true,
|
||||||
|
phoneVerification: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executePostLoginPhoneVerification(state) {
|
||||||
|
const visibleStep = getVisibleStep(state, 9);
|
||||||
|
activeFetchLoginCodeStep = visibleStep;
|
||||||
|
activeFetchLoginCodeStepKey = 'post-login-phone-verification';
|
||||||
|
const authTabId = await ensureAuthTabForPostLoginStep(state, visibleStep);
|
||||||
|
const pageState = await getLoginAuthStateFromContent(visibleStep, {
|
||||||
|
timeoutMs: await getStep8ReadyTimeoutMs('确认手机号验证页或 OAuth 授权页已就绪', state?.oauthUrl || '', visibleStep),
|
||||||
|
logMessage: `步骤 ${visibleStep}:正在确认是否需要手机号验证...`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pageState?.state === 'oauth_consent_page') {
|
||||||
|
await completePostLoginPhoneVerificationSkippedOnOauth(visibleStep, { nodeId: state?.nodeId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pageState?.state !== 'add_phone_page' && pageState?.state !== 'phone_verification_page') {
|
||||||
|
throw new Error(`步骤 ${visibleStep}:手机号验证步骤只处理添加手机号页或手机验证码页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
|
||||||
|
}
|
||||||
|
if (!state?.phoneVerificationEnabled) {
|
||||||
|
throw new Error(`步骤 ${visibleStep}:检测到需要手机号验证,但手机接码未开启。URL: ${pageState?.url || ''}`.trim());
|
||||||
|
}
|
||||||
|
if (typeof phoneVerificationHelpers?.completePhoneVerificationFlow !== 'function') {
|
||||||
|
throw new Error(`步骤 ${visibleStep}:手机号验证流程不可用,接码模块尚未初始化。`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateLastResendAt = Number(state?.loginVerificationRequestedAt) || 0;
|
const result = await phoneVerificationHelpers.completePhoneVerificationFlow(authTabId, pageState, {
|
||||||
let latestResendAt = Math.max(0, Number(runtime?.stickyLastResendAt) || 0, stateLastResendAt);
|
step: visibleStep,
|
||||||
|
visibleStep,
|
||||||
|
});
|
||||||
|
if (typeof completeNodeFromBackground === 'function') {
|
||||||
|
await completeNodeFromBackground(state?.nodeId || 'post-login-phone-verification', {
|
||||||
|
phoneVerification: true,
|
||||||
|
postLoginPhoneVerification: true,
|
||||||
|
code: result?.code || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeBindEmail(state) {
|
||||||
|
const visibleStep = getVisibleStep(state, 9);
|
||||||
|
activeFetchLoginCodeStep = visibleStep;
|
||||||
|
activeFetchLoginCodeStepKey = 'bind-email';
|
||||||
|
await ensureAuthTabForPostLoginStep(state, visibleStep);
|
||||||
|
const pageState = await getLoginAuthStateFromContent(visibleStep, {
|
||||||
|
timeoutMs: await getStep8ReadyTimeoutMs('确认添加邮箱页或 OAuth 授权页已就绪', state?.oauthUrl || '', visibleStep),
|
||||||
|
logMessage: `步骤 ${visibleStep}:正在确认是否需要绑定邮箱...`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pageState?.state === 'oauth_consent_page') {
|
||||||
|
await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过绑定邮箱步骤。`, 'warn', {
|
||||||
|
step: visibleStep,
|
||||||
|
stepKey: 'bind-email',
|
||||||
|
});
|
||||||
|
if (typeof completeNodeFromBackground === 'function') {
|
||||||
|
await completeNodeFromBackground(state?.nodeId || 'bind-email', {
|
||||||
|
directOAuthConsentPage: true,
|
||||||
|
bindEmailSubmitted: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageState?.state !== 'add_email_page') {
|
||||||
|
throw new Error(`步骤 ${visibleStep}:绑定邮箱步骤只处理添加邮箱页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
const addEmailPreparation = await submitAddEmailIfNeeded(state, visibleStep, pageState);
|
||||||
|
const preparedState = addEmailPreparation?.state || state;
|
||||||
|
const nextPageState = addEmailPreparation?.pageState || pageState;
|
||||||
|
if (nextPageState?.state !== 'verification_page') {
|
||||||
|
throw new Error(`步骤 ${visibleStep}:绑定邮箱提交后必须进入邮箱验证码页,当前状态:${nextPageState?.state || 'unknown'}。URL: ${nextPageState?.url || ''}`.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof completeNodeFromBackground === 'function') {
|
||||||
|
await completeNodeFromBackground(state?.nodeId || 'bind-email', {
|
||||||
|
bindEmailSubmitted: true,
|
||||||
|
email: preparedState?.email || '',
|
||||||
|
step8VerificationTargetEmail: preparedState?.step8VerificationTargetEmail || nextPageState?.displayedEmail || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pollEmailVerificationCode(preparedState, pageState, visibleStep, runtime = {}) {
|
||||||
|
let latestResendAt = Math.max(
|
||||||
|
0,
|
||||||
|
Number(runtime?.stickyLastResendAt) || 0,
|
||||||
|
Number(preparedState?.loginVerificationRequestedAt) || 0
|
||||||
|
);
|
||||||
const notifyResendRequestedAt = typeof runtime?.onResendRequestedAt === 'function'
|
const notifyResendRequestedAt = typeof runtime?.onResendRequestedAt === 'function'
|
||||||
? runtime.onResendRequestedAt
|
? runtime.onResendRequestedAt
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
throwIfStopped();
|
|
||||||
let pageState = await ensureStep8VerificationPageReady({
|
|
||||||
visibleStep,
|
|
||||||
authLoginStep: getAuthLoginStepForVisibleStep(visibleStep),
|
|
||||||
allowPhoneVerificationPage: true,
|
|
||||||
allowAddEmailPage: true,
|
|
||||||
timeoutMs: await getStep8ReadyTimeoutMs('确认登录验证码页已就绪', state?.oauthUrl || '', visibleStep),
|
|
||||||
});
|
|
||||||
if (pageState?.state === 'oauth_consent_page') {
|
|
||||||
await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, { nodeId: state?.nodeId });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (pageState?.state === 'phone_verification_page') {
|
|
||||||
return executeLoginPhoneCodeStep(state, authTabId, visibleStep);
|
|
||||||
}
|
|
||||||
|
|
||||||
let preparedState = state;
|
|
||||||
const addEmailPreparation = await submitAddEmailIfNeeded(preparedState, visibleStep, pageState);
|
|
||||||
preparedState = addEmailPreparation?.state || preparedState;
|
|
||||||
pageState = addEmailPreparation?.pageState || pageState;
|
|
||||||
if (pageState?.state === 'oauth_consent_page') {
|
|
||||||
await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, { nodeId: preparedState?.nodeId || state?.nodeId });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (pageState?.state === 'phone_verification_page') {
|
|
||||||
return executeLoginPhoneCodeStep(preparedState, authTabId, visibleStep);
|
|
||||||
}
|
|
||||||
|
|
||||||
const preparedStateLastResendAt = Number(preparedState?.loginVerificationRequestedAt) || 0;
|
|
||||||
if (preparedStateLastResendAt > 0) {
|
|
||||||
latestResendAt = Math.max(latestResendAt, preparedStateLastResendAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mail = getMailConfig(preparedState);
|
const mail = getMailConfig(preparedState);
|
||||||
if (mail.error) throw new Error(mail.error);
|
if (mail.error) throw new Error(mail.error);
|
||||||
const stepStartedAt = Date.now();
|
const stepStartedAt = Date.now();
|
||||||
const verificationFilterAfterTimestamp = mail.provider === '2925'
|
const verificationFilterAfterTimestamp = mail.provider === '2925'
|
||||||
? Math.max(0, stepStartedAt - MAIL_2925_FILTER_LOOKBACK_MS)
|
? Math.max(0, stepStartedAt - MAIL_2925_FILTER_LOOKBACK_MS)
|
||||||
: stepStartedAt;
|
: stepStartedAt;
|
||||||
const verificationSessionKey = `8:${stepStartedAt}`;
|
const verificationSessionKey = `${visibleStep}:${stepStartedAt}`;
|
||||||
const shouldCompareVerificationEmail = mail.provider !== '2925';
|
const shouldCompareVerificationEmail = mail.provider !== '2925';
|
||||||
const displayedVerificationEmail = shouldCompareVerificationEmail
|
const displayedVerificationEmail = shouldCompareVerificationEmail
|
||||||
? normalizeStep8VerificationTargetEmail(pageState?.displayedEmail)
|
? normalizeStep8VerificationTargetEmail(pageState?.displayedEmail)
|
||||||
: '';
|
: '';
|
||||||
const fixedTargetEmail = shouldCompareVerificationEmail
|
const fixedTargetEmail = shouldCompareVerificationEmail
|
||||||
? (displayedVerificationEmail || normalizeStep8VerificationTargetEmail(preparedState?.email))
|
? (displayedVerificationEmail || normalizeStep8VerificationTargetEmail(preparedState?.step8VerificationTargetEmail || preparedState?.email))
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
await setState({
|
await setState({
|
||||||
step8VerificationTargetEmail: displayedVerificationEmail || '',
|
step8VerificationTargetEmail: displayedVerificationEmail || '',
|
||||||
});
|
});
|
||||||
|
|
||||||
await addLog(`步骤 ${visibleStep}:登录验证码页面已就绪,开始获取验证码。`, 'info');
|
await addLog(`步骤 ${visibleStep}:邮箱验证码页面已就绪,开始获取验证码。`, 'info');
|
||||||
if (shouldCompareVerificationEmail && displayedVerificationEmail) {
|
if (shouldCompareVerificationEmail && displayedVerificationEmail) {
|
||||||
await addLog(`步骤 ${visibleStep}:已固定当前验证码页显示邮箱 ${displayedVerificationEmail} 作为后续匹配目标。`, 'info');
|
await addLog(`步骤 ${visibleStep}:已固定当前验证码页显示邮箱 ${displayedVerificationEmail} 作为后续匹配目标。`, 'info');
|
||||||
}
|
}
|
||||||
@@ -491,7 +548,7 @@
|
|||||||
completionStep: visibleStep,
|
completionStep: visibleStep,
|
||||||
promptStep: visibleStep,
|
promptStep: visibleStep,
|
||||||
});
|
});
|
||||||
return;
|
return { lastResendAt: latestResendAt };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') {
|
if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') {
|
||||||
@@ -564,6 +621,104 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function completeFetchBindEmailCodeSkippedOnOauth(visibleStep, options = {}) {
|
||||||
|
await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过绑定邮箱验证码步骤。`, 'warn', {
|
||||||
|
step: visibleStep,
|
||||||
|
stepKey: 'fetch-bind-email-code',
|
||||||
|
});
|
||||||
|
if (typeof completeNodeFromBackground === 'function') {
|
||||||
|
await completeNodeFromBackground(options.nodeId || 'fetch-bind-email-code', {
|
||||||
|
directOAuthConsentPage: true,
|
||||||
|
bindEmailCodeSkipped: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeFetchBindEmailCode(state) {
|
||||||
|
const visibleStep = getVisibleStep(state, 10);
|
||||||
|
activeFetchLoginCodeStep = visibleStep;
|
||||||
|
activeFetchLoginCodeStepKey = 'fetch-bind-email-code';
|
||||||
|
await ensureAuthTabForPostLoginStep(state, visibleStep);
|
||||||
|
const pageState = await getLoginAuthStateFromContent(visibleStep, {
|
||||||
|
timeoutMs: await getStep8ReadyTimeoutMs('确认绑定邮箱验证码页已就绪', state?.oauthUrl || '', visibleStep),
|
||||||
|
logMessage: `步骤 ${visibleStep}:正在确认绑定邮箱验证码页...`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pageState?.state === 'oauth_consent_page') {
|
||||||
|
if (state?.bindEmailSubmitted) {
|
||||||
|
throw new Error(`步骤 ${visibleStep}:绑定邮箱提交后不应直接进入 OAuth 授权页,必须先完成邮箱验证码。URL: ${pageState?.url || ''}`.trim());
|
||||||
|
}
|
||||||
|
await completeFetchBindEmailCodeSkippedOnOauth(visibleStep, { nodeId: state?.nodeId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pageState?.state !== 'verification_page') {
|
||||||
|
throw new Error(`步骤 ${visibleStep}:获取绑定邮箱验证码步骤只处理邮箱验证码页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
|
||||||
|
}
|
||||||
|
if (!state?.bindEmailSubmitted) {
|
||||||
|
throw new Error(`步骤 ${visibleStep}:尚未完成绑定邮箱提交,不能直接获取绑定邮箱验证码。`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pollEmailVerificationCode(state, pageState, visibleStep, {
|
||||||
|
stickyLastResendAt: Number(state?.loginVerificationRequestedAt) || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runStep8Attempt(state, runtime = {}) {
|
||||||
|
const visibleStep = getVisibleStep(state, 8);
|
||||||
|
activeFetchLoginCodeStep = visibleStep;
|
||||||
|
activeFetchLoginCodeStepKey = 'fetch-login-code';
|
||||||
|
const authTabId = await getTabId('signup-page');
|
||||||
|
|
||||||
|
if (authTabId) {
|
||||||
|
await chrome.tabs.update(authTabId, { active: true });
|
||||||
|
} else {
|
||||||
|
if (!state.oauthUrl) {
|
||||||
|
throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${getAuthLoginStepForVisibleStep(visibleStep)}。`);
|
||||||
|
}
|
||||||
|
await reuseOrCreateTab('signup-page', state.oauthUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
throwIfStopped();
|
||||||
|
let pageState = await ensureStep8VerificationPageReady({
|
||||||
|
visibleStep,
|
||||||
|
authLoginStep: getAuthLoginStepForVisibleStep(visibleStep),
|
||||||
|
allowPhoneVerificationPage: true,
|
||||||
|
allowAddEmailPage: true,
|
||||||
|
timeoutMs: await getStep8ReadyTimeoutMs('确认登录验证码页已就绪', state?.oauthUrl || '', visibleStep),
|
||||||
|
});
|
||||||
|
if (pageState?.state === 'oauth_consent_page') {
|
||||||
|
await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, { nodeId: state?.nodeId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const phoneLoginCodeMode = isPhoneLoginCodeMode(state);
|
||||||
|
if (phoneLoginCodeMode) {
|
||||||
|
if (pageState?.state === 'phone_verification_page') {
|
||||||
|
return executeLoginPhoneCodeStep(state, authTabId, visibleStep);
|
||||||
|
}
|
||||||
|
if (pageState?.state === 'add_email_page') {
|
||||||
|
await completeStep8WhenDeferredToBindEmail(visibleStep, { nodeId: state?.nodeId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pageState?.state === 'verification_page') {
|
||||||
|
throw new Error(`步骤 ${visibleStep}:手机号注册模式只允许处理手机登录验证码,当前进入了普通邮箱登录验证码页,不会回落到邮箱 provider。URL: ${pageState?.url || ''}`.trim());
|
||||||
|
}
|
||||||
|
if (pageState?.state === 'add_phone_page') {
|
||||||
|
throw new Error(`步骤 ${visibleStep}:手机号注册模式不应进入添加手机号页。URL: ${pageState?.url || ''}`.trim());
|
||||||
|
}
|
||||||
|
throw new Error(`步骤 ${visibleStep}:手机号注册模式登录验证码步骤进入了不允许的页面:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageState?.state === 'add_phone_page' || pageState?.state === 'phone_verification_page') {
|
||||||
|
await completeStep8WhenDeferredToPostLoginPhone(visibleStep, pageState, { nodeId: state?.nodeId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pageState?.state === 'add_email_page') {
|
||||||
|
throw new Error(`步骤 ${visibleStep}:邮箱注册模式不应进入添加邮箱页。URL: ${pageState?.url || ''}`.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
return pollEmailVerificationCode(state, pageState, visibleStep, runtime);
|
||||||
|
}
|
||||||
|
|
||||||
function isStep8RestartStep7Error(error) {
|
function isStep8RestartStep7Error(error) {
|
||||||
const message = String(error?.message || error || '');
|
const message = String(error?.message || error || '');
|
||||||
return /STEP8_RESTART_STEP7::/i.test(message);
|
return /STEP8_RESTART_STEP7::/i.test(message);
|
||||||
@@ -576,7 +731,6 @@
|
|||||||
let stickyLastResendAt = Number(state?.loginVerificationRequestedAt) || 0;
|
let stickyLastResendAt = Number(state?.loginVerificationRequestedAt) || 0;
|
||||||
let retryWithoutStep7Streak = 0;
|
let retryWithoutStep7Streak = 0;
|
||||||
const maxRetryWithoutStep7Streak = 3;
|
const maxRetryWithoutStep7Streak = 3;
|
||||||
let currentNodeRecoveryAttempt = 0;
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
@@ -600,27 +754,6 @@
|
|||||||
let currentError = err;
|
let currentError = err;
|
||||||
let retryWithoutStep7 = false;
|
let retryWithoutStep7 = false;
|
||||||
|
|
||||||
if (isStep8EmailInUseError(currentError) || isStep8MaxCheckAttemptsError(currentError)) {
|
|
||||||
currentNodeRecoveryAttempt += 1;
|
|
||||||
if (currentNodeRecoveryAttempt > STEP8_CURRENT_STEP_RECOVERY_MAX_ATTEMPTS) {
|
|
||||||
throw currentError;
|
|
||||||
}
|
|
||||||
if (isStep8EmailInUseError(currentError)) {
|
|
||||||
await resetStep8AfterEmailInUse(currentState, visibleStep);
|
|
||||||
await openStep8AddEmailPage(currentState, visibleStep, 'email_in_use');
|
|
||||||
} else {
|
|
||||||
await resetStep8AfterMaxCheckAttempts(visibleStep);
|
|
||||||
await openStep8AddEmailPage(currentState, visibleStep, 'max_check_attempts');
|
|
||||||
}
|
|
||||||
const latestState = typeof getState === 'function' ? await getState() : currentState;
|
|
||||||
currentState = {
|
|
||||||
...(currentState || {}),
|
|
||||||
...(latestState || {}),
|
|
||||||
oauthUrl: currentState?.oauthUrl || latestState?.oauthUrl || STEP8_ADD_EMAIL_URL,
|
|
||||||
};
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isMailPollingError = isVerificationMailPollingError(err);
|
const isMailPollingError = isVerificationMailPollingError(err);
|
||||||
if (isMailPollingError && !isStep8RestartStep7Error(err)) {
|
if (isMailPollingError && !isStep8RestartStep7Error(err)) {
|
||||||
const recovery = await recoverStep8PollingFailure(currentState, visibleStep);
|
const recovery = await recoverStep8PollingFailure(currentState, visibleStep);
|
||||||
@@ -717,7 +850,12 @@
|
|||||||
throw new Error(`步骤 ${visibleStep}:登录验证码流程未成功完成。`);
|
throw new Error(`步骤 ${visibleStep}:登录验证码流程未成功完成。`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { executeStep8 };
|
return {
|
||||||
|
executeStep8,
|
||||||
|
executePostLoginPhoneVerification,
|
||||||
|
executeBindEmail,
|
||||||
|
executeFetchBindEmailCode,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return { createStep8Executor };
|
return { createStep8Executor };
|
||||||
|
|||||||
@@ -1251,7 +1251,7 @@
|
|||||||
}
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data?.status !== 'ok') {
|
if (data?.status !== 'ok') {
|
||||||
throw new Error(data?.message || data?.status || 'unknown response');
|
throw new Error(data?.message || data?.status || '未知响应');
|
||||||
}
|
}
|
||||||
return buildDirectAddressSeed(countryCode, data.address || {}, fallbackSeed);
|
return buildDirectAddressSeed(countryCode, data.address || {}, fallbackSeed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
isStep6RecoverableResult,
|
isStep6RecoverableResult,
|
||||||
isStep6SuccessResult,
|
isStep6SuccessResult,
|
||||||
getTabId,
|
getTabId,
|
||||||
phoneVerificationHelpers = null,
|
|
||||||
refreshOAuthUrlBeforeStep6,
|
refreshOAuthUrlBeforeStep6,
|
||||||
reuseOrCreateTab,
|
reuseOrCreateTab,
|
||||||
sendToContentScriptResilient,
|
sendToContentScriptResilient,
|
||||||
@@ -103,40 +102,101 @@
|
|||||||
return match ? match[0] : 'https://auth.openai.com/add-phone';
|
return match ? match[0] : 'https://auth.openai.com/add-phone';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function completeStep7AddPhoneHandoff(state = {}, err, completionStep) {
|
function getStep7ResultState(result = {}) {
|
||||||
if (!state?.phoneVerificationEnabled) {
|
return String(result?.state || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStep7OauthConsentResult(result = {}) {
|
||||||
|
return Boolean(result?.directOAuthConsentPage)
|
||||||
|
|| getStep7ResultState(result) === 'oauth_consent_page';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStep7AddEmailResult(result = {}) {
|
||||||
|
return Boolean(result?.addEmailPage) || getStep7ResultState(result) === 'add_email_page';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStep7AddPhoneResult(result = {}) {
|
||||||
|
return Boolean(result?.addPhonePage) || getStep7ResultState(result) === 'add_phone_page';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStep7PhoneVerificationResult(result = {}) {
|
||||||
|
return Boolean(result?.phoneVerificationPage) || getStep7ResultState(result) === 'phone_verification_page';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStep7PlainVerificationResult(result = {}) {
|
||||||
|
return getStep7ResultState(result) === 'verification_page' && !isStep7PhoneVerificationResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildStep7CompletionPayload(result = {}, currentState = {}, currentIdentifierType = '', currentPhoneNumber = '') {
|
||||||
|
const phoneSignupMode = currentIdentifierType === 'phone';
|
||||||
|
const payload = {
|
||||||
|
loginVerificationRequestedAt: result.loginVerificationRequestedAt || null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentIdentifierType === 'phone') {
|
||||||
|
payload.accountIdentifierType = 'phone';
|
||||||
|
payload.accountIdentifier = currentPhoneNumber;
|
||||||
|
payload.signupPhoneNumber = currentPhoneNumber;
|
||||||
|
payload.signupPhoneCompletedActivation = currentState?.signupPhoneCompletedActivation || null;
|
||||||
|
payload.signupPhoneActivation = currentState?.signupPhoneActivation || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isStep7OauthConsentResult(result)) {
|
||||||
|
payload.skipLoginVerificationStep = true;
|
||||||
|
payload.directOAuthConsentPage = true;
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (phoneSignupMode) {
|
||||||
|
if (isStep7AddPhoneResult(result)) {
|
||||||
|
throw new Error(`步骤 ${completionStepForState(currentState)}:手机号注册模式 OAuth 登录不应进入添加手机号页。URL: ${result?.url || ''}`.trim());
|
||||||
|
}
|
||||||
|
if (isStep7AddEmailResult(result)) {
|
||||||
|
payload.skipLoginVerificationStep = true;
|
||||||
|
payload.addEmailPage = true;
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
if (isStep7PhoneVerificationResult(result)) {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
if (isStep7PlainVerificationResult(result)) {
|
||||||
|
throw new Error(`步骤 ${completionStepForState(currentState)}:手机号注册模式 OAuth 登录进入了普通邮箱登录验证码页,当前流程不会回落到邮箱验证码。URL: ${result?.url || ''}`.trim());
|
||||||
|
}
|
||||||
|
throw new Error(`步骤 ${completionStepForState(currentState)}:手机号注册模式 OAuth 登录进入了不允许的页面:${getLoginAuthStateLabel(result.state)}。URL: ${result?.url || ''}`.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isStep7AddEmailResult(result)) {
|
||||||
|
throw new Error(`步骤 ${completionStepForState(currentState)}:邮箱注册模式 OAuth 登录不应进入添加邮箱页。URL: ${result?.url || ''}`.trim());
|
||||||
|
}
|
||||||
|
if (isStep7AddPhoneResult(result) || isStep7PhoneVerificationResult(result)) {
|
||||||
|
payload.skipLoginVerificationStep = true;
|
||||||
|
payload.addPhonePage = isStep7AddPhoneResult(result);
|
||||||
|
payload.phoneVerificationPage = isStep7PhoneVerificationResult(result);
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
if (isStep7PlainVerificationResult(result)) {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`步骤 ${completionStepForState(currentState)}:邮箱注册模式 OAuth 登录进入了不允许的页面:${getLoginAuthStateLabel(result.state)}。URL: ${result?.url || ''}`.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
function completionStepForState(state = {}) {
|
||||||
|
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
|
||||||
|
return visibleStep > 0 ? visibleStep : 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function completeStep7PostLoginPhoneHandoff(state = {}, err, completionStep) {
|
||||||
|
if (normalizeStep7SignupMethod(state?.resolvedSignupMethod || state?.signupMethod) === 'phone') {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`步骤 ${completionStep}:登录提交后页面进入手机号页面,必须先启用接码/phone verification 后才能继续。URL: ${extractAddPhoneUrl(err)}`
|
`步骤 ${completionStep}:手机号注册模式 OAuth 登录进入了添加手机号页,当前流程不允许在手机号注册模式补手机号。URL: ${extractAddPhoneUrl(err)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (typeof phoneVerificationHelpers?.completePhoneVerificationFlow !== 'function') {
|
|
||||||
throw new Error(`步骤 ${completionStep}:手机号验证流程不可用,接码模块尚未初始化。`);
|
|
||||||
}
|
|
||||||
if (typeof getTabId !== 'function') {
|
|
||||||
throw new Error(`步骤 ${completionStep}:无法定位认证页面标签页,不能继续手机号验证。`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const signupTabId = await getTabId('signup-page');
|
|
||||||
if (!Number.isInteger(signupTabId)) {
|
|
||||||
throw new Error(`步骤 ${completionStep}:认证页面标签页已关闭,无法继续手机号验证。`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageState = {
|
|
||||||
addPhonePage: true,
|
|
||||||
phoneVerificationPage: false,
|
|
||||||
state: 'add_phone_page',
|
|
||||||
url: extractAddPhoneUrl(err),
|
|
||||||
};
|
|
||||||
await phoneVerificationHelpers.completePhoneVerificationFlow(signupTabId, pageState, {
|
|
||||||
step: completionStep,
|
|
||||||
visibleStep: completionStep,
|
|
||||||
});
|
|
||||||
await completeNodeFromBackground(state?.nodeId || 'oauth-login', {
|
await completeNodeFromBackground(state?.nodeId || 'oauth-login', {
|
||||||
loginVerificationRequestedAt: null,
|
loginVerificationRequestedAt: null,
|
||||||
skipLoginVerificationStep: true,
|
skipLoginVerificationStep: true,
|
||||||
directOAuthConsentPage: true,
|
addPhonePage: true,
|
||||||
phoneVerification: true,
|
directOAuthConsentPage: false,
|
||||||
loginPhoneVerification: true,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,22 +321,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isStep6SuccessResult(result)) {
|
if (isStep6SuccessResult(result)) {
|
||||||
const completionPayload = {
|
const completionPayload = buildStep7CompletionPayload(
|
||||||
loginVerificationRequestedAt: result.loginVerificationRequestedAt || null,
|
result,
|
||||||
};
|
{ ...(currentState || {}), visibleStep: completionStep },
|
||||||
if (currentIdentifierType === 'phone') {
|
currentIdentifierType,
|
||||||
completionPayload.accountIdentifierType = 'phone';
|
currentPhoneNumber
|
||||||
completionPayload.accountIdentifier = currentPhoneNumber;
|
);
|
||||||
completionPayload.signupPhoneNumber = currentPhoneNumber;
|
|
||||||
completionPayload.signupPhoneCompletedActivation = currentState?.signupPhoneCompletedActivation || null;
|
|
||||||
completionPayload.signupPhoneActivation = currentState?.signupPhoneActivation || null;
|
|
||||||
}
|
|
||||||
if (Object.prototype.hasOwnProperty.call(result || {}, 'skipLoginVerificationStep')) {
|
|
||||||
completionPayload.skipLoginVerificationStep = Boolean(result.skipLoginVerificationStep);
|
|
||||||
}
|
|
||||||
if (Object.prototype.hasOwnProperty.call(result || {}, 'directOAuthConsentPage')) {
|
|
||||||
completionPayload.directOAuthConsentPage = Boolean(result.directOAuthConsentPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
await completeNodeFromBackground(state?.nodeId || 'oauth-login', completionPayload);
|
await completeNodeFromBackground(state?.nodeId || 'oauth-login', completionPayload);
|
||||||
return;
|
return;
|
||||||
@@ -295,7 +345,7 @@
|
|||||||
const latestAddPhoneState = typeof getState === 'function'
|
const latestAddPhoneState = typeof getState === 'function'
|
||||||
? await getState().catch(() => state)
|
? await getState().catch(() => state)
|
||||||
: state;
|
: state;
|
||||||
await completeStep7AddPhoneHandoff(
|
await completeStep7PostLoginPhoneHandoff(
|
||||||
{ ...(state || {}), ...(latestAddPhoneState || {}) },
|
{ ...(state || {}), ...(latestAddPhoneState || {}) },
|
||||||
err,
|
err,
|
||||||
completionStep
|
completionStep
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
function isRetryableStep2TransportErrorMessage(errorLike) {
|
function isRetryableStep2TransportErrorMessage(errorLike) {
|
||||||
const message = getErrorMessage(errorLike);
|
const message = getErrorMessage(errorLike);
|
||||||
return /Content script on signup-page did not respond in \d+s|Receiving end does not exist|message channel closed|A listener indicated an asynchronous response|port closed before a response was received|did not respond in \d+s/i.test(message);
|
return /Content script on signup-page did not respond in \d+s|内容脚本\s+\d+(?:\.\d+)?\s*秒内未响应|Receiving end does not exist|message channel closed|A listener indicated an asynchronous response|port closed before a response was received|did not respond in \d+s/i.test(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLikelyLoggedInChatgptHomeUrl(rawUrl) {
|
function isLikelyLoggedInChatgptHomeUrl(rawUrl) {
|
||||||
|
|||||||
@@ -649,7 +649,7 @@
|
|||||||
settled = true;
|
settled = true;
|
||||||
const seconds = Math.ceil(responseTimeoutMs / 1000);
|
const seconds = Math.ceil(responseTimeoutMs / 1000);
|
||||||
console.warn(LOG_PREFIX, `[sendTabMessageWithTimeout] timeout ${debugLabel} after ${Date.now() - startedAt}ms`);
|
console.warn(LOG_PREFIX, `[sendTabMessageWithTimeout] timeout ${debugLabel} after ${Date.now() - startedAt}ms`);
|
||||||
reject(new Error(`Content script on ${source} did not respond in ${seconds}s. Try refreshing the tab and retry.`));
|
reject(new Error(`${getSourceLabel(source)} 内容脚本 ${seconds} 秒内未响应,请刷新页面后重试。`));
|
||||||
}, responseTimeoutMs);
|
}, responseTimeoutMs);
|
||||||
|
|
||||||
chrome.tabs.sendMessage(tabId, message)
|
chrome.tabs.sendMessage(tabId, message)
|
||||||
@@ -677,7 +677,7 @@
|
|||||||
const commandKey = getSourceCommandKey(source);
|
const commandKey = getSourceCommandKey(source);
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
pendingCommands.delete(commandKey);
|
pendingCommands.delete(commandKey);
|
||||||
reject(new Error(`Content script on ${source} did not respond in ${timeout / 1000}s. Try refreshing the tab and retry.`));
|
reject(new Error(`${getSourceLabel(source)} 内容脚本 ${timeout / 1000} 秒内未响应,请刷新页面后重试。`));
|
||||||
}, timeout);
|
}, timeout);
|
||||||
pendingCommands.set(commandKey, {
|
pendingCommands.set(commandKey, {
|
||||||
message,
|
message,
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
|
|
||||||
const isRetryableVerificationTransportError = typeof deps.isRetryableContentScriptTransportError === 'function'
|
const isRetryableVerificationTransportError = typeof deps.isRetryableContentScriptTransportError === 'function'
|
||||||
? deps.isRetryableContentScriptTransportError
|
? deps.isRetryableContentScriptTransportError
|
||||||
: ((error) => /back\/forward cache|message channel is closed|Receiving end does not exist|port closed before a response was received|A listener indicated an asynchronous response|did not respond in \d+s/i.test(
|
: ((error) => /back\/forward cache|message channel is closed|Receiving end does not exist|port closed before a response was received|A listener indicated an asynchronous response|内容脚本\s+\d+(?:\.\d+)?\s*秒内未响应|did not respond in \d+s/i.test(
|
||||||
String(typeof error === 'string' ? error : error?.message || '')
|
String(typeof error === 'string' ? error : error?.message || '')
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
@@ -89,8 +89,10 @@ function resolveCommandNodeId(message = {}) {
|
|||||||
const visibleStep = Number(message.payload?.visibleStep || message.step) || 0;
|
const visibleStep = Number(message.payload?.visibleStep || message.step) || 0;
|
||||||
if (visibleStep === 4) return 'fetch-signup-code';
|
if (visibleStep === 4) return 'fetch-signup-code';
|
||||||
if (visibleStep === 8 || visibleStep === 11) return 'fetch-login-code';
|
if (visibleStep === 8 || visibleStep === 11) return 'fetch-login-code';
|
||||||
if (visibleStep === 9 || visibleStep === 12) return 'confirm-oauth';
|
if (visibleStep === 9 || visibleStep === 12) return 'post-login-phone-verification';
|
||||||
if (visibleStep === 7 || visibleStep === 10) return 'oauth-login';
|
if (visibleStep === 10 || visibleStep === 13) return 'confirm-oauth';
|
||||||
|
if (visibleStep === 14 || visibleStep === 15) return 'platform-verify';
|
||||||
|
if (visibleStep === 7) return 'oauth-login';
|
||||||
if (visibleStep === 5) return 'fill-profile';
|
if (visibleStep === 5) return 'fill-profile';
|
||||||
if (visibleStep === 3) return 'fill-password';
|
if (visibleStep === 3) return 'fill-password';
|
||||||
if (visibleStep === 2) return 'submit-signup-email';
|
if (visibleStep === 2) return 'submit-signup-email';
|
||||||
|
|||||||
+6
-4
@@ -283,11 +283,13 @@ const DEFAULT_OPENAI_NODE_BY_STEP = Object.freeze({
|
|||||||
6: 'wait-registration-success',
|
6: 'wait-registration-success',
|
||||||
7: 'oauth-login',
|
7: 'oauth-login',
|
||||||
8: 'fetch-login-code',
|
8: 'fetch-login-code',
|
||||||
9: 'confirm-oauth',
|
9: 'post-login-phone-verification',
|
||||||
10: 'platform-verify',
|
10: 'confirm-oauth',
|
||||||
11: 'fetch-login-code',
|
11: 'fetch-login-code',
|
||||||
12: 'confirm-oauth',
|
12: 'post-login-phone-verification',
|
||||||
13: 'platform-verify',
|
13: 'confirm-oauth',
|
||||||
|
14: 'platform-verify',
|
||||||
|
15: 'platform-verify',
|
||||||
});
|
});
|
||||||
|
|
||||||
function resolveReportNodeId(stepOrNodeId, data = {}) {
|
function resolveReportNodeId(stepOrNodeId, data = {}) {
|
||||||
|
|||||||
+61
-23
@@ -9,20 +9,16 @@
|
|||||||
const SIGNUP_METHOD_EMAIL = 'email';
|
const SIGNUP_METHOD_EMAIL = 'email';
|
||||||
const SIGNUP_METHOD_PHONE = 'phone';
|
const SIGNUP_METHOD_PHONE = 'phone';
|
||||||
|
|
||||||
const NORMAL_STEP_DEFINITIONS = [
|
const NORMAL_PREFIX_STEP_DEFINITIONS = [
|
||||||
{ id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网', sourceId: 'chatgpt', driverId: null, command: 'open-chatgpt' },
|
{ id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网', sourceId: 'chatgpt', driverId: null, command: 'open-chatgpt' },
|
||||||
{ id: 2, order: 20, key: 'submit-signup-email', title: '注册并输入邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-signup-email' },
|
{ id: 2, order: 20, key: 'submit-signup-email', title: '注册并输入邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-signup-email' },
|
||||||
{ id: 3, order: 30, key: 'fill-password', title: '填写密码并继续', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-password' },
|
{ id: 3, order: 30, key: 'fill-password', title: '填写密码并继续', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-password' },
|
||||||
{ id: 4, order: 40, key: 'fetch-signup-code', title: '获取注册验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-signup-code' },
|
{ id: 4, order: 40, key: 'fetch-signup-code', title: '获取注册验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-signup-code' },
|
||||||
{ id: 5, order: 50, key: 'fill-profile', title: '填写姓名和生日', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-profile' },
|
{ id: 5, order: 50, key: 'fill-profile', title: '填写姓名和生日', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-profile' },
|
||||||
{ id: 6, order: 60, key: 'wait-registration-success', title: '等待注册成功', sourceId: 'chatgpt', driverId: null, command: 'wait-registration-success' },
|
{ id: 6, order: 60, key: 'wait-registration-success', title: '等待注册成功', sourceId: 'chatgpt', driverId: null, command: 'wait-registration-success' },
|
||||||
{ id: 7, order: 70, key: 'oauth-login', title: '刷新 OAuth 并登录', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'oauth-login' },
|
|
||||||
{ id: 8, order: 80, key: 'fetch-login-code', title: '获取登录验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-login-code' },
|
|
||||||
{ id: 9, order: 90, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' },
|
|
||||||
{ id: 10, order: 100, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const PLUS_PAYPAL_STEP_DEFINITIONS = [
|
const PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS = [
|
||||||
{ id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网', sourceId: 'chatgpt', driverId: null, command: 'open-chatgpt' },
|
{ id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网', sourceId: 'chatgpt', driverId: null, command: 'open-chatgpt' },
|
||||||
{ id: 2, order: 20, key: 'submit-signup-email', title: '注册并输入邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-signup-email' },
|
{ id: 2, order: 20, key: 'submit-signup-email', title: '注册并输入邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-signup-email' },
|
||||||
{ id: 3, order: 30, key: 'fill-password', title: '填写密码并继续', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-password' },
|
{ id: 3, order: 30, key: 'fill-password', title: '填写密码并继续', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-password' },
|
||||||
@@ -32,13 +28,9 @@
|
|||||||
{ id: 7, order: 70, key: 'plus-checkout-billing', title: '填写账单并提交订单', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-billing' },
|
{ id: 7, order: 70, key: 'plus-checkout-billing', title: '填写账单并提交订单', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-billing' },
|
||||||
{ id: 8, order: 80, key: 'paypal-approve', title: 'PayPal 登录与授权', sourceId: 'paypal-flow', driverId: 'content/paypal-flow', command: 'paypal-approve' },
|
{ id: 8, order: 80, key: 'paypal-approve', title: 'PayPal 登录与授权', sourceId: 'paypal-flow', driverId: 'content/paypal-flow', command: 'paypal-approve' },
|
||||||
{ id: 9, order: 90, key: 'plus-checkout-return', title: '订阅回跳确认', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-return' },
|
{ id: 9, order: 90, key: 'plus-checkout-return', title: '订阅回跳确认', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-return' },
|
||||||
{ id: 10, order: 100, key: 'oauth-login', title: '刷新 OAuth 并登录', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'oauth-login' },
|
|
||||||
{ id: 11, order: 110, key: 'fetch-login-code', title: '获取登录验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-login-code' },
|
|
||||||
{ id: 12, order: 120, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' },
|
|
||||||
{ id: 13, order: 130, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const PLUS_GOPAY_STEP_DEFINITIONS = [
|
const PLUS_GOPAY_PREFIX_STEP_DEFINITIONS = [
|
||||||
{ id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网', sourceId: 'chatgpt', driverId: null, command: 'open-chatgpt' },
|
{ id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网', sourceId: 'chatgpt', driverId: null, command: 'open-chatgpt' },
|
||||||
{ id: 2, order: 20, key: 'submit-signup-email', title: '注册并输入邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-signup-email' },
|
{ id: 2, order: 20, key: 'submit-signup-email', title: '注册并输入邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-signup-email' },
|
||||||
{ id: 3, order: 30, key: 'fill-password', title: '填写密码并继续', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-password' },
|
{ id: 3, order: 30, key: 'fill-password', title: '填写密码并继续', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-password' },
|
||||||
@@ -46,13 +38,9 @@
|
|||||||
{ id: 5, order: 50, key: 'fill-profile', title: '填写姓名和生日', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-profile' },
|
{ id: 5, order: 50, key: 'fill-profile', title: '填写姓名和生日', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-profile' },
|
||||||
{ id: 6, order: 60, key: 'plus-checkout-create', title: '打开 GoPay 订阅页', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-create' },
|
{ id: 6, order: 60, key: 'plus-checkout-create', title: '打开 GoPay 订阅页', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-create' },
|
||||||
{ id: 7, order: 70, key: 'gopay-subscription-confirm', title: '等待 GoPay 订阅确认', sourceId: 'gopay-flow', driverId: 'content/gopay-flow', command: 'gopay-subscription-confirm' },
|
{ id: 7, order: 70, key: 'gopay-subscription-confirm', title: '等待 GoPay 订阅确认', sourceId: 'gopay-flow', driverId: 'content/gopay-flow', command: 'gopay-subscription-confirm' },
|
||||||
{ id: 10, order: 100, key: 'oauth-login', title: '刷新 OAuth 并登录', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'oauth-login' },
|
|
||||||
{ id: 11, order: 110, key: 'fetch-login-code', title: '获取登录验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-login-code' },
|
|
||||||
{ id: 12, order: 120, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' },
|
|
||||||
{ id: 13, order: 130, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const PLUS_GPC_STEP_DEFINITIONS = [
|
const PLUS_GPC_PREFIX_STEP_DEFINITIONS = [
|
||||||
{ id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网', sourceId: 'chatgpt', driverId: null, command: 'open-chatgpt' },
|
{ id: 1, order: 10, key: 'open-chatgpt', title: '打开 ChatGPT 官网', sourceId: 'chatgpt', driverId: null, command: 'open-chatgpt' },
|
||||||
{ id: 2, order: 20, key: 'submit-signup-email', title: '注册并输入邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-signup-email' },
|
{ id: 2, order: 20, key: 'submit-signup-email', title: '注册并输入邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-signup-email' },
|
||||||
{ id: 3, order: 30, key: 'fill-password', title: '填写密码并继续', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-password' },
|
{ id: 3, order: 30, key: 'fill-password', title: '填写密码并继续', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-password' },
|
||||||
@@ -60,12 +48,50 @@
|
|||||||
{ id: 5, order: 50, key: 'fill-profile', title: '填写姓名和生日', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-profile' },
|
{ id: 5, order: 50, key: 'fill-profile', title: '填写姓名和生日', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fill-profile' },
|
||||||
{ id: 6, order: 60, key: 'plus-checkout-create', title: '创建 GPC 订单', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-create' },
|
{ id: 6, order: 60, key: 'plus-checkout-create', title: '创建 GPC 订单', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-create' },
|
||||||
{ id: 7, order: 70, key: 'plus-checkout-billing', title: '等待 GPC 任务完成', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-billing' },
|
{ id: 7, order: 70, key: 'plus-checkout-billing', title: '等待 GPC 任务完成', sourceId: 'plus-checkout', driverId: 'content/plus-checkout', command: 'plus-checkout-billing' },
|
||||||
{ id: 10, order: 100, key: 'oauth-login', title: '刷新 OAuth 并登录', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'oauth-login' },
|
|
||||||
{ id: 11, order: 110, key: 'fetch-login-code', title: '获取登录验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-login-code' },
|
|
||||||
{ id: 12, order: 120, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' },
|
|
||||||
{ id: 13, order: 130, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function createOpenAiAuthTail(startId, startOrder, signupMethod = SIGNUP_METHOD_EMAIL) {
|
||||||
|
const id = Number(startId) || 7;
|
||||||
|
const order = Number(startOrder) || id * 10;
|
||||||
|
const commonStart = [
|
||||||
|
{ id, order, key: 'oauth-login', title: '刷新 OAuth 并登录', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'oauth-login' },
|
||||||
|
{ id: id + 1, order: order + 10, key: 'fetch-login-code', title: '获取登录验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'submit-verification-code', mailRuleId: 'openai-login-code' },
|
||||||
|
];
|
||||||
|
|
||||||
|
if (signupMethod === SIGNUP_METHOD_PHONE) {
|
||||||
|
return [
|
||||||
|
...commonStart,
|
||||||
|
{ id: id + 2, order: order + 20, key: 'bind-email', title: '绑定邮箱', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'bind-email' },
|
||||||
|
{ id: id + 3, order: order + 30, key: 'fetch-bind-email-code', title: '获取绑定邮箱验证码', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'fetch-bind-email-code', mailRuleId: 'openai-login-code' },
|
||||||
|
{ id: id + 4, order: order + 40, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' },
|
||||||
|
{ id: id + 5, order: order + 50, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
...commonStart,
|
||||||
|
{ id: id + 2, order: order + 20, key: 'post-login-phone-verification', title: '手机号验证', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'post-login-phone-verification' },
|
||||||
|
{ id: id + 3, order: order + 30, key: 'confirm-oauth', title: '自动确认 OAuth', sourceId: 'openai-auth', driverId: 'content/signup-page', command: 'confirm-oauth' },
|
||||||
|
{ id: id + 4, order: order + 40, key: 'platform-verify', title: '平台回调验证', sourceId: 'platform-panel', driverId: 'content/platform-panel', command: 'platform-verify' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function createOpenAiSteps(prefixSteps, startId, startOrder, signupMethod = SIGNUP_METHOD_EMAIL) {
|
||||||
|
return [
|
||||||
|
...prefixSteps,
|
||||||
|
...createOpenAiAuthTail(startId, startOrder, signupMethod),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const NORMAL_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_EMAIL);
|
||||||
|
const NORMAL_PHONE_STEP_DEFINITIONS = createOpenAiSteps(NORMAL_PREFIX_STEP_DEFINITIONS, 7, 70, SIGNUP_METHOD_PHONE);
|
||||||
|
const PLUS_PAYPAL_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL);
|
||||||
|
const PLUS_PAYPAL_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_PAYPAL_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE);
|
||||||
|
const PLUS_GOPAY_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL);
|
||||||
|
const PLUS_GOPAY_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GOPAY_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE);
|
||||||
|
const PLUS_GPC_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_EMAIL);
|
||||||
|
const PLUS_GPC_PHONE_STEP_DEFINITIONS = createOpenAiSteps(PLUS_GPC_PREFIX_STEP_DEFINITIONS, 10, 100, SIGNUP_METHOD_PHONE);
|
||||||
|
|
||||||
const PHONE_SIGNUP_TITLE_OVERRIDES = Object.freeze({
|
const PHONE_SIGNUP_TITLE_OVERRIDES = Object.freeze({
|
||||||
'submit-signup-email': '注册并输入手机号',
|
'submit-signup-email': '注册并输入手机号',
|
||||||
'fetch-signup-code': '获取手机验证码',
|
'fetch-signup-code': '获取手机验证码',
|
||||||
@@ -103,14 +129,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getOpenAiModeStepDefinitions(options = {}) {
|
function getOpenAiModeStepDefinitions(options = {}) {
|
||||||
|
const signupMethod = getResolvedSignupMethod(options);
|
||||||
if (!isPlusModeEnabled(options)) {
|
if (!isPlusModeEnabled(options)) {
|
||||||
return NORMAL_STEP_DEFINITIONS;
|
return signupMethod === SIGNUP_METHOD_PHONE ? NORMAL_PHONE_STEP_DEFINITIONS : NORMAL_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
const paymentMethod = normalizePlusPaymentMethod(options?.plusPaymentMethod || options?.paymentMethod);
|
const paymentMethod = normalizePlusPaymentMethod(options?.plusPaymentMethod || options?.paymentMethod);
|
||||||
if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
if (paymentMethod === PLUS_PAYMENT_METHOD_GPC_HELPER) {
|
||||||
return PLUS_GPC_STEP_DEFINITIONS;
|
return signupMethod === SIGNUP_METHOD_PHONE ? PLUS_GPC_PHONE_STEP_DEFINITIONS : PLUS_GPC_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
return paymentMethod === PLUS_PAYMENT_METHOD_GOPAY ? PLUS_GOPAY_STEP_DEFINITIONS : PLUS_PAYPAL_STEP_DEFINITIONS;
|
if (paymentMethod === PLUS_PAYMENT_METHOD_GOPAY) {
|
||||||
|
return signupMethod === SIGNUP_METHOD_PHONE ? PLUS_GOPAY_PHONE_STEP_DEFINITIONS : PLUS_GOPAY_STEP_DEFINITIONS;
|
||||||
|
}
|
||||||
|
return signupMethod === SIGNUP_METHOD_PHONE ? PLUS_PAYPAL_PHONE_STEP_DEFINITIONS : PLUS_PAYPAL_STEP_DEFINITIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOpenAiPlusPaymentStepTitle(options = {}) {
|
function getOpenAiPlusPaymentStepTitle(options = {}) {
|
||||||
@@ -141,9 +171,13 @@
|
|||||||
const keyed = new Map();
|
const keyed = new Map();
|
||||||
for (const step of [
|
for (const step of [
|
||||||
...NORMAL_STEP_DEFINITIONS,
|
...NORMAL_STEP_DEFINITIONS,
|
||||||
|
...NORMAL_PHONE_STEP_DEFINITIONS,
|
||||||
...PLUS_PAYPAL_STEP_DEFINITIONS,
|
...PLUS_PAYPAL_STEP_DEFINITIONS,
|
||||||
|
...PLUS_PAYPAL_PHONE_STEP_DEFINITIONS,
|
||||||
...PLUS_GOPAY_STEP_DEFINITIONS,
|
...PLUS_GOPAY_STEP_DEFINITIONS,
|
||||||
|
...PLUS_GOPAY_PHONE_STEP_DEFINITIONS,
|
||||||
...PLUS_GPC_STEP_DEFINITIONS,
|
...PLUS_GPC_STEP_DEFINITIONS,
|
||||||
|
...PLUS_GPC_PHONE_STEP_DEFINITIONS,
|
||||||
]) {
|
]) {
|
||||||
keyed.set(`${step.id}:${step.key}`, step);
|
keyed.set(`${step.id}:${step.key}`, step);
|
||||||
}
|
}
|
||||||
@@ -317,10 +351,14 @@
|
|||||||
DEFAULT_ACTIVE_FLOW_ID,
|
DEFAULT_ACTIVE_FLOW_ID,
|
||||||
STEP_DEFINITIONS: NORMAL_STEP_DEFINITIONS,
|
STEP_DEFINITIONS: NORMAL_STEP_DEFINITIONS,
|
||||||
NORMAL_STEP_DEFINITIONS,
|
NORMAL_STEP_DEFINITIONS,
|
||||||
|
NORMAL_PHONE_STEP_DEFINITIONS,
|
||||||
PLUS_STEP_DEFINITIONS: PLUS_PAYPAL_STEP_DEFINITIONS,
|
PLUS_STEP_DEFINITIONS: PLUS_PAYPAL_STEP_DEFINITIONS,
|
||||||
PLUS_PAYPAL_STEP_DEFINITIONS,
|
PLUS_PAYPAL_STEP_DEFINITIONS,
|
||||||
|
PLUS_PAYPAL_PHONE_STEP_DEFINITIONS,
|
||||||
PLUS_GOPAY_STEP_DEFINITIONS,
|
PLUS_GOPAY_STEP_DEFINITIONS,
|
||||||
|
PLUS_GOPAY_PHONE_STEP_DEFINITIONS,
|
||||||
PLUS_GPC_STEP_DEFINITIONS,
|
PLUS_GPC_STEP_DEFINITIONS,
|
||||||
|
PLUS_GPC_PHONE_STEP_DEFINITIONS,
|
||||||
SIGNUP_METHOD_EMAIL,
|
SIGNUP_METHOD_EMAIL,
|
||||||
SIGNUP_METHOD_PHONE,
|
SIGNUP_METHOD_PHONE,
|
||||||
getAllSteps,
|
getAllSteps,
|
||||||
|
|||||||
@@ -578,7 +578,7 @@
|
|||||||
const maxPrice = normalizeFiveSimMaxPrice(state.fiveSimMaxPrice);
|
const maxPrice = normalizeFiveSimMaxPrice(state.fiveSimMaxPrice);
|
||||||
const operator = normalizeFiveSimOperator(state.fiveSimOperator);
|
const operator = normalizeFiveSimOperator(state.fiveSimOperator);
|
||||||
if (maxPrice && operator !== DEFAULT_OPERATOR) {
|
if (maxPrice && operator !== DEFAULT_OPERATOR) {
|
||||||
throw new Error('5sim maxPrice only works when operator is "any"; clear the price limit or switch operator to any before buying a number.');
|
throw new Error('5sim 价格上限仅支持运营商为 "any" 时使用;请清空价格上限,或先把运营商切换为 any。');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
query = { api_key: config.apiKey, ...query };
|
query = { api_key: config.apiKey, ...query };
|
||||||
}
|
}
|
||||||
if (!config.fetchImpl) {
|
if (!config.fetchImpl) {
|
||||||
throw new Error('HeroSMS fetch implementation is unavailable.');
|
throw new Error('HeroSMS 网络请求实现不可用。');
|
||||||
}
|
}
|
||||||
const controller = typeof AbortController === 'function' ? new AbortController() : null;
|
const controller = typeof AbortController === 'function' ? new AbortController() : null;
|
||||||
const timeoutId = controller
|
const timeoutId = controller
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
const payload = parsePayload(text);
|
const payload = parsePayload(text);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = new Error(`${actionLabel} failed: ${describePayload(payload) || response.status}`);
|
const error = new Error(`${actionLabel}失败:${describePayload(payload) || response.status}`);
|
||||||
error.payload = payload;
|
error.payload = payload;
|
||||||
error.status = response.status;
|
error.status = response.status;
|
||||||
throw error;
|
throw error;
|
||||||
@@ -131,7 +131,7 @@
|
|||||||
return payload;
|
return payload;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error?.name === 'AbortError') {
|
if (error?.name === 'AbortError') {
|
||||||
throw new Error(`${actionLabel} timed out.`);
|
throw new Error(`${actionLabel}超时。`);
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
async function fetchBalance(state = {}, deps = {}) {
|
async function fetchBalance(state = {}, deps = {}) {
|
||||||
const config = resolveConfig(state, deps);
|
const config = resolveConfig(state, deps);
|
||||||
if (!config.apiKey) {
|
if (!config.apiKey) {
|
||||||
throw new Error('HeroSMS API key is missing. Save it in the side panel before querying balance.');
|
throw new Error('HeroSMS API Key 缺失,请先在侧边栏保存接码 API Key。');
|
||||||
}
|
}
|
||||||
const payload = await fetchPayload(config, { action: 'getBalance' }, 'HeroSMS getBalance');
|
const payload = await fetchPayload(config, { action: 'getBalance' }, 'HeroSMS getBalance');
|
||||||
const balance = Number(String(describePayload(payload)).replace(/^ACCESS_BALANCE:/i, '').trim());
|
const balance = Number(String(describePayload(payload)).replace(/^ACCESS_BALANCE:/i, '').trim());
|
||||||
|
|||||||
@@ -112,7 +112,7 @@
|
|||||||
const definition = getProviderDefinition(providerId);
|
const definition = getProviderDefinition(providerId);
|
||||||
const module = getProviderModule(providerId);
|
const module = getProviderModule(providerId);
|
||||||
if (!module || typeof module.createProvider !== 'function') {
|
if (!module || typeof module.createProvider !== 'function') {
|
||||||
throw new Error(`Phone SMS provider module is not loaded: ${definition.id}`);
|
throw new Error(`接码平台模块未加载:${definition.id}`);
|
||||||
}
|
}
|
||||||
return module.createProvider(deps);
|
return module.createProvider(deps);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,6 +170,9 @@
|
|||||||
'fill-profile',
|
'fill-profile',
|
||||||
'oauth-login',
|
'oauth-login',
|
||||||
'submit-verification-code',
|
'submit-verification-code',
|
||||||
|
'post-login-phone-verification',
|
||||||
|
'bind-email',
|
||||||
|
'fetch-bind-email-code',
|
||||||
'confirm-oauth',
|
'confirm-oauth',
|
||||||
'detect-auth-state',
|
'detect-auth-state',
|
||||||
],
|
],
|
||||||
|
|||||||
+10
-10
@@ -6192,14 +6192,14 @@ async function loadHeroSmsCountries() {
|
|||||||
.filter((entry) => entry.id)
|
.filter((entry) => entry.id)
|
||||||
.sort((left, right) => String(left.label || '').localeCompare(String(right.label || '')));
|
.sort((left, right) => String(left.label || '').localeCompare(String(right.label || '')));
|
||||||
if (!optionItems.length) {
|
if (!optionItems.length) {
|
||||||
throw new Error('empty country list');
|
throw new Error('国家列表为空');
|
||||||
}
|
}
|
||||||
heroSmsCountrySearchTextById.clear();
|
heroSmsCountrySearchTextById.clear();
|
||||||
optionItems.forEach((entry) => heroSmsCountrySearchTextById.set(String(entry.id), entry.searchText));
|
optionItems.forEach((entry) => heroSmsCountrySearchTextById.set(String(entry.id), entry.searchText));
|
||||||
applyOptions(optionItems, selectHeroSmsCountry);
|
applyOptions(optionItems, selectHeroSmsCountry);
|
||||||
applyOptions(optionItems, selectHeroSmsCountryFallback);
|
applyOptions(optionItems, selectHeroSmsCountryFallback);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to load 5sim countries:', error);
|
console.warn('加载 5sim 国家列表失败:', error);
|
||||||
const fallbackItems = FIVE_SIM_SUPPORTED_COUNTRY_ITEMS.map((item) => ({
|
const fallbackItems = FIVE_SIM_SUPPORTED_COUNTRY_ITEMS.map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
label: formatFiveSimCountryDisplayLabel(item.id, item.eng),
|
label: formatFiveSimCountryDisplayLabel(item.id, item.eng),
|
||||||
@@ -6224,7 +6224,7 @@ async function loadHeroSmsCountries() {
|
|||||||
const payload = await response.json();
|
const payload = await response.json();
|
||||||
const countries = Array.isArray(payload?.value) ? payload.value : (Array.isArray(payload) ? payload : []);
|
const countries = Array.isArray(payload?.value) ? payload.value : (Array.isArray(payload) ? payload : []);
|
||||||
if (!countries.length) {
|
if (!countries.length) {
|
||||||
throw new Error('empty country list');
|
throw new Error('国家列表为空');
|
||||||
}
|
}
|
||||||
const optionItems = countries
|
const optionItems = countries
|
||||||
.filter((item) => Number(item?.id) > 0 && (String(item?.eng || '').trim() || String(item?.chn || '').trim()))
|
.filter((item) => Number(item?.id) > 0 && (String(item?.eng || '').trim() || String(item?.chn || '').trim()))
|
||||||
@@ -6240,7 +6240,7 @@ async function loadHeroSmsCountries() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!optionItems.length) {
|
if (!optionItems.length) {
|
||||||
throw new Error('empty country list');
|
throw new Error('国家列表为空');
|
||||||
}
|
}
|
||||||
|
|
||||||
heroSmsCountrySearchTextById.clear();
|
heroSmsCountrySearchTextById.clear();
|
||||||
@@ -6251,7 +6251,7 @@ async function loadHeroSmsCountries() {
|
|||||||
applyOptions(optionItems, selectHeroSmsCountry);
|
applyOptions(optionItems, selectHeroSmsCountry);
|
||||||
applyOptions(optionItems, selectHeroSmsCountryFallback);
|
applyOptions(optionItems, selectHeroSmsCountryFallback);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to load HeroSMS countries:', error);
|
console.warn('加载 HeroSMS 国家列表失败:', error);
|
||||||
const fallbackItems = HERO_SMS_FALLBACK_COUNTRY_ITEMS
|
const fallbackItems = HERO_SMS_FALLBACK_COUNTRY_ITEMS
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
const id = normalizeHeroSmsCountryId(item.id);
|
const id = normalizeHeroSmsCountryId(item.id);
|
||||||
@@ -6620,7 +6620,7 @@ async function loadFiveSimCountries() {
|
|||||||
const items = parseFiveSimCountriesPayload(payload);
|
const items = parseFiveSimCountriesPayload(payload);
|
||||||
applyOptions(items.length ? items : fallbackItems);
|
applyOptions(items.length ? items : fallbackItems);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to load 5sim countries:', error);
|
console.warn('加载 5sim 国家列表失败:', error);
|
||||||
applyOptions(fallbackItems);
|
applyOptions(fallbackItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9378,7 +9378,7 @@ function applySettingsState(state) {
|
|||||||
if (previousPhoneSmsProvider !== restoredPhoneSmsProvider) {
|
if (previousPhoneSmsProvider !== restoredPhoneSmsProvider) {
|
||||||
heroSmsCountrySelectionOrder = [];
|
heroSmsCountrySelectionOrder = [];
|
||||||
loadHeroSmsCountries().catch((error) => {
|
loadHeroSmsCountries().catch((error) => {
|
||||||
console.warn('Failed to reload SMS countries after provider restore:', error);
|
console.warn('恢复接码平台后重新加载国家列表失败:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (inputHeroSmsApiKey) {
|
if (inputHeroSmsApiKey) {
|
||||||
@@ -15491,13 +15491,13 @@ Promise.allSettled([
|
|||||||
const fiveSimResult = results[1];
|
const fiveSimResult = results[1];
|
||||||
const nexSmsResult = results[2];
|
const nexSmsResult = results[2];
|
||||||
if (heroResult?.status === 'rejected') {
|
if (heroResult?.status === 'rejected') {
|
||||||
console.error('Failed to load HeroSMS countries:', heroResult.reason);
|
console.error('加载 HeroSMS 国家列表失败:', heroResult.reason);
|
||||||
}
|
}
|
||||||
if (fiveSimResult?.status === 'rejected') {
|
if (fiveSimResult?.status === 'rejected') {
|
||||||
console.error('Failed to load 5sim countries:', fiveSimResult.reason);
|
console.error('加载 5sim 国家列表失败:', fiveSimResult.reason);
|
||||||
}
|
}
|
||||||
if (nexSmsResult?.status === 'rejected') {
|
if (nexSmsResult?.status === 'rejected') {
|
||||||
console.error('Failed to load NexSMS countries:', nexSmsResult.reason);
|
console.error('加载 NexSMS 国家列表失败:', nexSmsResult.reason);
|
||||||
}
|
}
|
||||||
return restoreState().then(() => {
|
return restoreState().then(() => {
|
||||||
syncPasswordToggleLabel();
|
syncPasswordToggleLabel();
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ test('step 7 retries up to configured limit and then fails', async () => {
|
|||||||
assert.equal(events.completed, 0);
|
assert.equal(events.completed, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('step 7 exits internal retry loop immediately when add-phone is detected', async () => {
|
test('step 7 hands add-phone to the dedicated post-login phone node without internal retry', async () => {
|
||||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||||
const globalScope = {};
|
const globalScope = {};
|
||||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||||
@@ -145,7 +145,7 @@ test('step 7 exits internal retry loop immediately when add-phone is detected',
|
|||||||
const events = {
|
const events = {
|
||||||
refreshCalls: 0,
|
refreshCalls: 0,
|
||||||
sendCalls: 0,
|
sendCalls: 0,
|
||||||
completed: 0,
|
completions: [],
|
||||||
logs: [],
|
logs: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -153,8 +153,8 @@ test('step 7 exits internal retry loop immediately when add-phone is detected',
|
|||||||
addLog: async (message, level = 'info') => {
|
addLog: async (message, level = 'info') => {
|
||||||
events.logs.push({ message, level });
|
events.logs.push({ message, level });
|
||||||
},
|
},
|
||||||
completeNodeFromBackground: async () => {
|
completeNodeFromBackground: async (step, payload) => {
|
||||||
events.completed += 1;
|
events.completions.push({ step, payload });
|
||||||
},
|
},
|
||||||
getErrorMessage: (error) => error?.message || String(error || ''),
|
getErrorMessage: (error) => error?.message || String(error || ''),
|
||||||
getLoginAuthStateLabel: (state) => state || 'unknown',
|
getLoginAuthStateLabel: (state) => state || 'unknown',
|
||||||
@@ -174,21 +174,28 @@ test('step 7 exits internal retry loop immediately when add-phone is detected',
|
|||||||
throwIfStopped: () => {},
|
throwIfStopped: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
await assert.rejects(
|
await executor.executeStep7({ email: 'user@example.com', password: 'secret' });
|
||||||
() => executor.executeStep7({ email: 'user@example.com', password: 'secret' }),
|
|
||||||
/add-phone/
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(events.refreshCalls, 1, 'add-phone should stop further OAuth refresh attempts');
|
assert.equal(events.refreshCalls, 1, 'add-phone should stop further OAuth refresh attempts');
|
||||||
assert.equal(events.sendCalls, 1, 'add-phone should stop after the first failed login attempt');
|
assert.equal(events.sendCalls, 1, 'add-phone should stop after the first failed login attempt');
|
||||||
assert.equal(events.completed, 0);
|
assert.deepStrictEqual(events.completions, [
|
||||||
|
{
|
||||||
|
step: 'oauth-login',
|
||||||
|
payload: {
|
||||||
|
loginVerificationRequestedAt: null,
|
||||||
|
skipLoginVerificationStep: true,
|
||||||
|
addPhonePage: true,
|
||||||
|
directOAuthConsentPage: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
assert.ok(
|
assert.ok(
|
||||||
!events.logs.some(({ message }) => /准备重试/.test(message)),
|
!events.logs.some(({ message }) => /准备重试/.test(message)),
|
||||||
'add-phone failure should not be logged as an internal retryable attempt'
|
'add-phone failure should not be logged as an internal retryable attempt'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('step 7 hands direct add-phone to shared phone verification when enabled', async () => {
|
test('step 7 no longer runs shared phone verification inside oauth-login', async () => {
|
||||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||||
const globalScope = {};
|
const globalScope = {};
|
||||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||||
@@ -239,36 +246,21 @@ test('step 7 hands direct add-phone to shared phone verification when enabled',
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(events.refreshCalls, 1);
|
assert.equal(events.refreshCalls, 1);
|
||||||
assert.deepStrictEqual(events.phoneCalls, [
|
assert.deepStrictEqual(events.phoneCalls, []);
|
||||||
{
|
|
||||||
tabId: 91,
|
|
||||||
pageState: {
|
|
||||||
addPhonePage: true,
|
|
||||||
phoneVerificationPage: false,
|
|
||||||
state: 'add_phone_page',
|
|
||||||
url: 'https://auth.openai.com/add-phone',
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
step: 7,
|
|
||||||
visibleStep: 7,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
assert.deepStrictEqual(events.completions, [
|
assert.deepStrictEqual(events.completions, [
|
||||||
{
|
{
|
||||||
step: 'oauth-login',
|
step: 'oauth-login',
|
||||||
payload: {
|
payload: {
|
||||||
loginVerificationRequestedAt: null,
|
loginVerificationRequestedAt: null,
|
||||||
skipLoginVerificationStep: true,
|
skipLoginVerificationStep: true,
|
||||||
directOAuthConsentPage: true,
|
addPhonePage: true,
|
||||||
phoneVerification: true,
|
directOAuthConsentPage: false,
|
||||||
loginPhoneVerification: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('step 7 direct add-phone stays fatal when phone verification is disabled', async () => {
|
test('step 7 add-phone handoff does not depend on phone verification being enabled', async () => {
|
||||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||||
const globalScope = {};
|
const globalScope = {};
|
||||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||||
@@ -304,15 +296,12 @@ test('step 7 direct add-phone stays fatal when phone verification is disabled',
|
|||||||
throwIfStopped: () => {},
|
throwIfStopped: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
await assert.rejects(
|
await executor.executeStep7({ email: 'user@example.com', password: 'secret', phoneVerificationEnabled: false });
|
||||||
() => executor.executeStep7({ email: 'user@example.com', password: 'secret', phoneVerificationEnabled: false }),
|
|
||||||
/手机号页面.*接码|phone verification/i
|
|
||||||
);
|
|
||||||
assert.equal(events.phoneCalls, 0);
|
assert.equal(events.phoneCalls, 0);
|
||||||
assert.equal(events.completions, 0);
|
assert.equal(events.completions, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('step 7 propagates fatal errors from shared add-phone verification', async () => {
|
test('step 7 ignores obsolete shared add-phone verifier during handoff', async () => {
|
||||||
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
const source = fs.readFileSync('background/steps/oauth-login.js', 'utf8');
|
||||||
const globalScope = {};
|
const globalScope = {};
|
||||||
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
const api = new Function('self', `${source}; return self.MultiPageBackgroundStep7;`)(globalScope);
|
||||||
@@ -348,12 +337,9 @@ test('step 7 propagates fatal errors from shared add-phone verification', async
|
|||||||
throwIfStopped: () => {},
|
throwIfStopped: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
await assert.rejects(
|
await executor.executeStep7({ email: 'user@example.com', password: 'secret', phoneVerificationEnabled: true });
|
||||||
() => executor.executeStep7({ email: 'user@example.com', password: 'secret', phoneVerificationEnabled: true }),
|
assert.equal(events.phoneCalls, 0);
|
||||||
/没有可用手机号/
|
assert.equal(events.completions, 1);
|
||||||
);
|
|
||||||
assert.equal(events.phoneCalls, 1);
|
|
||||||
assert.equal(events.completions, 0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('step 7 starts a new oauth timeout window for each refreshed oauth url', async () => {
|
test('step 7 starts a new oauth timeout window for each refreshed oauth url', async () => {
|
||||||
@@ -382,6 +368,7 @@ test('step 7 starts a new oauth timeout window for each refreshed oauth url', as
|
|||||||
reuseOrCreateTab: async () => {},
|
reuseOrCreateTab: async () => {},
|
||||||
sendToContentScriptResilient: async (_source, _message, options) => ({
|
sendToContentScriptResilient: async (_source, _message, options) => ({
|
||||||
step6Outcome: 'success',
|
step6Outcome: 'success',
|
||||||
|
state: 'verification_page',
|
||||||
usedTimeoutMs: options.timeoutMs,
|
usedTimeoutMs: options.timeoutMs,
|
||||||
}),
|
}),
|
||||||
startOAuthFlowTimeoutWindow: async (payload) => {
|
startOAuthFlowTimeoutWindow: async (payload) => {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
|||||||
assert.equal(calls.resolveOptions.completionStep, 8);
|
assert.equal(calls.resolveOptions.completionStep, 8);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('step 8 keeps phone-registered accounts on email-code flow when page is email verification', async () => {
|
test('step 8 rejects ordinary email verification page in phone login mode', async () => {
|
||||||
const calls = {
|
const calls = {
|
||||||
getMailConfigCalls: 0,
|
getMailConfigCalls: 0,
|
||||||
helperCalls: [],
|
helperCalls: [],
|
||||||
@@ -159,18 +159,21 @@ test('step 8 keeps phone-registered accounts on email-code flow when page is ema
|
|||||||
throwIfStopped: () => {},
|
throwIfStopped: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
await executor.executeStep8({
|
await assert.rejects(
|
||||||
visibleStep: 8,
|
() => executor.executeStep8({
|
||||||
accountIdentifierType: 'phone',
|
visibleStep: 8,
|
||||||
signupPhoneCompletedActivation: {
|
accountIdentifierType: 'phone',
|
||||||
activationId: 'signup-done',
|
signupPhoneCompletedActivation: {
|
||||||
phoneNumber: '66959916439',
|
activationId: 'signup-done',
|
||||||
},
|
phoneNumber: '66959916439',
|
||||||
oauthUrl: 'https://oauth.example/latest',
|
},
|
||||||
});
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
}),
|
||||||
|
/手机号注册模式只允许处理手机登录验证码/
|
||||||
|
);
|
||||||
|
|
||||||
assert.equal(calls.getMailConfigCalls, 1);
|
assert.equal(calls.getMailConfigCalls, 0);
|
||||||
assert.equal(calls.resolveCalls, 1);
|
assert.equal(calls.resolveCalls, 0);
|
||||||
assert.deepStrictEqual(calls.helperCalls, []);
|
assert.deepStrictEqual(calls.helperCalls, []);
|
||||||
assert.deepStrictEqual(calls.completions, []);
|
assert.deepStrictEqual(calls.completions, []);
|
||||||
});
|
});
|
||||||
@@ -260,9 +263,154 @@ test('step 8 routes only a real phone verification page through sms helper', asy
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('step 8 submits add-email before polling the email verification code', async () => {
|
test('post-login phone verification completes only on phone pages', async () => {
|
||||||
|
const calls = {
|
||||||
|
helperCalls: [],
|
||||||
|
completions: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const executor = api.createStep8Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeNodeFromBackground: async (step, payload) => {
|
||||||
|
calls.completions.push({ step, payload });
|
||||||
|
},
|
||||||
|
ensureStep8VerificationPageReady: async () => {
|
||||||
|
throw new Error('post-login phone step should inspect auth state directly');
|
||||||
|
},
|
||||||
|
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||||
|
getState: async () => ({}),
|
||||||
|
getTabId: async () => 1,
|
||||||
|
phoneVerificationHelpers: {
|
||||||
|
completePhoneVerificationFlow: async (tabId, pageState, options) => {
|
||||||
|
calls.helperCalls.push({ tabId, pageState, options });
|
||||||
|
return { code: '112233' };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reuseOrCreateTab: async () => 1,
|
||||||
|
sendToContentScriptResilient: async () => ({
|
||||||
|
state: 'add_phone_page',
|
||||||
|
url: 'https://auth.openai.com/add-phone',
|
||||||
|
}),
|
||||||
|
setState: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executePostLoginPhoneVerification({
|
||||||
|
visibleStep: 9,
|
||||||
|
nodeId: 'post-login-phone-verification',
|
||||||
|
phoneVerificationEnabled: true,
|
||||||
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(calls.helperCalls, [
|
||||||
|
{
|
||||||
|
tabId: 1,
|
||||||
|
pageState: {
|
||||||
|
state: 'add_phone_page',
|
||||||
|
url: 'https://auth.openai.com/add-phone',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
step: 9,
|
||||||
|
visibleStep: 9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
assert.deepStrictEqual(calls.completions, [
|
||||||
|
{
|
||||||
|
step: 'post-login-phone-verification',
|
||||||
|
payload: {
|
||||||
|
phoneVerification: true,
|
||||||
|
postLoginPhoneVerification: true,
|
||||||
|
code: '112233',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('post-login phone verification skips on OAuth consent and errors when disabled', async () => {
|
||||||
|
const completions = [];
|
||||||
|
const executor = api.createStep8Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeNodeFromBackground: async (step, payload) => {
|
||||||
|
completions.push({ step, payload });
|
||||||
|
},
|
||||||
|
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||||
|
getTabId: async () => 1,
|
||||||
|
phoneVerificationHelpers: {
|
||||||
|
completePhoneVerificationFlow: async () => {
|
||||||
|
throw new Error('OAuth consent should not call phone helper');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reuseOrCreateTab: async () => 1,
|
||||||
|
sendToContentScriptResilient: async () => ({ state: 'oauth_consent_page' }),
|
||||||
|
setState: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executePostLoginPhoneVerification({
|
||||||
|
visibleStep: 9,
|
||||||
|
nodeId: 'post-login-phone-verification',
|
||||||
|
phoneVerificationEnabled: true,
|
||||||
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepStrictEqual(completions, [
|
||||||
|
{
|
||||||
|
step: 'post-login-phone-verification',
|
||||||
|
payload: {
|
||||||
|
directOAuthConsentPage: true,
|
||||||
|
phoneVerification: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const disabledExecutor = api.createStep8Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||||
|
getTabId: async () => 1,
|
||||||
|
phoneVerificationHelpers: {
|
||||||
|
completePhoneVerificationFlow: async () => {
|
||||||
|
throw new Error('disabled phone verification should not call helper');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reuseOrCreateTab: async () => 1,
|
||||||
|
sendToContentScriptResilient: async () => ({
|
||||||
|
state: 'phone_verification_page',
|
||||||
|
url: 'https://auth.openai.com/phone-verification',
|
||||||
|
}),
|
||||||
|
setState: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => disabledExecutor.executePostLoginPhoneVerification({
|
||||||
|
visibleStep: 9,
|
||||||
|
phoneVerificationEnabled: false,
|
||||||
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
}),
|
||||||
|
/手机接码未开启/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('step 8 defers add-email page to the dedicated bind-email node in phone mode', async () => {
|
||||||
const calls = {
|
const calls = {
|
||||||
contentMessages: [],
|
contentMessages: [],
|
||||||
|
completions: [],
|
||||||
resolvedStates: [],
|
resolvedStates: [],
|
||||||
setStates: [],
|
setStates: [],
|
||||||
mailStates: [],
|
mailStates: [],
|
||||||
@@ -277,6 +425,9 @@ test('step 8 submits add-email before polling the email verification code', asyn
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
completeNodeFromBackground: async (step, payload) => {
|
||||||
|
calls.completions.push({ step, payload });
|
||||||
|
},
|
||||||
confirmCustomVerificationStepBypass: async () => {},
|
confirmCustomVerificationStepBypass: async () => {},
|
||||||
ensureStep8VerificationPageReady: async () => ({ state: 'add_email_page', url: 'https://auth.openai.com/add-email' }),
|
ensureStep8VerificationPageReady: async () => ({ state: 'add_email_page', url: 'https://auth.openai.com/add-email' }),
|
||||||
getOAuthFlowRemainingMs: async () => 5000,
|
getOAuthFlowRemainingMs: async () => 5000,
|
||||||
@@ -339,29 +490,320 @@ test('step 8 submits add-email before polling the email verification code', asyn
|
|||||||
oauthUrl: 'https://oauth.example/latest',
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(calls.contentMessages.length, 1);
|
assert.equal(calls.contentMessages.length, 0);
|
||||||
assert.equal(calls.resolvedStates.length, 1);
|
assert.equal(calls.resolvedStates.length, 0);
|
||||||
assert.equal(calls.resolveOptions.preserveAccountIdentity, true);
|
assert.equal(calls.persistCalls.length, 0);
|
||||||
assert.equal(calls.persistCalls.length, 1);
|
assert.equal(calls.mailStates.length, 0);
|
||||||
assert.equal(calls.persistCalls[0].email, 'new.user@example.com');
|
|
||||||
assert.equal(calls.persistCalls[0].options.preserveAccountIdentity, true);
|
|
||||||
assert.equal(calls.persistCalls[0].options.source, 'step8_add_email');
|
|
||||||
assert.equal(calls.mailStates[0].email, 'new.user@example.com');
|
|
||||||
assert.equal(calls.resolvedVerification.state.email, 'new.user@example.com');
|
|
||||||
assert.equal(calls.resolvedVerification.options.targetEmail, 'new.user@example.com');
|
|
||||||
assert.deepStrictEqual(calls.setStates, [
|
assert.deepStrictEqual(calls.setStates, [
|
||||||
{
|
{
|
||||||
step8VerificationTargetEmail: 'new.user@example.com',
|
step8VerificationTargetEmail: '',
|
||||||
|
loginVerificationRequestedAt: null,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
assert.deepStrictEqual(calls.completions, [
|
||||||
|
{
|
||||||
|
step: 'fetch-login-code',
|
||||||
|
payload: {
|
||||||
|
loginVerificationRequestedAt: null,
|
||||||
|
skipLoginVerificationStep: true,
|
||||||
|
addEmailPage: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('step 8 reruns step 7 with preserved phone login identity after add-email verification failure', async () => {
|
test('bind-email submits add-email and requires an email verification page', async () => {
|
||||||
|
const calls = {
|
||||||
|
contentMessages: [],
|
||||||
|
completions: [],
|
||||||
|
persistCalls: [],
|
||||||
|
setStates: [],
|
||||||
|
};
|
||||||
|
let runtimeState = {
|
||||||
|
email: '',
|
||||||
|
password: 'secret',
|
||||||
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
};
|
||||||
|
|
||||||
|
const executor = api.createStep8Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeNodeFromBackground: async (step, payload) => {
|
||||||
|
calls.completions.push({ step, payload });
|
||||||
|
},
|
||||||
|
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||||
|
getState: async () => ({ ...runtimeState }),
|
||||||
|
getTabId: async () => 1,
|
||||||
|
persistRegistrationEmailState: async (state, email, options) => {
|
||||||
|
calls.persistCalls.push({ state, email, options });
|
||||||
|
runtimeState = {
|
||||||
|
...runtimeState,
|
||||||
|
email,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
resolveSignupEmailForFlow: async (_state, options = {}) => {
|
||||||
|
assert.equal(options.preserveAccountIdentity, true);
|
||||||
|
return 'bind.user@example.com';
|
||||||
|
},
|
||||||
|
reuseOrCreateTab: async () => 1,
|
||||||
|
sendToContentScriptResilient: async (_source, message) => {
|
||||||
|
if (message.type === 'GET_LOGIN_AUTH_STATE') {
|
||||||
|
return { state: 'add_email_page', url: 'https://auth.openai.com/add-email' };
|
||||||
|
}
|
||||||
|
calls.contentMessages.push(message);
|
||||||
|
assert.equal(message.type, 'SUBMIT_ADD_EMAIL');
|
||||||
|
assert.equal(message.payload.email, 'bind.user@example.com');
|
||||||
|
return {
|
||||||
|
submitted: true,
|
||||||
|
displayedEmail: 'bind.user@example.com',
|
||||||
|
url: 'https://auth.openai.com/email-verification',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
setState: async (payload) => {
|
||||||
|
calls.setStates.push(payload);
|
||||||
|
runtimeState = {
|
||||||
|
...runtimeState,
|
||||||
|
...payload,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await executor.executeBindEmail({
|
||||||
|
visibleStep: 9,
|
||||||
|
nodeId: 'bind-email',
|
||||||
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(calls.contentMessages.length, 1);
|
||||||
|
assert.equal(calls.persistCalls.length, 1);
|
||||||
|
assert.equal(calls.persistCalls[0].options.source, 'bind_email');
|
||||||
|
assert.deepStrictEqual(calls.completions, [
|
||||||
|
{
|
||||||
|
step: 'bind-email',
|
||||||
|
payload: {
|
||||||
|
bindEmailSubmitted: true,
|
||||||
|
email: 'bind.user@example.com',
|
||||||
|
step8VerificationTargetEmail: 'bind.user@example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('bind-email skips on OAuth consent and rejects direct OAuth after submit', async () => {
|
||||||
|
const completions = [];
|
||||||
|
const skipExecutor = api.createStep8Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeNodeFromBackground: async (step, payload) => {
|
||||||
|
completions.push({ step, payload });
|
||||||
|
},
|
||||||
|
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||||
|
getTabId: async () => 1,
|
||||||
|
reuseOrCreateTab: async () => 1,
|
||||||
|
sendToContentScriptResilient: async () => ({ state: 'oauth_consent_page' }),
|
||||||
|
setState: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await skipExecutor.executeBindEmail({
|
||||||
|
visibleStep: 9,
|
||||||
|
nodeId: 'bind-email',
|
||||||
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(completions, [
|
||||||
|
{
|
||||||
|
step: 'bind-email',
|
||||||
|
payload: {
|
||||||
|
directOAuthConsentPage: true,
|
||||||
|
bindEmailSubmitted: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const directOauthExecutor = api.createStep8Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||||
|
getState: async () => ({ email: '', oauthUrl: 'https://oauth.example/latest' }),
|
||||||
|
getTabId: async () => 1,
|
||||||
|
resolveSignupEmailForFlow: async () => 'bind.user@example.com',
|
||||||
|
reuseOrCreateTab: async () => 1,
|
||||||
|
sendToContentScriptResilient: async (_source, message) => {
|
||||||
|
if (message.type === 'GET_LOGIN_AUTH_STATE') {
|
||||||
|
return { state: 'add_email_page', url: 'https://auth.openai.com/add-email' };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
submitted: true,
|
||||||
|
directOAuthConsentPage: true,
|
||||||
|
url: 'https://auth.openai.com/authorize',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
setState: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => directOauthExecutor.executeBindEmail({
|
||||||
|
visibleStep: 9,
|
||||||
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
}),
|
||||||
|
/绑定邮箱提交后必须进入邮箱验证码页/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fetch-bind-email-code polls only after bind-email submitted', async () => {
|
||||||
|
const calls = {
|
||||||
|
resolveOptions: null,
|
||||||
|
setStates: [],
|
||||||
|
};
|
||||||
|
const realDateNow = Date.now;
|
||||||
|
Date.now = () => 222000;
|
||||||
|
|
||||||
|
const executor = api.createStep8Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
confirmCustomVerificationStepBypass: async () => {},
|
||||||
|
getOAuthFlowRemainingMs: async () => 9000,
|
||||||
|
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||||
|
getMailConfig: () => ({
|
||||||
|
provider: 'qq',
|
||||||
|
label: 'QQ 邮箱',
|
||||||
|
source: 'mail-qq',
|
||||||
|
url: 'https://mail.qq.com',
|
||||||
|
navigateOnReuse: false,
|
||||||
|
}),
|
||||||
|
getTabId: async () => 1,
|
||||||
|
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||||
|
isTabAlive: async () => true,
|
||||||
|
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||||
|
resolveVerificationStep: async (_step, _state, _mail, options) => {
|
||||||
|
calls.resolveOptions = options;
|
||||||
|
},
|
||||||
|
reuseOrCreateTab: async () => 1,
|
||||||
|
sendToContentScriptResilient: async () => ({
|
||||||
|
state: 'verification_page',
|
||||||
|
displayedEmail: 'bind.user@example.com',
|
||||||
|
url: 'https://auth.openai.com/email-verification',
|
||||||
|
}),
|
||||||
|
setState: async (payload) => {
|
||||||
|
calls.setStates.push(payload);
|
||||||
|
},
|
||||||
|
shouldUseCustomRegistrationEmail: () => false,
|
||||||
|
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000,
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await executor.executeFetchBindEmailCode({
|
||||||
|
visibleStep: 10,
|
||||||
|
nodeId: 'fetch-bind-email-code',
|
||||||
|
bindEmailSubmitted: true,
|
||||||
|
email: 'bind.user@example.com',
|
||||||
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
Date.now = realDateNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(calls.resolveOptions.completionStep, 10);
|
||||||
|
assert.equal(calls.resolveOptions.sessionKey, '10:222000');
|
||||||
|
assert.equal(calls.resolveOptions.targetEmail, 'bind.user@example.com');
|
||||||
|
assert.deepStrictEqual(calls.setStates, [
|
||||||
|
{
|
||||||
|
step8VerificationTargetEmail: 'bind.user@example.com',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fetch-bind-email-code rejects unexpected pages after bind-email submitted', async () => {
|
||||||
|
const executor = api.createStep8Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||||
|
getTabId: async () => 1,
|
||||||
|
reuseOrCreateTab: async () => 1,
|
||||||
|
sendToContentScriptResilient: async () => ({
|
||||||
|
state: 'oauth_consent_page',
|
||||||
|
url: 'https://auth.openai.com/authorize',
|
||||||
|
}),
|
||||||
|
setState: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
() => executor.executeFetchBindEmailCode({
|
||||||
|
visibleStep: 10,
|
||||||
|
bindEmailSubmitted: true,
|
||||||
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
}),
|
||||||
|
/绑定邮箱提交后不应直接进入 OAuth 授权页/
|
||||||
|
);
|
||||||
|
|
||||||
|
const notSubmittedCompletions = [];
|
||||||
|
const skipExecutor = api.createStep8Executor({
|
||||||
|
addLog: async () => {},
|
||||||
|
chrome: {
|
||||||
|
tabs: {
|
||||||
|
update: async () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
completeNodeFromBackground: async (step, payload) => {
|
||||||
|
notSubmittedCompletions.push({ step, payload });
|
||||||
|
},
|
||||||
|
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||||
|
getTabId: async () => 1,
|
||||||
|
reuseOrCreateTab: async () => 1,
|
||||||
|
sendToContentScriptResilient: async () => ({ state: 'oauth_consent_page' }),
|
||||||
|
setState: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await skipExecutor.executeFetchBindEmailCode({
|
||||||
|
visibleStep: 10,
|
||||||
|
nodeId: 'fetch-bind-email-code',
|
||||||
|
bindEmailSubmitted: false,
|
||||||
|
oauthUrl: 'https://oauth.example/latest',
|
||||||
|
});
|
||||||
|
assert.deepStrictEqual(notSubmittedCompletions, [
|
||||||
|
{
|
||||||
|
step: 'fetch-bind-email-code',
|
||||||
|
payload: {
|
||||||
|
directOAuthConsentPage: true,
|
||||||
|
bindEmailCodeSkipped: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('step 8 does not submit or recover add-email inside fetch-login-code', async () => {
|
||||||
const calls = {
|
const calls = {
|
||||||
ensureCalls: 0,
|
ensureCalls: 0,
|
||||||
resolveCalls: 0,
|
resolveCalls: 0,
|
||||||
rerunStates: [],
|
rerunStates: [],
|
||||||
contentMessages: [],
|
contentMessages: [],
|
||||||
|
completions: [],
|
||||||
};
|
};
|
||||||
let runtimeState = {
|
let runtimeState = {
|
||||||
visibleStep: 8,
|
visibleStep: 8,
|
||||||
@@ -388,6 +830,9 @@ test('step 8 reruns step 7 with preserved phone login identity after add-email v
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
|
completeNodeFromBackground: async (step, payload) => {
|
||||||
|
calls.completions.push({ step, payload });
|
||||||
|
},
|
||||||
confirmCustomVerificationStepBypass: async () => {},
|
confirmCustomVerificationStepBypass: async () => {},
|
||||||
ensureStep8VerificationPageReady: async () => {
|
ensureStep8VerificationPageReady: async () => {
|
||||||
calls.ensureCalls += 1;
|
calls.ensureCalls += 1;
|
||||||
@@ -463,16 +908,23 @@ test('step 8 reruns step 7 with preserved phone login identity after add-email v
|
|||||||
|
|
||||||
await executor.executeStep8({ ...runtimeState });
|
await executor.executeStep8({ ...runtimeState });
|
||||||
|
|
||||||
assert.equal(calls.contentMessages.length, 1);
|
assert.equal(calls.ensureCalls, 1);
|
||||||
assert.equal(calls.resolveCalls, 2);
|
assert.equal(calls.contentMessages.length, 0);
|
||||||
assert.equal(calls.rerunStates.length, 1);
|
assert.equal(calls.resolveCalls, 0);
|
||||||
assert.equal(calls.rerunStates[0].email, 'new.user@example.com');
|
assert.equal(calls.rerunStates.length, 0);
|
||||||
assert.equal(calls.rerunStates[0].accountIdentifierType, 'phone');
|
assert.deepStrictEqual(calls.completions, [
|
||||||
assert.equal(calls.rerunStates[0].accountIdentifier, '+447780579093');
|
{
|
||||||
assert.equal(calls.rerunStates[0].signupPhoneNumber, '+447780579093');
|
step: 'fetch-login-code',
|
||||||
|
payload: {
|
||||||
|
loginVerificationRequestedAt: null,
|
||||||
|
skipLoginVerificationStep: true,
|
||||||
|
addEmailPage: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('step 8 add-email rereads persisted phone identity before rerunning step 7', async () => {
|
test('step 8 rejects add-email page in email login mode', async () => {
|
||||||
const calls = {
|
const calls = {
|
||||||
resolveCalls: 0,
|
resolveCalls: 0,
|
||||||
rerunStates: [],
|
rerunStates: [],
|
||||||
@@ -563,16 +1015,14 @@ test('step 8 add-email rereads persisted phone identity before rerunning step 7'
|
|||||||
|
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
() => executor.executeStep8({ ...runtimeState }),
|
() => executor.executeStep8({ ...runtimeState }),
|
||||||
/STEP8_RESTART_STEP7::/
|
/邮箱注册模式不应进入添加邮箱页/
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(calls.resolveCalls, 2);
|
assert.equal(calls.resolveCalls, 0);
|
||||||
assert.equal(calls.rerunStates.length, 1);
|
assert.equal(calls.rerunStates.length, 0);
|
||||||
assert.equal(calls.rerunStates[0].accountIdentifierType, 'phone');
|
|
||||||
assert.equal(calls.rerunStates[0].signupPhoneNumber, '+447780579093');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('step 8 email_in_use recovery preserves the previous registration baseline', async () => {
|
test('step 8 does not run add-email email_in_use recovery in email login mode', async () => {
|
||||||
const calls = {
|
const calls = {
|
||||||
contentCalls: 0,
|
contentCalls: 0,
|
||||||
setStates: [],
|
setStates: [],
|
||||||
@@ -588,15 +1038,6 @@ test('step 8 email_in_use recovery preserves the previous registration baseline'
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
buildRegistrationEmailStateUpdates: () => ({
|
|
||||||
email: null,
|
|
||||||
registrationEmailState: {
|
|
||||||
current: '',
|
|
||||||
previous: 'old.user@example.com',
|
|
||||||
source: 'step8_recovery',
|
|
||||||
updatedAt: 123,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||||
confirmCustomVerificationStepBypass: async () => {},
|
confirmCustomVerificationStepBypass: async () => {},
|
||||||
ensureStep8VerificationPageReady: async () => ({
|
ensureStep8VerificationPageReady: async () => ({
|
||||||
@@ -653,30 +1094,24 @@ test('step 8 email_in_use recovery preserves the previous registration baseline'
|
|||||||
throwIfStopped: () => {},
|
throwIfStopped: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
await executor.executeStep8({
|
await assert.rejects(
|
||||||
email: 'old.user@example.com',
|
() => executor.executeStep8({
|
||||||
registrationEmailState: {
|
email: 'old.user@example.com',
|
||||||
current: 'old.user@example.com',
|
registrationEmailState: {
|
||||||
previous: 'old.user@example.com',
|
current: 'old.user@example.com',
|
||||||
source: 'generated:duck',
|
previous: 'old.user@example.com',
|
||||||
updatedAt: 1,
|
source: 'generated:duck',
|
||||||
},
|
updatedAt: 1,
|
||||||
oauthUrl: 'https://auth.openai.com/add-email',
|
},
|
||||||
password: 'secret',
|
oauthUrl: 'https://auth.openai.com/add-email',
|
||||||
visibleStep: 8,
|
password: 'secret',
|
||||||
});
|
visibleStep: 8,
|
||||||
|
}),
|
||||||
|
/邮箱注册模式不应进入添加邮箱页/
|
||||||
|
);
|
||||||
|
|
||||||
assert.deepStrictEqual(calls.setStates[0], {
|
assert.equal(calls.contentCalls, 0);
|
||||||
email: null,
|
assert.deepStrictEqual(calls.setStates, []);
|
||||||
registrationEmailState: {
|
|
||||||
current: '',
|
|
||||||
previous: 'old.user@example.com',
|
|
||||||
source: 'step8_recovery',
|
|
||||||
updatedAt: 123,
|
|
||||||
},
|
|
||||||
step8VerificationTargetEmail: '',
|
|
||||||
loginVerificationRequestedAt: null,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Plus login-code step reuses step 8 verification logic but completes visible step 11', async () => {
|
test('Plus login-code step reuses step 8 verification logic but completes visible step 11', async () => {
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ test('5sim provider rejects maxPrice with custom operator before buying', async
|
|||||||
fiveSimMaxPrice: '12',
|
fiveSimMaxPrice: '12',
|
||||||
fiveSimOperator: 'virtual21',
|
fiveSimOperator: 'virtual21',
|
||||||
}),
|
}),
|
||||||
/maxPrice only works when operator is "any"/
|
/价格上限仅支持运营商为 "any"/
|
||||||
);
|
);
|
||||||
assert.deepStrictEqual(requests, []);
|
assert.deepStrictEqual(requests, []);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,5 +40,5 @@ test('phone sms provider registry normalizes ids, order and labels consistently'
|
|||||||
registry.createProvider('5sim', { foo: 1 }),
|
registry.createProvider('5sim', { foo: 1 }),
|
||||||
{ provider: '5sim', deps: { foo: 1 } }
|
{ provider: '5sim', deps: { foo: 1 } }
|
||||||
);
|
);
|
||||||
assert.throws(() => registry.createProvider('nexsms'), /Phone SMS provider module is not loaded: nexsms/);
|
assert.throws(() => registry.createProvider('nexsms'), /接码平台模块未加载:nexsms/);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1634,7 +1634,7 @@ test('phone verification helper fails fast when HeroSMS country list is empty',
|
|||||||
heroSmsCountryId: 0,
|
heroSmsCountryId: 0,
|
||||||
heroSmsCountryFallback: [],
|
heroSmsCountryFallback: [],
|
||||||
}),
|
}),
|
||||||
/HeroSMS countries are empty/i
|
/HeroSMS 未选择国家/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1968,7 +1968,7 @@ test('phone verification helper rejects HeroSMS WRONG_MAX_PRICE below configured
|
|||||||
|
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key', heroSmsMinPrice: '0.07' }),
|
helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key', heroSmsMinPrice: '0.07' }),
|
||||||
/below configured minPrice=0\.07/i
|
/低于当前配置的最低购买价 0\.07/
|
||||||
);
|
);
|
||||||
|
|
||||||
const actions = requests.map((requestUrl) => `${requestUrl.searchParams.get('action')}:${requestUrl.searchParams.get('maxPrice') || ''}`);
|
const actions = requests.map((requestUrl) => `${requestUrl.searchParams.get('action')}:${requestUrl.searchParams.get('maxPrice') || ''}`);
|
||||||
@@ -2002,7 +2002,7 @@ test('phone verification helper rejects reversed price range before fetching pri
|
|||||||
heroSmsMinPrice: '0.2',
|
heroSmsMinPrice: '0.2',
|
||||||
heroSmsMaxPrice: '0.1',
|
heroSmsMaxPrice: '0.1',
|
||||||
}),
|
}),
|
||||||
/price range is invalid/i
|
/价格区间无效/
|
||||||
);
|
);
|
||||||
assert.equal(fetchCalled, false);
|
assert.equal(fetchCalled, false);
|
||||||
});
|
});
|
||||||
@@ -2039,7 +2039,7 @@ test('phone verification helper stops when WRONG_MAX_PRICE exceeds configured ma
|
|||||||
|
|
||||||
await assert.rejects(
|
await assert.rejects(
|
||||||
helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key', heroSmsMaxPrice: '0.05' }),
|
helpers.requestPhoneActivation({ heroSmsApiKey: 'demo-key', heroSmsMaxPrice: '0.05' }),
|
||||||
/exceeds configured maxPrice=0\.05/i
|
/超过当前配置的价格上限 0\.05/
|
||||||
);
|
);
|
||||||
|
|
||||||
const actions = requests.map((requestUrl) => `${requestUrl.searchParams.get('action')}:${requestUrl.searchParams.get('maxPrice') || ''}`);
|
const actions = requests.map((requestUrl) => `${requestUrl.searchParams.get('action')}:${requestUrl.searchParams.get('maxPrice') || ''}`);
|
||||||
@@ -2412,7 +2412,7 @@ test('phone verification helper rejects 5sim maxPrice with custom operator befor
|
|||||||
fiveSimMaxPrice: '0.1',
|
fiveSimMaxPrice: '0.1',
|
||||||
heroSmsActivationRetryRounds: 1,
|
heroSmsActivationRetryRounds: 1,
|
||||||
}),
|
}),
|
||||||
/maxPrice only works when operator is "any"/
|
/价格上限仅支持运营商为 "any"/
|
||||||
);
|
);
|
||||||
assert.deepStrictEqual(requests, []);
|
assert.deepStrictEqual(requests, []);
|
||||||
});
|
});
|
||||||
@@ -8105,14 +8105,14 @@ test('phone verification helper logs no-supply diagnostics with consecutive stre
|
|||||||
phoneVerificationPage: false,
|
phoneVerificationPage: false,
|
||||||
url: 'https://auth.openai.com/add-phone',
|
url: 'https://auth.openai.com/add-phone',
|
||||||
}),
|
}),
|
||||||
/all provider candidates failed to acquire number/i
|
/所有接码平台候选均未获取到手机号/
|
||||||
);
|
);
|
||||||
|
|
||||||
await runOnce();
|
await runOnce();
|
||||||
await runOnce();
|
await runOnce();
|
||||||
|
|
||||||
const diagnosticsLogs = logs
|
const diagnosticsLogs = logs
|
||||||
.filter((entry) => String(entry.message || '').includes('diagnostics: 无号连续失败'));
|
.filter((entry) => String(entry.message || '').includes('步骤 9 诊断:无号连续失败'));
|
||||||
|
|
||||||
assert.equal(diagnosticsLogs.length >= 2, true);
|
assert.equal(diagnosticsLogs.length >= 2, true);
|
||||||
assert.equal(diagnosticsLogs.every((entry) => entry.options?.step === 9), true);
|
assert.equal(diagnosticsLogs.every((entry) => entry.options?.step === 9), true);
|
||||||
@@ -8120,15 +8120,15 @@ test('phone verification helper logs no-supply diagnostics with consecutive stre
|
|||||||
assert.equal(diagnosticsLogs.some((entry) => entry.message.includes('无号连续失败 1 次')), true);
|
assert.equal(diagnosticsLogs.some((entry) => entry.message.includes('无号连续失败 1 次')), true);
|
||||||
assert.equal(diagnosticsLogs.some((entry) => entry.message.includes('无号连续失败 2 次')), true);
|
assert.equal(diagnosticsLogs.some((entry) => entry.message.includes('无号连续失败 2 次')), true);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
diagnosticsLogs.some((entry) => entry.message.includes('priceRange=0.04~0.06')),
|
diagnosticsLogs.some((entry) => entry.message.includes('价格区间=0.04~0.06')),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
diagnosticsLogs.some((entry) => entry.message.includes('minPrice=0.04')),
|
diagnosticsLogs.some((entry) => entry.message.includes('最低价=0.04')),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
diagnosticsLogs.some((entry) => entry.message.includes('maxPrice=0.06')),
|
diagnosticsLogs.some((entry) => entry.message.includes('最高价=0.06')),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
@@ -8139,6 +8139,63 @@ test('phone verification helper logs no-supply diagnostics with consecutive stre
|
|||||||
assert.equal(requests.some((entry) => entry.searchParams.get('action') === 'getNumber'), true);
|
assert.equal(requests.some((entry) => entry.searchParams.get('action') === 'getNumber'), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('phone verification helper localizes HeroSMS BAD_KEY acquisition failure', async () => {
|
||||||
|
let currentState = {
|
||||||
|
heroSmsApiKey: 'bad-key',
|
||||||
|
heroSmsCountryId: 52,
|
||||||
|
heroSmsCountryLabel: 'Thailand',
|
||||||
|
heroSmsCountryFallback: [],
|
||||||
|
currentPhoneActivation: null,
|
||||||
|
reusablePhoneActivation: null,
|
||||||
|
phoneVerificationReplacementLimit: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const helpers = api.createPhoneVerificationHelpers({
|
||||||
|
addLog: async () => {},
|
||||||
|
ensureStep8SignupPageReady: async () => {},
|
||||||
|
fetchImpl: async (url) => {
|
||||||
|
const action = new URL(url).searchParams.get('action');
|
||||||
|
if (action === 'getPrices') {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
text: async () => buildHeroSmsPricesPayload({ country: '52', cost: 0.05, count: 20 }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (action === 'getNumber' || action === 'getNumberV2') {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
text: async () => 'BAD_KEY',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||||
|
},
|
||||||
|
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||||
|
getState: async () => ({ ...currentState }),
|
||||||
|
sendToContentScriptResilient: async (_source, message) => {
|
||||||
|
throw new Error(`Unexpected content-script message: ${message.type}`);
|
||||||
|
},
|
||||||
|
setState: async (updates) => {
|
||||||
|
currentState = { ...currentState, ...updates };
|
||||||
|
},
|
||||||
|
sleepWithStop: async () => {},
|
||||||
|
throwIfStopped: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
await assert.rejects(
|
||||||
|
helpers.completePhoneVerificationFlow(1, {
|
||||||
|
addPhonePage: true,
|
||||||
|
phoneVerificationPage: false,
|
||||||
|
url: 'https://auth.openai.com/add-phone',
|
||||||
|
}),
|
||||||
|
(error) => {
|
||||||
|
assert.match(error.message, /步骤 9:所有接码平台候选均未获取到手机号/);
|
||||||
|
assert.match(error.message, /HeroSMS:获取手机号失败:API Key 无效(BAD_KEY)/);
|
||||||
|
assert.doesNotMatch(error.message, /all provider candidates failed|failed to acquire number|HeroSMS getNumber failed/i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('phone verification helper routes 5sim buy, check, and finish by current activation provider', async () => {
|
test('phone verification helper routes 5sim buy, check, and finish by current activation provider', async () => {
|
||||||
const requests = [];
|
const requests = [];
|
||||||
let currentState = {
|
let currentState = {
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ test('shared source registry exposes canonical source, alias, detection, and rea
|
|||||||
assert.equal(registry.shouldReportReadyForFrame('unknown-source', false), false);
|
assert.equal(registry.shouldReportReadyForFrame('unknown-source', false), false);
|
||||||
assert.equal(registry.getCleanupOwnerSource('oauth-localhost-callback'), 'openai-auth');
|
assert.equal(registry.getCleanupOwnerSource('oauth-localhost-callback'), 'openai-auth');
|
||||||
assert.equal(registry.driverAcceptsCommand('openai-auth', 'submit-signup-email'), true);
|
assert.equal(registry.driverAcceptsCommand('openai-auth', 'submit-signup-email'), true);
|
||||||
|
assert.equal(registry.driverAcceptsCommand('openai-auth', 'post-login-phone-verification'), true);
|
||||||
|
assert.equal(registry.driverAcceptsCommand('openai-auth', 'bind-email'), true);
|
||||||
|
assert.equal(registry.driverAcceptsCommand('openai-auth', 'fetch-bind-email-code'), true);
|
||||||
assert.equal(registry.driverAcceptsCommand('content/platform-panel', 'platform-verify'), true);
|
assert.equal(registry.driverAcceptsCommand('content/platform-panel', 'platform-verify'), true);
|
||||||
assert.equal(registry.driverAcceptsCommand('openai-auth', 'platform-verify'), false);
|
assert.equal(registry.driverAcceptsCommand('openai-auth', 'platform-verify'), false);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
|||||||
const gpcSteps = api.getSteps({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' });
|
const gpcSteps = api.getSteps({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' });
|
||||||
|
|
||||||
assert.equal(Array.isArray(steps), true);
|
assert.equal(Array.isArray(steps), true);
|
||||||
assert.equal(steps.length, 10);
|
assert.equal(steps.length, 11);
|
||||||
assert.equal(steps.every((step) => step.flowId === 'openai'), true);
|
assert.equal(steps.every((step) => step.flowId === 'openai'), true);
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
steps.map((step) => step.order),
|
steps.map((step) => step.order),
|
||||||
@@ -32,6 +32,7 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
|||||||
'wait-registration-success',
|
'wait-registration-success',
|
||||||
'oauth-login',
|
'oauth-login',
|
||||||
'fetch-login-code',
|
'fetch-login-code',
|
||||||
|
'post-login-phone-verification',
|
||||||
'confirm-oauth',
|
'confirm-oauth',
|
||||||
'platform-verify',
|
'platform-verify',
|
||||||
]
|
]
|
||||||
@@ -40,6 +41,23 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
|||||||
assert.equal(steps[5].title, '等待注册成功');
|
assert.equal(steps[5].title, '等待注册成功');
|
||||||
assert.equal(phoneSteps[1].title, '注册并输入手机号');
|
assert.equal(phoneSteps[1].title, '注册并输入手机号');
|
||||||
assert.equal(phoneSteps[3].title, '获取手机验证码');
|
assert.equal(phoneSteps[3].title, '获取手机验证码');
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
phoneSteps.map((step) => step.key),
|
||||||
|
[
|
||||||
|
'open-chatgpt',
|
||||||
|
'submit-signup-email',
|
||||||
|
'fill-password',
|
||||||
|
'fetch-signup-code',
|
||||||
|
'fill-profile',
|
||||||
|
'wait-registration-success',
|
||||||
|
'oauth-login',
|
||||||
|
'fetch-login-code',
|
||||||
|
'bind-email',
|
||||||
|
'fetch-bind-email-code',
|
||||||
|
'confirm-oauth',
|
||||||
|
'platform-verify',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
plusSteps.map((step) => step.key),
|
plusSteps.map((step) => step.key),
|
||||||
@@ -55,6 +73,7 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
|||||||
'plus-checkout-return',
|
'plus-checkout-return',
|
||||||
'oauth-login',
|
'oauth-login',
|
||||||
'fetch-login-code',
|
'fetch-login-code',
|
||||||
|
'post-login-phone-verification',
|
||||||
'confirm-oauth',
|
'confirm-oauth',
|
||||||
'platform-verify',
|
'platform-verify',
|
||||||
]
|
]
|
||||||
@@ -64,11 +83,33 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
|||||||
assert.equal(plusSteps.find((step) => step.key === 'paypal-approve')?.title, 'PayPal 登录与授权');
|
assert.equal(plusSteps.find((step) => step.key === 'paypal-approve')?.title, 'PayPal 登录与授权');
|
||||||
assert.equal(plusPhoneSteps[1].title, '注册并输入手机号');
|
assert.equal(plusPhoneSteps[1].title, '注册并输入手机号');
|
||||||
assert.equal(plusPhoneSteps[3].title, '获取手机验证码');
|
assert.equal(plusPhoneSteps[3].title, '获取手机验证码');
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
plusPhoneSteps.map((step) => step.key),
|
||||||
|
[
|
||||||
|
'open-chatgpt',
|
||||||
|
'submit-signup-email',
|
||||||
|
'fill-password',
|
||||||
|
'fetch-signup-code',
|
||||||
|
'fill-profile',
|
||||||
|
'plus-checkout-create',
|
||||||
|
'plus-checkout-billing',
|
||||||
|
'paypal-approve',
|
||||||
|
'plus-checkout-return',
|
||||||
|
'oauth-login',
|
||||||
|
'fetch-login-code',
|
||||||
|
'bind-email',
|
||||||
|
'fetch-bind-email-code',
|
||||||
|
'confirm-oauth',
|
||||||
|
'platform-verify',
|
||||||
|
]
|
||||||
|
);
|
||||||
assert.equal(goPaySteps.some((step) => step.key === 'paypal-approve'), false);
|
assert.equal(goPaySteps.some((step) => step.key === 'paypal-approve'), false);
|
||||||
assert.equal(api.getStepById(8, { plusModeEnabled: true, plusPaymentMethod: 'gopay' }), null);
|
assert.equal(api.getStepById(8, { plusModeEnabled: true, plusPaymentMethod: 'gopay' }), null);
|
||||||
assert.equal(api.getPlusPaymentStepTitle({ plusModeEnabled: true, plusPaymentMethod: 'gopay' }), '');
|
assert.equal(api.getPlusPaymentStepTitle({ plusModeEnabled: true, plusPaymentMethod: 'gopay' }), '');
|
||||||
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true }), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]);
|
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true }), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]);
|
||||||
assert.equal(api.getLastStepId({ plusModeEnabled: true }), 13);
|
assert.equal(api.getLastStepId({ plusModeEnabled: true }), 14);
|
||||||
|
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, signupMethod: 'phone' }), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
|
||||||
|
assert.equal(api.getLastStepId({ plusModeEnabled: true, signupMethod: 'phone' }), 15);
|
||||||
assert.equal(api.hasFlow('openai'), true);
|
assert.equal(api.hasFlow('openai'), true);
|
||||||
assert.equal(api.hasFlow('site-a'), false);
|
assert.equal(api.hasFlow('site-a'), false);
|
||||||
assert.deepStrictEqual(api.getRegisteredFlowIds(), ['openai']);
|
assert.deepStrictEqual(api.getRegisteredFlowIds(), ['openai']);
|
||||||
@@ -89,12 +130,13 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
|||||||
'gopay-subscription-confirm',
|
'gopay-subscription-confirm',
|
||||||
'oauth-login',
|
'oauth-login',
|
||||||
'fetch-login-code',
|
'fetch-login-code',
|
||||||
|
'post-login-phone-verification',
|
||||||
'confirm-oauth',
|
'confirm-oauth',
|
||||||
'platform-verify',
|
'platform-verify',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, plusPaymentMethod: 'gopay' }), [1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13]);
|
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, plusPaymentMethod: 'gopay' }), [1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14]);
|
||||||
assert.equal(api.getLastStepId({ plusModeEnabled: true, plusPaymentMethod: 'gopay' }), 13);
|
assert.equal(api.getLastStepId({ plusModeEnabled: true, plusPaymentMethod: 'gopay' }), 14);
|
||||||
assert.equal(goPaySteps[5].title, '打开 GoPay 订阅页');
|
assert.equal(goPaySteps[5].title, '打开 GoPay 订阅页');
|
||||||
assert.equal(goPaySteps[6].title, '等待 GoPay 订阅确认');
|
assert.equal(goPaySteps[6].title, '等待 GoPay 订阅确认');
|
||||||
|
|
||||||
@@ -110,12 +152,13 @@ test('step definitions module exposes ordered normal and Plus step metadata', ()
|
|||||||
'plus-checkout-billing',
|
'plus-checkout-billing',
|
||||||
'oauth-login',
|
'oauth-login',
|
||||||
'fetch-login-code',
|
'fetch-login-code',
|
||||||
|
'post-login-phone-verification',
|
||||||
'confirm-oauth',
|
'confirm-oauth',
|
||||||
'platform-verify',
|
'platform-verify',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' }), [1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13]);
|
assert.deepStrictEqual(api.getStepIds({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' }), [1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14]);
|
||||||
assert.equal(api.getLastStepId({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' }), 13);
|
assert.equal(api.getLastStepId({ plusModeEnabled: true, plusPaymentMethod: 'gpc-helper' }), 14);
|
||||||
assert.equal(gpcSteps[5].title, '创建 GPC 订单');
|
assert.equal(gpcSteps[5].title, '创建 GPC 订单');
|
||||||
assert.equal(gpcSteps[6].title, '等待 GPC 任务完成');
|
assert.equal(gpcSteps[6].title, '等待 GPC 任务完成');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ return {
|
|||||||
assert.match(String(result?.url || ''), /chatgpt\.com/);
|
assert.match(String(result?.url || ''), /chatgpt\.com/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('step 8 ready check completes phone verification flow before waiting for OAuth consent', async () => {
|
test('step 8 ready check rejects add-phone instead of completing phone verification', async () => {
|
||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
let pollCount = 0;
|
let pollCount = 0;
|
||||||
const phoneVerificationCalls = [];
|
const phoneVerificationCalls = [];
|
||||||
@@ -292,28 +292,13 @@ return {
|
|||||||
};
|
};
|
||||||
`)();
|
`)();
|
||||||
|
|
||||||
const { result, phoneVerificationCalls } = await api.run();
|
await assert.rejects(
|
||||||
|
() => api.run(),
|
||||||
assert.deepStrictEqual(phoneVerificationCalls, [
|
/自动确认 OAuth 只处理 OAuth 授权页/
|
||||||
{
|
);
|
||||||
tabId: 88,
|
|
||||||
pageState: {
|
|
||||||
url: 'https://auth.openai.com/add-phone',
|
|
||||||
addPhonePage: true,
|
|
||||||
phoneVerificationPage: false,
|
|
||||||
consentReady: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
assert.deepStrictEqual(result, {
|
|
||||||
url: 'https://auth.openai.com/authorize',
|
|
||||||
addPhonePage: false,
|
|
||||||
phoneVerificationPage: false,
|
|
||||||
consentReady: true,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('step 8 ready check blocks phone verification flow when sms toggle is disabled', async () => {
|
test('step 8 ready check rejects phone pages before OAuth confirmation', async () => {
|
||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
const phoneVerificationCalls = [];
|
const phoneVerificationCalls = [];
|
||||||
|
|
||||||
@@ -358,6 +343,6 @@ return {
|
|||||||
|
|
||||||
const { error, phoneVerificationCalls } = await api.run();
|
const { error, phoneVerificationCalls } = await api.run();
|
||||||
|
|
||||||
assert.match(String(error?.message || ''), /未开启接码功能/);
|
assert.match(String(error?.message || ''), /自动确认 OAuth 只处理 OAuth 授权页/);
|
||||||
assert.deepStrictEqual(phoneVerificationCalls, []);
|
assert.deepStrictEqual(phoneVerificationCalls, []);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user