手机号注册模式
This commit is contained in:
@@ -90,8 +90,61 @@
|
||||
return '流程失败';
|
||||
}
|
||||
|
||||
function buildRecordId(email = '') {
|
||||
return String(email || '').trim().toLowerCase();
|
||||
function normalizeAccountIdentifierType(value = '') {
|
||||
return String(value || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email';
|
||||
}
|
||||
|
||||
function normalizeAccountIdentifierValue(value = '', identifierType = 'email') {
|
||||
const normalizedValue = String(value || '').trim();
|
||||
if (!normalizedValue) {
|
||||
return '';
|
||||
}
|
||||
return normalizeAccountIdentifierType(identifierType) === 'phone'
|
||||
? normalizedValue
|
||||
: normalizedValue.toLowerCase();
|
||||
}
|
||||
|
||||
function resolveRecordIdentity(record = {}) {
|
||||
const email = String(record.email || '').trim().toLowerCase();
|
||||
const phoneNumber = String(record.phoneNumber ?? record.phone ?? record.number ?? '').trim();
|
||||
const rawIdentifierType = String(record.accountIdentifierType || '').trim().toLowerCase();
|
||||
const inferredIdentifierType = rawIdentifierType === 'phone'
|
||||
|| (!email && phoneNumber)
|
||||
? 'phone'
|
||||
: 'email';
|
||||
const rawAccountIdentifier = String(
|
||||
record.accountIdentifier
|
||||
|| (inferredIdentifierType === 'phone' ? phoneNumber : email)
|
||||
|| ''
|
||||
).trim();
|
||||
const accountIdentifierType = rawAccountIdentifier
|
||||
? normalizeAccountIdentifierType(inferredIdentifierType)
|
||||
: (email ? 'email' : (phoneNumber ? 'phone' : ''));
|
||||
const accountIdentifier = normalizeAccountIdentifierValue(
|
||||
rawAccountIdentifier || (accountIdentifierType === 'phone' ? phoneNumber : email),
|
||||
accountIdentifierType || inferredIdentifierType
|
||||
);
|
||||
|
||||
return {
|
||||
email,
|
||||
phoneNumber,
|
||||
accountIdentifierType,
|
||||
accountIdentifier,
|
||||
};
|
||||
}
|
||||
|
||||
function buildRecordId(identifier = '', identifierType = 'email') {
|
||||
const normalizedIdentifierType = normalizeAccountIdentifierType(identifierType);
|
||||
const normalizedIdentifier = normalizeAccountIdentifierValue(identifier, normalizedIdentifierType);
|
||||
if (!normalizedIdentifier) {
|
||||
return '';
|
||||
}
|
||||
if (normalizedIdentifierType === 'phone' && /^phone:/i.test(normalizedIdentifier)) {
|
||||
return normalizedIdentifier.toLowerCase();
|
||||
}
|
||||
return normalizedIdentifierType === 'phone'
|
||||
? `phone:${normalizedIdentifier.toLowerCase()}`
|
||||
: normalizedIdentifier;
|
||||
}
|
||||
|
||||
function normalizeSource(value = '') {
|
||||
@@ -140,11 +193,15 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
const email = String(record.email || '').trim();
|
||||
const password = String(record.password || '').trim();
|
||||
const identity = resolveRecordIdentity(record);
|
||||
const email = identity.email;
|
||||
const phoneNumber = identity.phoneNumber;
|
||||
const accountIdentifierType = identity.accountIdentifierType;
|
||||
const accountIdentifier = identity.accountIdentifier;
|
||||
const password = String(record.password ?? '').trim();
|
||||
const finalStatus = normalizeFinalStatus(record.finalStatus || record.status || '');
|
||||
|
||||
if (!email || !password || !finalStatus) {
|
||||
if (!accountIdentifier || !finalStatus) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -167,8 +224,11 @@
|
||||
const rawFailureLabel = String(record.failureLabel || '').trim();
|
||||
|
||||
return {
|
||||
recordId: String(record.recordId || '').trim() || buildRecordId(email),
|
||||
recordId: String(record.recordId || '').trim() || buildRecordId(accountIdentifier, accountIdentifierType),
|
||||
accountIdentifierType,
|
||||
accountIdentifier,
|
||||
email,
|
||||
phoneNumber,
|
||||
password,
|
||||
finalStatus,
|
||||
finishedAt,
|
||||
@@ -215,11 +275,20 @@
|
||||
}
|
||||
|
||||
function buildAccountRunHistoryRecord(state = {}, status = '', reason = '') {
|
||||
const email = String(state.email || '').trim();
|
||||
const password = String(state.password || state.customPassword || '').trim() || '无';
|
||||
const identity = resolveRecordIdentity({
|
||||
accountIdentifierType: state.accountIdentifierType,
|
||||
accountIdentifier: state.accountIdentifier,
|
||||
email: state.email,
|
||||
phoneNumber: state.phoneNumber || state.signupPhoneNumber,
|
||||
});
|
||||
const email = identity.email;
|
||||
const phoneNumber = identity.phoneNumber;
|
||||
const accountIdentifierType = identity.accountIdentifierType;
|
||||
const accountIdentifier = identity.accountIdentifier;
|
||||
const password = String(state.password || state.customPassword || '').trim();
|
||||
const finalStatus = normalizeFinalStatus(status);
|
||||
|
||||
if (!email || !finalStatus) {
|
||||
if (!accountIdentifier || !finalStatus) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -233,8 +302,11 @@
|
||||
const finishedAt = new Date().toISOString();
|
||||
|
||||
return {
|
||||
recordId: buildRecordId(email),
|
||||
recordId: buildRecordId(accountIdentifier, accountIdentifierType),
|
||||
accountIdentifierType,
|
||||
accountIdentifier,
|
||||
email,
|
||||
phoneNumber,
|
||||
password,
|
||||
finalStatus,
|
||||
finishedAt,
|
||||
@@ -257,10 +329,23 @@
|
||||
|
||||
const recordId = String(record.recordId || '').trim();
|
||||
const emailKey = String(record.email || '').trim().toLowerCase();
|
||||
const phoneKey = String(record.phoneNumber || '').trim().toLowerCase();
|
||||
const identifierKey = buildRecordId(
|
||||
record.accountIdentifier || record.email || record.phoneNumber,
|
||||
record.accountIdentifierType || (phoneKey && !emailKey ? 'phone' : 'email')
|
||||
);
|
||||
const nextHistory = normalizedHistory.filter((item) => {
|
||||
const itemRecordId = String(item.recordId || '').trim();
|
||||
const itemEmailKey = String(item.email || '').trim().toLowerCase();
|
||||
return itemRecordId !== recordId && itemEmailKey !== emailKey;
|
||||
const itemPhoneKey = String(item.phoneNumber || '').trim().toLowerCase();
|
||||
const itemIdentifierKey = buildRecordId(
|
||||
item.accountIdentifier || item.email || item.phoneNumber,
|
||||
item.accountIdentifierType || (itemPhoneKey && !itemEmailKey ? 'phone' : 'email')
|
||||
);
|
||||
return itemRecordId !== recordId
|
||||
&& itemIdentifierKey !== identifierKey
|
||||
&& (!emailKey || itemEmailKey !== emailKey)
|
||||
&& (!phoneKey || itemPhoneKey !== phoneKey);
|
||||
});
|
||||
|
||||
nextHistory.unshift(record);
|
||||
@@ -283,7 +368,12 @@
|
||||
}
|
||||
|
||||
const selectedIds = new Set(normalizedIds);
|
||||
const nextHistory = normalizedHistory.filter((record) => !selectedIds.has(buildRecordId(record.recordId || record.email)));
|
||||
const nextHistory = normalizedHistory.filter((record) => !selectedIds.has(buildRecordId(
|
||||
record.recordId || record.accountIdentifier || record.email || record.phoneNumber,
|
||||
String(record.recordId || '').startsWith('phone:') || String(record.accountIdentifierType || '').trim().toLowerCase() === 'phone'
|
||||
? 'phone'
|
||||
: 'email'
|
||||
)));
|
||||
|
||||
return {
|
||||
deletedCount: normalizedHistory.length - nextHistory.length,
|
||||
|
||||
@@ -417,6 +417,7 @@
|
||||
autoRunDelayEnabled: prevState.autoRunDelayEnabled,
|
||||
autoRunDelayMinutes: prevState.autoRunDelayMinutes,
|
||||
autoStepDelaySeconds: prevState.autoStepDelaySeconds,
|
||||
signupMethod: prevState.signupMethod,
|
||||
mailProvider: prevState.mailProvider,
|
||||
emailGenerator: prevState.emailGenerator,
|
||||
gmailBaseEmail: prevState.gmailBaseEmail,
|
||||
|
||||
@@ -48,6 +48,14 @@
|
||||
getStepDefinitionForState,
|
||||
getStepIdsForState,
|
||||
getLastStepIdForState,
|
||||
normalizeSignupMethod = (value = '') => String(value || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email',
|
||||
canUsePhoneSignup = (state = {}) => Boolean(state?.phoneVerificationEnabled)
|
||||
&& !Boolean(state?.plusModeEnabled)
|
||||
&& !Boolean(state?.contributionMode),
|
||||
resolveSignupMethod = (state = {}) => {
|
||||
const method = normalizeSignupMethod(state?.signupMethod);
|
||||
return method === 'phone' && canUsePhoneSignup(state) ? 'phone' : 'email';
|
||||
},
|
||||
getTabId,
|
||||
getStopRequested,
|
||||
handleAutoRunLoopUnhandledError,
|
||||
@@ -275,7 +283,13 @@
|
||||
|
||||
if (stepKey === 'fetch-login-code') {
|
||||
await setState({
|
||||
lastEmailTimestamp: payload.emailTimestamp || null,
|
||||
...(payload.phoneVerification || payload.loginPhoneVerification ? {
|
||||
currentPhoneVerificationCode: '',
|
||||
signupPhoneVerificationRequestedAt: null,
|
||||
signupPhoneVerificationPurpose: '',
|
||||
} : {
|
||||
lastEmailTimestamp: payload.emailTimestamp || null,
|
||||
}),
|
||||
loginVerificationRequestedAt: null,
|
||||
});
|
||||
return;
|
||||
@@ -340,7 +354,8 @@
|
||||
const step3Status = latestState.stepStatuses?.[3];
|
||||
if (step3Status !== 'running' && step3Status !== 'completed' && step3Status !== 'manual_completed') {
|
||||
await setStepStatus(3, 'skipped');
|
||||
await addLog('步骤 2:提交邮箱后页面直接进入邮箱验证码页,已自动跳过步骤 3。', 'warn');
|
||||
const identityLabel = payload.accountIdentifierType === 'phone' ? '手机号' : '邮箱';
|
||||
await addLog(`步骤 2:提交${identityLabel}后页面直接进入验证码页,已自动跳过步骤 3。`, 'warn');
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -349,13 +364,27 @@
|
||||
if (payload.signupVerificationRequestedAt) {
|
||||
await setState({ signupVerificationRequestedAt: payload.signupVerificationRequestedAt });
|
||||
}
|
||||
if (payload.skipProfileStep) {
|
||||
const latestState = await getState();
|
||||
const step5Status = latestState.stepStatuses?.[5];
|
||||
if (step5Status !== 'running' && step5Status !== 'completed' && step5Status !== 'manual_completed') {
|
||||
await setStepStatus(5, 'skipped');
|
||||
await addLog('步骤 3:页面已直接进入已登录态,已自动跳过步骤 5。', 'warn');
|
||||
}
|
||||
}
|
||||
if (payload.loginVerificationRequestedAt) {
|
||||
await setState({ loginVerificationRequestedAt: payload.loginVerificationRequestedAt });
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
await setState({
|
||||
lastEmailTimestamp: payload.emailTimestamp || null,
|
||||
...(payload.phoneVerification ? {
|
||||
currentPhoneVerificationCode: '',
|
||||
signupPhoneVerificationRequestedAt: null,
|
||||
signupPhoneVerificationPurpose: '',
|
||||
} : {
|
||||
lastEmailTimestamp: payload.emailTimestamp || null,
|
||||
}),
|
||||
signupVerificationRequestedAt: null,
|
||||
});
|
||||
if (payload.skipProfileStep) {
|
||||
@@ -373,7 +402,13 @@
|
||||
break;
|
||||
case 8:
|
||||
await setState({
|
||||
lastEmailTimestamp: payload.emailTimestamp || null,
|
||||
...(payload.phoneVerification || payload.loginPhoneVerification ? {
|
||||
currentPhoneVerificationCode: '',
|
||||
signupPhoneVerificationRequestedAt: null,
|
||||
signupPhoneVerificationPurpose: '',
|
||||
} : {
|
||||
lastEmailTimestamp: payload.emailTimestamp || null,
|
||||
}),
|
||||
loginVerificationRequestedAt: null,
|
||||
});
|
||||
break;
|
||||
@@ -758,6 +793,18 @@
|
||||
const currentState = await getState();
|
||||
const updates = buildPersistentSettingsPayload(message.payload || {});
|
||||
const sessionUpdates = buildLuckmailSessionSettingsPayload(message.payload || {});
|
||||
const nextSignupState = {
|
||||
...currentState,
|
||||
...updates,
|
||||
resolvedSignupMethod: null,
|
||||
};
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(updates, 'phoneVerificationEnabled')
|
||||
|| Object.prototype.hasOwnProperty.call(updates, 'plusModeEnabled')
|
||||
|| Object.prototype.hasOwnProperty.call(updates, 'signupMethod')
|
||||
) {
|
||||
updates.signupMethod = resolveSignupMethod(nextSignupState);
|
||||
}
|
||||
const modeChanged = Object.prototype.hasOwnProperty.call(updates, 'plusModeEnabled')
|
||||
&& Boolean(currentState?.plusModeEnabled) !== Boolean(updates.plusModeEnabled);
|
||||
const plusPaymentChanged = Object.prototype.hasOwnProperty.call(updates, 'plusPaymentMethod')
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
const MAX_ACTIVATION_PRICE_HINTS = 256;
|
||||
const activationPriceHintsByKey = new Map();
|
||||
let activePhoneVerificationLogStep = null;
|
||||
let activePhoneVerificationLogStepKey = null;
|
||||
|
||||
function normalizeLogStep(value) {
|
||||
const step = Math.floor(Number(value) || 0);
|
||||
@@ -114,13 +115,26 @@
|
||||
if (step) {
|
||||
normalizedOptions.step = step;
|
||||
if (!normalizedOptions.stepKey) {
|
||||
normalizedOptions.stepKey = 'phone-verification';
|
||||
normalizedOptions.stepKey = activePhoneVerificationLogStepKey || 'phone-verification';
|
||||
}
|
||||
}
|
||||
delete normalizedOptions.visibleStep;
|
||||
return rawAddLog(normalizePhoneVerificationLogMessage(message), level, normalizedOptions);
|
||||
}
|
||||
|
||||
async function withPhoneVerificationLogContext(options = {}, action) {
|
||||
const previousStep = activePhoneVerificationLogStep;
|
||||
const previousStepKey = activePhoneVerificationLogStepKey;
|
||||
activePhoneVerificationLogStep = normalizeLogStep(options.step || options.visibleStep) || previousStep;
|
||||
activePhoneVerificationLogStepKey = String(options.stepKey || '').trim() || previousStepKey;
|
||||
try {
|
||||
return await action();
|
||||
} finally {
|
||||
activePhoneVerificationLogStep = previousStep;
|
||||
activePhoneVerificationLogStepKey = previousStepKey;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeUrl(value, fallback = DEFAULT_HERO_SMS_BASE_URL) {
|
||||
const trimmed = String(value || '').trim();
|
||||
if (!trimmed) {
|
||||
@@ -3661,6 +3675,60 @@
|
||||
return result || {};
|
||||
}
|
||||
|
||||
async function submitSignupPhoneVerificationCode(tabId, code, options = {}) {
|
||||
const visibleStep = 4;
|
||||
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
|
||||
? await getOAuthFlowStepTimeoutMs(45000, { step: visibleStep, actionLabel: '提交注册手机验证码' })
|
||||
: 45000;
|
||||
const result = await sendToContentScriptResilient('signup-page', {
|
||||
type: 'SUBMIT_PHONE_VERIFICATION_CODE',
|
||||
step: visibleStep,
|
||||
source: 'background',
|
||||
payload: {
|
||||
code,
|
||||
purpose: 'signup',
|
||||
signupProfile: options.signupProfile || null,
|
||||
},
|
||||
}, {
|
||||
timeoutMs,
|
||||
responseTimeoutMs: timeoutMs,
|
||||
retryDelayMs: 600,
|
||||
logMessage: '步骤 4:等待注册手机验证码页面就绪后填写短信验证码...',
|
||||
logStep: visibleStep,
|
||||
logStepKey: 'fetch-signup-code',
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
return result || {};
|
||||
}
|
||||
|
||||
async function resendSignupPhoneVerificationCode(tabId) {
|
||||
const visibleStep = 4;
|
||||
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
|
||||
? await getOAuthFlowStepTimeoutMs(65000, { step: visibleStep, actionLabel: '重新发送注册手机验证码' })
|
||||
: 65000;
|
||||
const result = await sendToContentScriptResilient('signup-page', {
|
||||
type: 'RESEND_VERIFICATION_CODE',
|
||||
step: visibleStep,
|
||||
source: 'background',
|
||||
payload: {},
|
||||
}, {
|
||||
timeoutMs,
|
||||
responseTimeoutMs: timeoutMs,
|
||||
retryDelayMs: 600,
|
||||
logMessage: '步骤 4:等待注册手机验证码重发按钮出现...',
|
||||
logStep: visibleStep,
|
||||
logStepKey: 'fetch-signup-code',
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
return result || {};
|
||||
}
|
||||
|
||||
async function returnToAddPhone(tabId) {
|
||||
const visibleStep = normalizeLogStep(activePhoneVerificationLogStep) || 9;
|
||||
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
|
||||
@@ -3773,6 +3841,25 @@
|
||||
});
|
||||
}
|
||||
|
||||
async function persistSignupPhoneRuntimeState(updates = {}) {
|
||||
await setPhoneRuntimeState({
|
||||
signupPhoneNumber: '',
|
||||
signupPhoneActivation: null,
|
||||
signupPhoneVerificationRequestedAt: null,
|
||||
signupPhoneVerificationPurpose: '',
|
||||
accountIdentifierType: null,
|
||||
accountIdentifier: '',
|
||||
...updates,
|
||||
});
|
||||
}
|
||||
|
||||
async function clearSignupPhoneRuntimeState(extraUpdates = {}) {
|
||||
await persistSignupPhoneRuntimeState({
|
||||
[PHONE_VERIFICATION_CODE_STATE_KEY]: '',
|
||||
...extraUpdates,
|
||||
});
|
||||
}
|
||||
|
||||
async function acquirePhoneActivation(state = {}, options = {}) {
|
||||
const provider = normalizePhoneSmsProvider(state?.phoneSmsProvider || DEFAULT_PHONE_SMS_PROVIDER);
|
||||
const providerOrder = resolvePhoneProviderOrder(state, provider);
|
||||
@@ -3982,6 +4069,28 @@
|
||||
throw lastProviderError || new Error('Step 9: failed to acquire phone activation.');
|
||||
}
|
||||
|
||||
async function prepareSignupPhoneActivation(state = {}, options = {}) {
|
||||
return withPhoneVerificationLogContext({ step: 2, stepKey: 'submit-signup-email' }, async () => {
|
||||
const activation = await acquirePhoneActivation(state, {
|
||||
...options,
|
||||
logLabel: options?.logLabel || '步骤 2',
|
||||
});
|
||||
const normalizedActivation = normalizeActivation(activation);
|
||||
if (!normalizedActivation) {
|
||||
throw new Error('步骤 2:接码平台返回的手机号订单无效。');
|
||||
}
|
||||
await persistSignupPhoneRuntimeState({
|
||||
signupPhoneNumber: normalizedActivation.phoneNumber,
|
||||
signupPhoneActivation: normalizedActivation,
|
||||
signupPhoneVerificationRequestedAt: null,
|
||||
signupPhoneVerificationPurpose: 'signup',
|
||||
accountIdentifierType: 'phone',
|
||||
accountIdentifier: normalizedActivation.phoneNumber,
|
||||
});
|
||||
return normalizedActivation;
|
||||
});
|
||||
}
|
||||
|
||||
async function markActivationReusableAfterSuccess(state, activation) {
|
||||
const normalizedActivation = normalizeActivation(activation);
|
||||
if (!isPhoneSmsReuseEnabled(state)) {
|
||||
@@ -4151,8 +4260,505 @@
|
||||
throw new Error('手机号验证未完成。');
|
||||
}
|
||||
|
||||
function buildCompletedActivationSnapshot(activation) {
|
||||
const normalizedActivation = normalizeActivation(activation);
|
||||
if (!normalizedActivation) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...normalizedActivation,
|
||||
successfulUses: normalizedActivation.successfulUses + 1,
|
||||
};
|
||||
}
|
||||
|
||||
async function waitForScopedPhoneCode(state = {}, activation, options = {}) {
|
||||
const normalizedActivation = normalizeActivation(activation);
|
||||
const visibleStep = normalizeLogStep(options?.step) || 4;
|
||||
const stepKey = String(options?.stepKey || 'fetch-signup-code').trim() || 'fetch-signup-code';
|
||||
const purpose = String(options?.purpose || 'signup').trim() || 'signup';
|
||||
const actionLabelPrefix = String(options?.actionLabelPrefix || 'signup phone verification').trim() || 'phone verification';
|
||||
if (!normalizedActivation) {
|
||||
throw new Error(options?.missingActivationMessage || `步骤 ${visibleStep}:手机号激活记录缺失,请重新执行前置步骤。`);
|
||||
}
|
||||
|
||||
return withPhoneVerificationLogContext({ step: visibleStep, stepKey }, async () => {
|
||||
const providerLabel = getPhoneSmsProviderLabel(normalizedActivation.provider);
|
||||
const waitSeconds = normalizePhoneCodeWaitSeconds(state?.phoneCodeWaitSeconds);
|
||||
const timeoutWindows = normalizePhoneCodeTimeoutWindows(state?.phoneCodeTimeoutWindows);
|
||||
const pollIntervalSeconds = normalizePhoneCodePollIntervalSeconds(state?.phoneCodePollIntervalSeconds);
|
||||
const pollMaxRounds = normalizePhoneCodePollMaxRounds(state?.phoneCodePollMaxRounds);
|
||||
let lastLoggedStatus = '';
|
||||
let lastLoggedPollCount = 0;
|
||||
|
||||
for (let windowIndex = 1; windowIndex <= timeoutWindows; windowIndex += 1) {
|
||||
await setPhoneRuntimeState({
|
||||
signupPhoneActivation: normalizedActivation,
|
||||
signupPhoneNumber: normalizedActivation.phoneNumber,
|
||||
signupPhoneVerificationPurpose: purpose,
|
||||
signupPhoneVerificationRequestedAt: Date.now(),
|
||||
[PHONE_RUNTIME_COUNTDOWN_ENDS_AT_KEY]: Date.now() + waitSeconds * 1000,
|
||||
[PHONE_RUNTIME_COUNTDOWN_WINDOW_INDEX_KEY]: windowIndex,
|
||||
[PHONE_RUNTIME_COUNTDOWN_WINDOW_TOTAL_KEY]: timeoutWindows,
|
||||
});
|
||||
await addLog(
|
||||
`步骤 ${visibleStep}:正在等待 ${normalizedActivation.phoneNumber} 的短信验证码(${windowIndex}/${timeoutWindows},最长 ${waitSeconds} 秒)。`,
|
||||
'info',
|
||||
{ step: visibleStep, stepKey }
|
||||
);
|
||||
try {
|
||||
const code = await pollPhoneActivationCode(state, normalizedActivation, {
|
||||
actionLabel: windowIndex === 1
|
||||
? `poll ${actionLabelPrefix} code from ${providerLabel}`
|
||||
: `poll resent ${actionLabelPrefix} code from ${providerLabel}`,
|
||||
timeoutMs: waitSeconds * 1000,
|
||||
intervalMs: pollIntervalSeconds * 1000,
|
||||
maxRounds: pollMaxRounds,
|
||||
onStatus: async ({ elapsedMs, pollCount, statusText }) => {
|
||||
const shouldLog = (
|
||||
pollCount === 1
|
||||
|| statusText !== lastLoggedStatus
|
||||
|| pollCount - lastLoggedPollCount >= 3
|
||||
);
|
||||
if (!shouldLog) {
|
||||
return;
|
||||
}
|
||||
lastLoggedStatus = statusText;
|
||||
lastLoggedPollCount = pollCount;
|
||||
await addLog(
|
||||
`步骤 ${visibleStep}:${providerLabel} 状态 ${normalizedActivation.phoneNumber}: ${statusText}(已等待 ${Math.ceil(elapsedMs / 1000)} 秒,第 ${pollCount}/${pollMaxRounds} 轮)。`,
|
||||
'info',
|
||||
{ step: visibleStep, stepKey }
|
||||
);
|
||||
},
|
||||
});
|
||||
await clearPhoneRuntimeCountdown();
|
||||
await setPhoneRuntimeState({
|
||||
[PHONE_VERIFICATION_CODE_STATE_KEY]: String(code || '').trim(),
|
||||
signupPhoneVerificationRequestedAt: Date.now(),
|
||||
});
|
||||
return code;
|
||||
} catch (error) {
|
||||
if (!isPhoneCodeTimeoutError(error)) {
|
||||
if (isPhoneActivationOrderMissingError(error, normalizedActivation.provider)) {
|
||||
throw new Error(`步骤 ${visibleStep}:当前手机号激活已失效,请重新执行前置步骤获取新短信。${error.message || error}`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (windowIndex < timeoutWindows) {
|
||||
await addLog(
|
||||
`步骤 ${visibleStep}:${normalizedActivation.phoneNumber} 在 ${waitSeconds} 秒内未收到短信,准备请求重发。`,
|
||||
'warn',
|
||||
{ step: visibleStep, stepKey }
|
||||
);
|
||||
await requestAdditionalPhoneSms(state, normalizedActivation);
|
||||
if (typeof options.onTimeoutWindow === 'function') {
|
||||
await options.onTimeoutWindow({
|
||||
activation: normalizedActivation,
|
||||
windowIndex,
|
||||
timeoutWindows,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
await clearPhoneRuntimeCountdown();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`步骤 ${visibleStep}:手机验证码未能成功获取。`);
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForSignupPhoneCode(state = {}, activation, options = {}) {
|
||||
return waitForScopedPhoneCode(state, activation, {
|
||||
...options,
|
||||
step: 4,
|
||||
stepKey: 'fetch-signup-code',
|
||||
purpose: 'signup',
|
||||
actionLabelPrefix: 'signup phone verification',
|
||||
missingActivationMessage: '步骤 4:注册手机号激活记录缺失,请重新执行步骤 2。',
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForLoginPhoneCode(state = {}, activation, options = {}) {
|
||||
const visibleStep = normalizeLogStep(options?.visibleStep || options?.step) || 8;
|
||||
return waitForScopedPhoneCode(state, activation, {
|
||||
...options,
|
||||
step: visibleStep,
|
||||
stepKey: 'fetch-login-code',
|
||||
purpose: 'login',
|
||||
actionLabelPrefix: 'login phone verification',
|
||||
missingActivationMessage: `步骤 ${visibleStep}:登录手机号激活记录缺失,请重新执行步骤 ${visibleStep >= 11 ? 10 : 7}。`,
|
||||
});
|
||||
}
|
||||
|
||||
async function finalizeSignupPhoneActivationAfterSuccess(state = {}, activation = null) {
|
||||
const normalizedActivation = normalizeActivation(activation || state?.signupPhoneActivation);
|
||||
if (!normalizedActivation) {
|
||||
await clearSignupPhoneRuntimeState();
|
||||
return null;
|
||||
}
|
||||
await completePhoneActivation(state, normalizedActivation);
|
||||
await markActivationReusableAfterSuccess(state, normalizedActivation);
|
||||
await clearSignupPhoneRuntimeState({
|
||||
signupPhoneCompletedActivation: buildCompletedActivationSnapshot(normalizedActivation),
|
||||
signupPhoneNumber: normalizedActivation.phoneNumber,
|
||||
accountIdentifierType: 'phone',
|
||||
accountIdentifier: normalizedActivation.phoneNumber,
|
||||
});
|
||||
return normalizedActivation;
|
||||
}
|
||||
|
||||
async function cancelSignupPhoneActivation(state = {}, activation = null) {
|
||||
const normalizedActivation = normalizeActivation(activation || state?.signupPhoneActivation);
|
||||
if (normalizedActivation) {
|
||||
await cancelPhoneActivation(state, normalizedActivation);
|
||||
}
|
||||
await clearSignupPhoneRuntimeState();
|
||||
}
|
||||
|
||||
async function completeSignupPhoneVerificationFlow(tabId, options = {}) {
|
||||
return withPhoneVerificationLogContext({ step: 4, stepKey: 'fetch-signup-code' }, async () => {
|
||||
let state = options?.state || await getState();
|
||||
const activation = normalizeActivation(options?.activation || state?.signupPhoneActivation);
|
||||
if (!activation) {
|
||||
throw new Error('步骤 4:未找到当前注册手机号激活记录,请重新执行步骤 2。');
|
||||
}
|
||||
|
||||
let shouldCancelActivation = true;
|
||||
try {
|
||||
for (let attempt = 1; attempt <= DEFAULT_PHONE_SUBMIT_ATTEMPTS; attempt += 1) {
|
||||
throwIfStopped();
|
||||
state = await getState();
|
||||
const code = await waitForSignupPhoneCode(state, activation, {
|
||||
onTimeoutWindow: async () => {
|
||||
try {
|
||||
await resendSignupPhoneVerificationCode(tabId);
|
||||
await addLog('步骤 4:已点击注册手机验证码页面的“重新发送”。', 'info', {
|
||||
step: 4,
|
||||
stepKey: 'fetch-signup-code',
|
||||
});
|
||||
} catch (resendError) {
|
||||
if (isStopRequestedError(resendError)) {
|
||||
throw resendError;
|
||||
}
|
||||
await addLog(`步骤 4:注册手机验证码页面重发失败,将继续轮询短信。${resendError.message}`, 'warn', {
|
||||
step: 4,
|
||||
stepKey: 'fetch-signup-code',
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await setPhoneRuntimeState({
|
||||
[PHONE_VERIFICATION_CODE_STATE_KEY]: String(code || '').trim(),
|
||||
signupPhoneVerificationRequestedAt: Date.now(),
|
||||
signupPhoneVerificationPurpose: 'signup',
|
||||
});
|
||||
await addLog(`步骤 4:已获取手机验证码 ${code}。`, 'info', {
|
||||
step: 4,
|
||||
stepKey: 'fetch-signup-code',
|
||||
});
|
||||
|
||||
const submitResult = await submitSignupPhoneVerificationCode(tabId, code, {
|
||||
signupProfile: options.signupProfile || null,
|
||||
});
|
||||
|
||||
if (submitResult.invalidCode) {
|
||||
const invalidErrorText = String(submitResult.errorText || submitResult.url || '未知错误').trim();
|
||||
if (attempt >= DEFAULT_PHONE_SUBMIT_ATTEMPTS) {
|
||||
throw new Error(`步骤 4:手机验证码连续 ${DEFAULT_PHONE_SUBMIT_ATTEMPTS} 次被拒绝:${invalidErrorText}`);
|
||||
}
|
||||
|
||||
await requestAdditionalPhoneSms(state, activation);
|
||||
try {
|
||||
await resendSignupPhoneVerificationCode(tabId);
|
||||
} catch (resendError) {
|
||||
if (isStopRequestedError(resendError)) {
|
||||
throw resendError;
|
||||
}
|
||||
await addLog(`步骤 4:验证码被拒后点击重发失败。${resendError.message}`, 'warn', {
|
||||
step: 4,
|
||||
stepKey: 'fetch-signup-code',
|
||||
});
|
||||
}
|
||||
await addLog(
|
||||
`步骤 4:手机验证码被拒绝,已请求新短信(${attempt + 1}/${DEFAULT_PHONE_SUBMIT_ATTEMPTS})。`,
|
||||
'warn',
|
||||
{ step: 4, stepKey: 'fetch-signup-code' }
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
await finalizeSignupPhoneActivationAfterSuccess(state, activation);
|
||||
shouldCancelActivation = false;
|
||||
await setPhoneRuntimeState({
|
||||
[PHONE_VERIFICATION_CODE_STATE_KEY]: '',
|
||||
signupPhoneVerificationRequestedAt: null,
|
||||
signupPhoneVerificationPurpose: '',
|
||||
});
|
||||
await addLog('步骤 4:手机验证码已通过,继续进入资料填写。', 'ok', {
|
||||
step: 4,
|
||||
stepKey: 'fetch-signup-code',
|
||||
});
|
||||
return submitResult || {};
|
||||
}
|
||||
|
||||
throw new Error('步骤 4:手机验证码未能成功提交。');
|
||||
} catch (error) {
|
||||
if (shouldCancelActivation && activation) {
|
||||
await cancelSignupPhoneActivation(state, activation).catch(() => {});
|
||||
}
|
||||
await setPhoneRuntimeState({
|
||||
[PHONE_VERIFICATION_CODE_STATE_KEY]: '',
|
||||
signupPhoneVerificationRequestedAt: null,
|
||||
signupPhoneVerificationPurpose: '',
|
||||
});
|
||||
throw sanitizePhoneCodeTimeoutError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function submitLoginPhoneVerificationCode(tabId, code, options = {}) {
|
||||
const visibleStep = normalizeLogStep(options?.visibleStep || options?.step) || 8;
|
||||
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
|
||||
? await getOAuthFlowStepTimeoutMs(45000, { step: visibleStep, actionLabel: '提交登录手机验证码' })
|
||||
: 45000;
|
||||
const result = await sendToContentScriptResilient('signup-page', {
|
||||
type: 'SUBMIT_PHONE_VERIFICATION_CODE',
|
||||
step: visibleStep,
|
||||
source: 'background',
|
||||
payload: {
|
||||
code,
|
||||
purpose: 'login',
|
||||
visibleStep,
|
||||
},
|
||||
}, {
|
||||
timeoutMs,
|
||||
responseTimeoutMs: timeoutMs,
|
||||
retryDelayMs: 600,
|
||||
logMessage: `步骤 ${visibleStep}:等待登录手机验证码页面就绪后填写短信验证码...`,
|
||||
logStep: visibleStep,
|
||||
logStepKey: 'fetch-login-code',
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
return result || {};
|
||||
}
|
||||
|
||||
async function resendLoginPhoneVerificationCode(tabId, options = {}) {
|
||||
const visibleStep = normalizeLogStep(options?.visibleStep || options?.step) || 8;
|
||||
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
|
||||
? await getOAuthFlowStepTimeoutMs(65000, { step: visibleStep, actionLabel: '重新发送登录手机验证码' })
|
||||
: 65000;
|
||||
const result = await sendToContentScriptResilient('signup-page', {
|
||||
type: 'RESEND_VERIFICATION_CODE',
|
||||
step: visibleStep,
|
||||
source: 'background',
|
||||
payload: {},
|
||||
}, {
|
||||
timeoutMs,
|
||||
responseTimeoutMs: timeoutMs,
|
||||
retryDelayMs: 600,
|
||||
logMessage: `步骤 ${visibleStep}:等待登录手机验证码重发按钮出现...`,
|
||||
logStep: visibleStep,
|
||||
logStepKey: 'fetch-login-code',
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
return result || {};
|
||||
}
|
||||
|
||||
async function prepareLoginPhoneActivation(state = {}, options = {}) {
|
||||
const visibleStep = normalizeLogStep(options?.visibleStep || options?.step) || 8;
|
||||
return withPhoneVerificationLogContext({ step: visibleStep, stepKey: 'fetch-login-code' }, async () => {
|
||||
const preferredActivation = normalizeActivation(
|
||||
options?.activation
|
||||
|| state?.signupPhoneCompletedActivation
|
||||
|| state?.signupPhoneActivation
|
||||
);
|
||||
if (!preferredActivation) {
|
||||
throw new Error(`步骤 ${visibleStep}:缺少已注册手机号激活记录,无法继续手机号登录验证码流程。`);
|
||||
}
|
||||
|
||||
const activeActivation = normalizeActivation(state?.signupPhoneActivation);
|
||||
if (activeActivation && isSameActivation(activeActivation, preferredActivation)) {
|
||||
await setPhoneRuntimeState({
|
||||
signupPhoneNumber: activeActivation.phoneNumber,
|
||||
signupPhoneVerificationPurpose: 'login',
|
||||
});
|
||||
return activeActivation;
|
||||
}
|
||||
|
||||
const reactivated = await reactivatePhoneActivation(state, preferredActivation);
|
||||
const normalizedActivation = normalizeActivation(reactivated);
|
||||
if (!normalizedActivation) {
|
||||
throw new Error(`步骤 ${visibleStep}:无法复用当前注册手机号,请重新执行步骤 ${visibleStep >= 11 ? 10 : 7}。`);
|
||||
}
|
||||
|
||||
await setPhoneRuntimeState({
|
||||
signupPhoneActivation: normalizedActivation,
|
||||
signupPhoneCompletedActivation: preferredActivation,
|
||||
signupPhoneNumber: normalizedActivation.phoneNumber,
|
||||
signupPhoneVerificationRequestedAt: null,
|
||||
signupPhoneVerificationPurpose: 'login',
|
||||
[PHONE_VERIFICATION_CODE_STATE_KEY]: '',
|
||||
accountIdentifierType: 'phone',
|
||||
accountIdentifier: normalizedActivation.phoneNumber,
|
||||
});
|
||||
return normalizedActivation;
|
||||
});
|
||||
}
|
||||
|
||||
async function finalizeLoginPhoneActivationAfterSuccess(state = {}, activation = null, options = {}) {
|
||||
const normalizedActivation = normalizeActivation(activation || state?.signupPhoneActivation);
|
||||
const visibleStep = normalizeLogStep(options?.visibleStep || options?.step) || 8;
|
||||
if (!normalizedActivation) {
|
||||
await setPhoneRuntimeState({
|
||||
signupPhoneActivation: null,
|
||||
signupPhoneVerificationRequestedAt: null,
|
||||
signupPhoneVerificationPurpose: '',
|
||||
[PHONE_VERIFICATION_CODE_STATE_KEY]: '',
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
return withPhoneVerificationLogContext({ step: visibleStep, stepKey: 'fetch-login-code' }, async () => {
|
||||
await completePhoneActivation(state, normalizedActivation);
|
||||
await setPhoneRuntimeState({
|
||||
signupPhoneActivation: null,
|
||||
signupPhoneCompletedActivation: buildCompletedActivationSnapshot(normalizedActivation),
|
||||
signupPhoneNumber: normalizedActivation.phoneNumber,
|
||||
signupPhoneVerificationRequestedAt: null,
|
||||
signupPhoneVerificationPurpose: '',
|
||||
[PHONE_VERIFICATION_CODE_STATE_KEY]: '',
|
||||
accountIdentifierType: 'phone',
|
||||
accountIdentifier: normalizedActivation.phoneNumber,
|
||||
});
|
||||
return normalizedActivation;
|
||||
});
|
||||
}
|
||||
|
||||
async function completeLoginPhoneVerificationFlow(tabId, options = {}) {
|
||||
const visibleStep = normalizeLogStep(options?.visibleStep || options?.step) || 8;
|
||||
return withPhoneVerificationLogContext({ step: visibleStep, stepKey: 'fetch-login-code' }, async () => {
|
||||
let state = options?.state || await getState();
|
||||
const baseActivation = normalizeActivation(
|
||||
options?.activation
|
||||
|| state?.signupPhoneCompletedActivation
|
||||
|| state?.signupPhoneActivation
|
||||
);
|
||||
if (!baseActivation) {
|
||||
throw new Error(`步骤 ${visibleStep}:未找到当前登录手机号激活记录,请重新执行步骤 ${visibleStep >= 11 ? 10 : 7}。`);
|
||||
}
|
||||
|
||||
let activation = await prepareLoginPhoneActivation(state, {
|
||||
activation: baseActivation,
|
||||
visibleStep,
|
||||
});
|
||||
let shouldCancelActivation = true;
|
||||
|
||||
try {
|
||||
for (let attempt = 1; attempt <= DEFAULT_PHONE_SUBMIT_ATTEMPTS; attempt += 1) {
|
||||
throwIfStopped();
|
||||
state = await getState();
|
||||
const code = await waitForLoginPhoneCode(state, activation, {
|
||||
visibleStep,
|
||||
onTimeoutWindow: async () => {
|
||||
try {
|
||||
await resendLoginPhoneVerificationCode(tabId, { visibleStep });
|
||||
await addLog(`步骤 ${visibleStep}:已点击登录手机验证码页面的“重新发送”。`, 'info', {
|
||||
step: visibleStep,
|
||||
stepKey: 'fetch-login-code',
|
||||
});
|
||||
} catch (resendError) {
|
||||
if (isStopRequestedError(resendError)) {
|
||||
throw resendError;
|
||||
}
|
||||
await addLog(`步骤 ${visibleStep}:登录手机验证码页面重发失败,将继续轮询短信。${resendError.message}`, 'warn', {
|
||||
step: visibleStep,
|
||||
stepKey: 'fetch-login-code',
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await setPhoneRuntimeState({
|
||||
[PHONE_VERIFICATION_CODE_STATE_KEY]: String(code || '').trim(),
|
||||
signupPhoneVerificationRequestedAt: Date.now(),
|
||||
signupPhoneVerificationPurpose: 'login',
|
||||
});
|
||||
await addLog(`步骤 ${visibleStep}:已获取登录手机验证码 ${code}。`, 'info', {
|
||||
step: visibleStep,
|
||||
stepKey: 'fetch-login-code',
|
||||
});
|
||||
|
||||
const submitResult = await submitLoginPhoneVerificationCode(tabId, code, {
|
||||
visibleStep,
|
||||
});
|
||||
|
||||
if (submitResult.invalidCode) {
|
||||
const invalidErrorText = String(submitResult.errorText || submitResult.url || '未知错误').trim();
|
||||
if (attempt >= DEFAULT_PHONE_SUBMIT_ATTEMPTS) {
|
||||
throw new Error(`步骤 ${visibleStep}:登录手机验证码连续 ${DEFAULT_PHONE_SUBMIT_ATTEMPTS} 次被拒绝:${invalidErrorText}`);
|
||||
}
|
||||
|
||||
await requestAdditionalPhoneSms(state, activation);
|
||||
try {
|
||||
await resendLoginPhoneVerificationCode(tabId, { visibleStep });
|
||||
} catch (resendError) {
|
||||
if (isStopRequestedError(resendError)) {
|
||||
throw resendError;
|
||||
}
|
||||
await addLog(`步骤 ${visibleStep}:登录手机验证码被拒后点击重发失败。${resendError.message}`, 'warn', {
|
||||
step: visibleStep,
|
||||
stepKey: 'fetch-login-code',
|
||||
});
|
||||
}
|
||||
await addLog(
|
||||
`步骤 ${visibleStep}:登录手机验证码被拒绝,已请求新短信(${attempt + 1}/${DEFAULT_PHONE_SUBMIT_ATTEMPTS})。`,
|
||||
'warn',
|
||||
{ step: visibleStep, stepKey: 'fetch-login-code' }
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
await finalizeLoginPhoneActivationAfterSuccess(state, activation, { visibleStep });
|
||||
shouldCancelActivation = false;
|
||||
await addLog(`步骤 ${visibleStep}:登录手机验证码已通过,继续进入后续授权流程。`, 'ok', {
|
||||
step: visibleStep,
|
||||
stepKey: 'fetch-login-code',
|
||||
});
|
||||
return submitResult || {};
|
||||
}
|
||||
|
||||
throw new Error(`步骤 ${visibleStep}:登录手机验证码未能成功提交。`);
|
||||
} catch (error) {
|
||||
if (shouldCancelActivation && activation) {
|
||||
await cancelPhoneActivation(state, activation).catch(() => {});
|
||||
}
|
||||
await setPhoneRuntimeState({
|
||||
signupPhoneActivation: null,
|
||||
[PHONE_VERIFICATION_CODE_STATE_KEY]: '',
|
||||
signupPhoneVerificationRequestedAt: null,
|
||||
signupPhoneVerificationPurpose: '',
|
||||
});
|
||||
throw sanitizePhoneCodeTimeoutError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function completePhoneVerificationFlow(tabId, initialPageState = null, options = {}) {
|
||||
const previousLogStep = activePhoneVerificationLogStep;
|
||||
const previousLogStepKey = activePhoneVerificationLogStepKey;
|
||||
activePhoneVerificationLogStep = normalizeLogStep(options.visibleStep || options.step) || 9;
|
||||
activePhoneVerificationLogStepKey = 'phone-verification';
|
||||
let state = await getState();
|
||||
let activation = normalizeActivation(state[PHONE_ACTIVATION_STATE_KEY]);
|
||||
let pageState = initialPageState || await readPhonePageState(tabId);
|
||||
@@ -4796,15 +5402,27 @@
|
||||
}
|
||||
await clearCurrentActivation();
|
||||
throw sanitizePhoneRestartStep7Error(sanitizePhoneCodeTimeoutError(error));
|
||||
} finally {
|
||||
activePhoneVerificationLogStep = previousLogStep;
|
||||
activePhoneVerificationLogStepKey = previousLogStepKey;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
cancelSignupPhoneActivation,
|
||||
completeLoginPhoneVerificationFlow,
|
||||
completePhoneVerificationFlow,
|
||||
completeSignupPhoneVerificationFlow,
|
||||
finalizeLoginPhoneActivationAfterSuccess,
|
||||
finalizeSignupPhoneActivationAfterSuccess,
|
||||
normalizeActivation,
|
||||
pollPhoneActivationCode,
|
||||
prepareLoginPhoneActivation,
|
||||
prepareSignupPhoneActivation,
|
||||
reactivatePhoneActivation,
|
||||
requestPhoneActivation,
|
||||
waitForLoginPhoneCode,
|
||||
waitForSignupPhoneCode,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
isLuckmailProvider,
|
||||
isSignupEmailVerificationPageUrl,
|
||||
isSignupPasswordPageUrl,
|
||||
isSignupPhoneVerificationPageUrl = null,
|
||||
isSignupProfilePageUrl = null,
|
||||
reuseOrCreateTab,
|
||||
sendToContentScriptResilient,
|
||||
setEmailState,
|
||||
@@ -62,46 +64,79 @@
|
||||
return { tabId, result: result || {} };
|
||||
}
|
||||
|
||||
function resolveSignupPostEmailState(rawUrl) {
|
||||
function parseUrlSafely(rawUrl) {
|
||||
if (!rawUrl) return null;
|
||||
try {
|
||||
return new URL(rawUrl);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function fallbackSignupPhoneVerificationPageUrl(rawUrl) {
|
||||
const parsed = parseUrlSafely(rawUrl);
|
||||
if (!parsed) return false;
|
||||
return /\/phone-verification(?:[/?#]|$)/i.test(parsed.pathname || '');
|
||||
}
|
||||
|
||||
function fallbackSignupProfilePageUrl(rawUrl) {
|
||||
const parsed = parseUrlSafely(rawUrl);
|
||||
if (!parsed) return false;
|
||||
return /\/create-account\/profile(?:[/?#]|$)/i.test(parsed.pathname || '');
|
||||
}
|
||||
|
||||
function resolveSignupPostIdentityState(rawUrl) {
|
||||
if (isSignupPasswordPageUrl(rawUrl)) {
|
||||
return 'password_page';
|
||||
}
|
||||
if (isSignupEmailVerificationPageUrl(rawUrl)) {
|
||||
return 'verification_page';
|
||||
}
|
||||
const isPhoneVerificationUrl = typeof isSignupPhoneVerificationPageUrl === 'function'
|
||||
? isSignupPhoneVerificationPageUrl(rawUrl)
|
||||
: fallbackSignupPhoneVerificationPageUrl(rawUrl);
|
||||
if (isPhoneVerificationUrl) {
|
||||
return 'phone_verification_page';
|
||||
}
|
||||
const isProfileUrl = typeof isSignupProfilePageUrl === 'function'
|
||||
? isSignupProfilePageUrl(rawUrl)
|
||||
: fallbackSignupProfilePageUrl(rawUrl);
|
||||
if (isProfileUrl) {
|
||||
return 'profile_page';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
async function ensureSignupPostEmailPageReadyInTab(tabId, step = 2, options = {}) {
|
||||
async function ensureSignupPostIdentityPageReadyInTab(tabId, step = 2, options = {}) {
|
||||
const { skipUrlWait = false } = options;
|
||||
let landingUrl = '';
|
||||
let landingState = '';
|
||||
|
||||
if (!skipUrlWait) {
|
||||
const matchedTab = await waitForTabUrlMatch(tabId, (url) => Boolean(resolveSignupPostEmailState(url)), {
|
||||
const matchedTab = await waitForTabUrlMatch(tabId, (url) => Boolean(resolveSignupPostIdentityState(url)), {
|
||||
timeoutMs: 45000,
|
||||
retryDelayMs: 300,
|
||||
});
|
||||
if (!matchedTab) {
|
||||
throw new Error('等待邮箱提交后的页面跳转超时,请检查页面是否仍停留在邮箱输入页。');
|
||||
throw new Error('等待注册身份提交后的页面跳转超时,请检查页面是否仍停留在输入页。');
|
||||
}
|
||||
|
||||
landingUrl = matchedTab.url || '';
|
||||
landingState = resolveSignupPostEmailState(landingUrl);
|
||||
landingState = resolveSignupPostIdentityState(landingUrl);
|
||||
}
|
||||
|
||||
if (!landingState) {
|
||||
try {
|
||||
const currentTab = await chrome.tabs.get(tabId);
|
||||
landingUrl = landingUrl || currentTab?.url || '';
|
||||
landingState = resolveSignupPostEmailState(landingUrl);
|
||||
landingState = resolveSignupPostIdentityState(landingUrl);
|
||||
} catch {
|
||||
landingUrl = landingUrl || '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!landingState) {
|
||||
throw new Error(`邮箱提交后未能识别当前页面,既不是密码页也不是邮箱验证码页。URL: ${landingUrl || 'unknown'}`);
|
||||
throw new Error(`注册身份提交后未能识别当前页面,既不是密码页、验证码页,也不是资料页。URL: ${landingUrl || 'unknown'}`);
|
||||
}
|
||||
|
||||
await ensureContentScriptReadyOnTab('signup-page', tabId, {
|
||||
@@ -109,12 +144,12 @@
|
||||
injectSource: 'signup-page',
|
||||
timeoutMs: 45000,
|
||||
retryDelayMs: 900,
|
||||
logMessage: landingState === 'verification_page'
|
||||
? `步骤 ${step}:邮箱验证码页仍在加载,正在等待页面恢复...`
|
||||
: `步骤 ${step}:密码页仍在加载,正在重试连接内容脚本...`,
|
||||
logMessage: landingState === 'password_page'
|
||||
? `步骤 ${step}:密码页仍在加载,正在重试连接内容脚本...`
|
||||
: `步骤 ${step}:注册后续页面仍在加载,正在等待页面恢复...`,
|
||||
});
|
||||
|
||||
if (landingState === 'verification_page') {
|
||||
if (landingState !== 'password_page') {
|
||||
return {
|
||||
ready: true,
|
||||
state: landingState,
|
||||
@@ -145,6 +180,10 @@
|
||||
};
|
||||
}
|
||||
|
||||
async function ensureSignupPostEmailPageReadyInTab(tabId, step = 2, options = {}) {
|
||||
return ensureSignupPostIdentityPageReadyInTab(tabId, step, options);
|
||||
}
|
||||
|
||||
async function ensureSignupPasswordPageReadyInTab(tabId, step = 2, options = {}) {
|
||||
const result = await ensureSignupPostEmailPageReadyInTab(tabId, step, options);
|
||||
if (result.state !== 'password_page') {
|
||||
@@ -240,6 +279,7 @@
|
||||
|
||||
return {
|
||||
ensureSignupEntryPageReady,
|
||||
ensureSignupPostIdentityPageReadyInTab,
|
||||
ensureSignupPostEmailPageReadyInTab,
|
||||
finalizeSignupPasswordSubmitInTab,
|
||||
ensureSignupPasswordPageReadyInTab,
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
async function executePlusCheckoutCreate(state = {}) {
|
||||
const paymentMethod = normalizePlusPaymentMethod(state?.plusPaymentMethod);
|
||||
const paymentMethodLabel = getPlusPaymentMethodLabel(paymentMethod);
|
||||
const checkoutModeLabel = getCheckoutModeLabel(state);
|
||||
await addLog('步骤 6:正在新打开 ChatGPT 会话页,准备创建 Plus Checkout...', 'info');
|
||||
const tabId = await openFreshChatGptTabForCheckoutCreate();
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
resolveVerificationStep,
|
||||
rerunStep7ForStep8Recovery,
|
||||
reuseOrCreateTab,
|
||||
phoneVerificationHelpers = null,
|
||||
resolveSignupMethod = () => 'email',
|
||||
setState,
|
||||
shouldUseCustomRegistrationEmail,
|
||||
sleepWithStop,
|
||||
@@ -97,6 +99,13 @@
|
||||
return String(value || '').trim().toLowerCase();
|
||||
}
|
||||
|
||||
function isPhoneLoginState(state = {}) {
|
||||
return String(state?.accountIdentifierType || '').trim().toLowerCase() === 'phone'
|
||||
|| resolveSignupMethod(state) === 'phone'
|
||||
|| Boolean(state?.signupPhoneCompletedActivation)
|
||||
|| Boolean(state?.signupPhoneActivation);
|
||||
}
|
||||
|
||||
async function completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, options = {}) {
|
||||
await setState({
|
||||
step8VerificationTargetEmail: '',
|
||||
@@ -206,9 +215,45 @@
|
||||
return Math.max(0, Number(STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS) || 0);
|
||||
}
|
||||
|
||||
async function executeLoginPhoneCodeStep(state, signupTabId, visibleStep) {
|
||||
if (!Number.isInteger(signupTabId)) {
|
||||
throw new Error(`步骤 ${visibleStep}:认证页面标签页已关闭,无法继续手机号登录验证码流程。`);
|
||||
}
|
||||
if (typeof phoneVerificationHelpers?.completeLoginPhoneVerificationFlow !== 'function') {
|
||||
throw new Error(`步骤 ${visibleStep}:手机号登录验证码流程不可用,接码模块尚未初始化。`);
|
||||
}
|
||||
|
||||
const result = await phoneVerificationHelpers.completeLoginPhoneVerificationFlow(signupTabId, {
|
||||
state,
|
||||
visibleStep,
|
||||
});
|
||||
|
||||
await completeStepFromBackground(visibleStep, {
|
||||
phoneVerification: true,
|
||||
loginPhoneVerification: true,
|
||||
code: result?.code || '',
|
||||
});
|
||||
return result || {};
|
||||
}
|
||||
|
||||
async function runStep8Attempt(state, runtime = {}) {
|
||||
const visibleStep = getVisibleStep(state, 8);
|
||||
activeFetchLoginCodeStep = visibleStep;
|
||||
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);
|
||||
}
|
||||
|
||||
if (isPhoneLoginState(state)) {
|
||||
return executeLoginPhoneCodeStep(state, authTabId, visibleStep);
|
||||
}
|
||||
|
||||
const mail = getMailConfig(state);
|
||||
if (mail.error) throw new Error(mail.error);
|
||||
const stateLastResendAt = Number(state?.loginVerificationRequestedAt) || 0;
|
||||
@@ -222,16 +267,6 @@
|
||||
? Math.max(0, stepStartedAt - MAIL_2925_FILTER_LOOKBACK_MS)
|
||||
: stepStartedAt;
|
||||
const verificationSessionKey = `8:${stepStartedAt}`;
|
||||
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();
|
||||
const pageState = await ensureStep8VerificationPageReady({
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
shouldUseCustomRegistrationEmail,
|
||||
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
|
||||
throwIfStopped,
|
||||
phoneVerificationHelpers = null,
|
||||
resolveSignupMethod = () => 'email',
|
||||
} = deps;
|
||||
|
||||
function buildSignupProfileForVerificationStep() {
|
||||
@@ -58,6 +60,32 @@
|
||||
return String(state?.mail2925BaseEmail || '').trim().toLowerCase();
|
||||
}
|
||||
|
||||
function isPhoneSignupState(state = {}) {
|
||||
return resolveSignupMethod(state) === 'phone'
|
||||
|| state?.accountIdentifierType === 'phone'
|
||||
|| Boolean(state?.signupPhoneActivation);
|
||||
}
|
||||
|
||||
async function executeSignupPhoneCodeStep(state, signupTabId) {
|
||||
if (typeof phoneVerificationHelpers?.completeSignupPhoneVerificationFlow !== 'function') {
|
||||
throw new Error('步骤 4:手机号注册验证码流程不可用,接码模块尚未初始化。');
|
||||
}
|
||||
|
||||
const signupProfile = buildSignupProfileForVerificationStep();
|
||||
const result = await phoneVerificationHelpers.completeSignupPhoneVerificationFlow(signupTabId, {
|
||||
state,
|
||||
signupProfile,
|
||||
});
|
||||
|
||||
await completeStepFromBackground(4, {
|
||||
phoneVerification: true,
|
||||
code: result?.code || '',
|
||||
...(result?.skipProfileStep ? { skipProfileStep: true } : {}),
|
||||
...(result?.skipProfileStepReason ? { skipProfileStepReason: result.skipProfileStepReason } : {}),
|
||||
});
|
||||
return result || {};
|
||||
}
|
||||
|
||||
async function focusOrOpenMailTab(mail) {
|
||||
const alive = await isTabAlive(mail.source);
|
||||
if (alive) {
|
||||
@@ -81,13 +109,7 @@
|
||||
}
|
||||
|
||||
async function executeStep4(state) {
|
||||
const mail = getMailConfig(state);
|
||||
if (mail.error) throw new Error(mail.error);
|
||||
|
||||
const stepStartedAt = Date.now();
|
||||
const verificationFilterAfterTimestamp = mail.provider === '2925'
|
||||
? Math.max(0, stepStartedAt - MAIL_2925_FILTER_LOOKBACK_MS)
|
||||
: stepStartedAt;
|
||||
const verificationSessionKey = `4:${stepStartedAt}`;
|
||||
const signupTabId = await getTabId('signup-page');
|
||||
|
||||
@@ -175,11 +197,22 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPhoneSignupState(state)) {
|
||||
return executeSignupPhoneCodeStep(state, signupTabId);
|
||||
}
|
||||
|
||||
if (shouldUseCustomRegistrationEmail(state)) {
|
||||
await confirmCustomVerificationStepBypass(4);
|
||||
return;
|
||||
}
|
||||
|
||||
const mail = getMailConfig(state);
|
||||
if (mail.error) throw new Error(mail.error);
|
||||
|
||||
const verificationFilterAfterTimestamp = mail.provider === '2925'
|
||||
? Math.max(0, stepStartedAt - MAIL_2925_FILTER_LOOKBACK_MS)
|
||||
: stepStartedAt;
|
||||
|
||||
if (mail.source === 'icloud-mail' && typeof ensureIcloudMailSession === 'function') {
|
||||
await addLog('步骤 4:正在确认 iCloud 邮箱登录态...', 'info');
|
||||
await ensureIcloudMailSession({
|
||||
|
||||
@@ -15,10 +15,32 @@
|
||||
SIGNUP_PAGE_INJECT_FILES,
|
||||
} = deps;
|
||||
|
||||
function resolveStep3AccountIdentity(state = {}) {
|
||||
const resolvedEmail = String(state?.email || '').trim();
|
||||
const signupPhoneNumber = String(
|
||||
state?.signupPhoneNumber
|
||||
|| (state?.accountIdentifierType === 'phone' ? state?.accountIdentifier : '')
|
||||
|| ''
|
||||
).trim();
|
||||
const accountIdentifierType = signupPhoneNumber && state?.accountIdentifierType === 'phone'
|
||||
? 'phone'
|
||||
: (resolvedEmail ? 'email' : (signupPhoneNumber ? 'phone' : 'email'));
|
||||
const accountIdentifier = accountIdentifierType === 'phone'
|
||||
? signupPhoneNumber
|
||||
: resolvedEmail;
|
||||
|
||||
return {
|
||||
accountIdentifierType,
|
||||
accountIdentifier,
|
||||
email: resolvedEmail,
|
||||
phoneNumber: signupPhoneNumber,
|
||||
};
|
||||
}
|
||||
|
||||
async function executeStep3(state) {
|
||||
const resolvedEmail = state.email;
|
||||
if (!resolvedEmail) {
|
||||
throw new Error('缺少邮箱地址,请先完成步骤 2。');
|
||||
const identity = resolveStep3AccountIdentity(state);
|
||||
if (!identity.accountIdentifier) {
|
||||
throw new Error('缺少注册账号,请先完成步骤 2。');
|
||||
}
|
||||
|
||||
const signupTabId = await getTabId('signup-page');
|
||||
@@ -30,7 +52,13 @@
|
||||
await setPasswordState(password);
|
||||
|
||||
const accounts = state.accounts || [];
|
||||
accounts.push({ email: resolvedEmail, createdAt: new Date().toISOString() });
|
||||
accounts.push({
|
||||
email: identity.email,
|
||||
phoneNumber: identity.phoneNumber,
|
||||
accountIdentifierType: identity.accountIdentifierType,
|
||||
accountIdentifier: identity.accountIdentifier,
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
await setState({ accounts });
|
||||
|
||||
await chrome.tabs.update(signupTabId, { active: true });
|
||||
@@ -42,14 +70,23 @@
|
||||
logMessage: '步骤 3:密码页内容脚本未就绪,正在等待页面恢复...',
|
||||
});
|
||||
|
||||
const identityLabel = identity.accountIdentifierType === 'phone'
|
||||
? `注册手机号为 ${identity.accountIdentifier}`
|
||||
: `邮箱为 ${identity.accountIdentifier}`;
|
||||
await addLog(
|
||||
`步骤 3:正在填写密码,邮箱为 ${resolvedEmail},密码为${state.customPassword ? '自定义' : '自动生成'}(${password.length} 位)`
|
||||
`步骤 3:正在填写密码,${identityLabel},密码为${state.customPassword ? '自定义' : '自动生成'}(${password.length} 位)`
|
||||
);
|
||||
await sendToContentScript('signup-page', {
|
||||
type: 'EXECUTE_STEP',
|
||||
step: 3,
|
||||
source: 'background',
|
||||
payload: { email: resolvedEmail, password },
|
||||
payload: {
|
||||
email: identity.email,
|
||||
phoneNumber: identity.phoneNumber,
|
||||
accountIdentifierType: identity.accountIdentifierType,
|
||||
accountIdentifier: identity.accountIdentifier,
|
||||
password,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -41,12 +41,30 @@
|
||||
}
|
||||
|
||||
async function executeStep7(state) {
|
||||
if (!state.email) {
|
||||
throw new Error('缺少邮箱地址,请先完成步骤 3。');
|
||||
}
|
||||
|
||||
const visibleStep = Math.floor(Number(state?.visibleStep) || 0);
|
||||
const completionStep = visibleStep > 0 ? visibleStep : 7;
|
||||
const resolvedIdentifierType = String(
|
||||
state?.accountIdentifierType
|
||||
|| (state?.signupPhoneNumber ? 'phone' : '')
|
||||
|| ''
|
||||
).trim().toLowerCase() === 'phone'
|
||||
? 'phone'
|
||||
: 'email';
|
||||
const phoneNumber = String(
|
||||
state?.signupPhoneNumber
|
||||
|| (resolvedIdentifierType === 'phone' ? state?.accountIdentifier : '')
|
||||
|| state?.signupPhoneCompletedActivation?.phoneNumber
|
||||
|| state?.signupPhoneActivation?.phoneNumber
|
||||
|| ''
|
||||
).trim();
|
||||
const email = String(
|
||||
state?.email
|
||||
|| (resolvedIdentifierType === 'email' ? state?.accountIdentifier : '')
|
||||
|| ''
|
||||
).trim();
|
||||
if (!email && !phoneNumber) {
|
||||
throw new Error('缺少登录账号,请先完成步骤 2 和步骤 3。');
|
||||
}
|
||||
|
||||
let attempt = 0;
|
||||
let lastError = null;
|
||||
@@ -57,6 +75,28 @@
|
||||
try {
|
||||
const currentState = attempt === 1 ? state : await getState();
|
||||
const password = currentState.password || currentState.customPassword || '';
|
||||
const currentIdentifierType = String(
|
||||
currentState?.accountIdentifierType
|
||||
|| (currentState?.signupPhoneNumber ? 'phone' : '')
|
||||
|| resolvedIdentifierType
|
||||
).trim().toLowerCase() === 'phone'
|
||||
? 'phone'
|
||||
: 'email';
|
||||
const currentPhoneNumber = String(
|
||||
currentState?.signupPhoneNumber
|
||||
|| (currentIdentifierType === 'phone' ? currentState?.accountIdentifier : '')
|
||||
|| currentState?.signupPhoneCompletedActivation?.phoneNumber
|
||||
|| currentState?.signupPhoneActivation?.phoneNumber
|
||||
|| phoneNumber
|
||||
).trim();
|
||||
const currentEmail = String(
|
||||
currentState?.email
|
||||
|| (currentIdentifierType === 'email' ? currentState?.accountIdentifier : '')
|
||||
|| email
|
||||
).trim();
|
||||
const accountIdentifier = currentIdentifierType === 'phone'
|
||||
? currentPhoneNumber
|
||||
: currentEmail;
|
||||
const oauthUrl = await refreshOAuthUrlBeforeStep6(currentState);
|
||||
if (typeof startOAuthFlowTimeoutWindow === 'function') {
|
||||
await startOAuthFlowTimeoutWindow({ step: completionStep, oauthUrl });
|
||||
@@ -90,7 +130,18 @@
|
||||
step: 7,
|
||||
source: 'background',
|
||||
payload: {
|
||||
email: currentState.email,
|
||||
email: currentEmail,
|
||||
phoneNumber: currentPhoneNumber,
|
||||
countryId: currentState?.signupPhoneCompletedActivation?.countryId
|
||||
?? currentState?.signupPhoneActivation?.countryId
|
||||
?? null,
|
||||
countryLabel: String(
|
||||
currentState?.signupPhoneCompletedActivation?.countryLabel
|
||||
|| currentState?.signupPhoneActivation?.countryLabel
|
||||
|| ''
|
||||
).trim(),
|
||||
accountIdentifier,
|
||||
loginIdentifierType: currentIdentifierType,
|
||||
password,
|
||||
visibleStep: completionStep,
|
||||
},
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
ensureSignupAuthEntryPageReady,
|
||||
ensureSignupEntryPageReady,
|
||||
ensureSignupPostEmailPageReadyInTab,
|
||||
ensureSignupPostIdentityPageReadyInTab = ensureSignupPostEmailPageReadyInTab,
|
||||
getTabId,
|
||||
isTabAlive,
|
||||
phoneVerificationHelpers = null,
|
||||
resolveSignupMethod = () => 'email',
|
||||
resolveSignupEmailForFlow,
|
||||
sendToContentScriptResilient,
|
||||
SIGNUP_PAGE_INJECT_FILES,
|
||||
@@ -94,7 +97,7 @@
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
async function submitSignupEmail(resolvedEmail, options = {}) {
|
||||
async function sendSignupIdentity(payload = {}, options = {}) {
|
||||
const {
|
||||
timeoutMs = 35000,
|
||||
retryDelayMs = 700,
|
||||
@@ -106,7 +109,7 @@
|
||||
type: 'EXECUTE_STEP',
|
||||
step: 2,
|
||||
source: 'background',
|
||||
payload: { email: resolvedEmail },
|
||||
payload,
|
||||
}, {
|
||||
timeoutMs,
|
||||
retryDelayMs,
|
||||
@@ -117,9 +120,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function executeStep2(state) {
|
||||
const resolvedEmail = await resolveSignupEmailForFlow(state);
|
||||
async function submitSignupEmail(resolvedEmail, options = {}) {
|
||||
return sendSignupIdentity({ email: resolvedEmail }, options);
|
||||
}
|
||||
|
||||
async function submitSignupPhone(phoneNumber, activation, options = {}) {
|
||||
return sendSignupIdentity({
|
||||
signupMethod: 'phone',
|
||||
phoneNumber,
|
||||
countryId: activation?.countryId ?? null,
|
||||
countryLabel: String(activation?.countryLabel || '').trim(),
|
||||
}, {
|
||||
logMessage: '步骤 2:官网注册入口正在切换,等待手机号注册入口恢复...',
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureSignupTabForStep2() {
|
||||
let signupTabId = await getTabId('signup-page');
|
||||
if (!signupTabId || !(await isTabAlive('signup-page'))) {
|
||||
await addLog('步骤 2:未发现可用的注册页标签,正在重新打开 ChatGPT 官网...', 'warn');
|
||||
@@ -134,6 +151,84 @@
|
||||
logMessage: '步骤 2:注册入口页内容脚本未就绪,正在等待页面恢复...',
|
||||
});
|
||||
}
|
||||
return signupTabId;
|
||||
}
|
||||
|
||||
async function executeSignupPhoneEntry(state) {
|
||||
if (typeof phoneVerificationHelpers?.prepareSignupPhoneActivation !== 'function') {
|
||||
throw new Error('手机号注册流程不可用:接码模块尚未初始化。');
|
||||
}
|
||||
|
||||
let signupTabId = await ensureSignupTabForStep2();
|
||||
if (await shouldForceAuthEntryRetry(signupTabId)) {
|
||||
await addLog('步骤 2:检测到当前位于已登录 ChatGPT 首页,先切换认证入口页再提交手机号。', 'warn');
|
||||
try {
|
||||
signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId;
|
||||
} catch (entryError) {
|
||||
const entryErrorMessage = getErrorMessage(entryError);
|
||||
if (await failStep2OnLoggedInSession(signupTabId, entryErrorMessage)) {
|
||||
return;
|
||||
}
|
||||
await addLog('步骤 2:切换认证入口失败,正在重新打开官网入口并重试提交手机号...', 'warn');
|
||||
signupTabId = (await ensureSignupEntryPageReady(2)).tabId;
|
||||
}
|
||||
}
|
||||
|
||||
const activation = await phoneVerificationHelpers.prepareSignupPhoneActivation(state);
|
||||
let step2Result = await submitSignupPhone(activation.phoneNumber, activation, {
|
||||
timeoutMs: 45000,
|
||||
retryDelayMs: 700,
|
||||
logMessage: '步骤 2:官网注册入口正在切换,等待手机号注册入口恢复...',
|
||||
});
|
||||
|
||||
if (step2Result?.error) {
|
||||
const errorMessage = getErrorMessage(step2Result.error);
|
||||
if (isSignupEntryUnavailableErrorMessage(errorMessage) || isRetryableStep2TransportErrorMessage(errorMessage)) {
|
||||
await addLog('步骤 2:手机号注册入口不可用或通信超时,正在切换认证入口页后重试一次...', 'warn');
|
||||
signupTabId = (await ensureSignupAuthEntryPageReady(2)).tabId;
|
||||
step2Result = await submitSignupPhone(activation.phoneNumber, activation, {
|
||||
timeoutMs: 45000,
|
||||
retryDelayMs: 700,
|
||||
logMessage: '步骤 2:认证入口页已打开,正在重新提交手机号...',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (step2Result?.error) {
|
||||
const finalErrorMessage = getErrorMessage(step2Result.error);
|
||||
if (
|
||||
(isSignupEntryUnavailableErrorMessage(finalErrorMessage)
|
||||
|| isRetryableStep2TransportErrorMessage(finalErrorMessage))
|
||||
&& await failStep2OnLoggedInSession(signupTabId, finalErrorMessage)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (typeof phoneVerificationHelpers?.cancelSignupPhoneActivation === 'function') {
|
||||
await phoneVerificationHelpers.cancelSignupPhoneActivation(state, activation).catch(() => {});
|
||||
}
|
||||
throw new Error(finalErrorMessage);
|
||||
}
|
||||
|
||||
await addLog(`步骤 2:手机号 ${activation.phoneNumber} 已提交,正在等待页面加载并确认下一步入口...`);
|
||||
const landingResult = await ensureSignupPostIdentityPageReadyInTab(signupTabId, 2, {
|
||||
skipUrlWait: Boolean(step2Result?.alreadyOnPasswordPage),
|
||||
});
|
||||
|
||||
await completeStepFromBackground(2, {
|
||||
accountIdentifierType: 'phone',
|
||||
accountIdentifier: activation.phoneNumber,
|
||||
signupPhoneNumber: activation.phoneNumber,
|
||||
signupPhoneActivation: activation,
|
||||
nextSignupState: landingResult?.state || step2Result?.state || 'password_page',
|
||||
nextSignupUrl: landingResult?.url || step2Result?.url || '',
|
||||
skippedPasswordStep: landingResult?.state === 'phone_verification_page' || landingResult?.state === 'profile_page',
|
||||
});
|
||||
}
|
||||
|
||||
async function executeSignupEmailEntry(state) {
|
||||
const resolvedEmail = await resolveSignupEmailForFlow(state);
|
||||
|
||||
let signupTabId = await ensureSignupTabForStep2();
|
||||
|
||||
if (await shouldForceAuthEntryRetry(signupTabId)) {
|
||||
await addLog('步骤 2:检测到当前位于已登录 ChatGPT 首页,先切换认证入口页再提交邮箱。', 'warn');
|
||||
@@ -214,12 +309,21 @@
|
||||
|
||||
await completeStepFromBackground(2, {
|
||||
email: resolvedEmail,
|
||||
accountIdentifierType: 'email',
|
||||
accountIdentifier: resolvedEmail,
|
||||
nextSignupState: landingResult?.state || 'password_page',
|
||||
nextSignupUrl: landingResult?.url || step2Result?.url || '',
|
||||
skippedPasswordStep: landingResult?.state === 'verification_page',
|
||||
});
|
||||
}
|
||||
|
||||
async function executeStep2(state) {
|
||||
if (resolveSignupMethod(state) === 'phone') {
|
||||
return executeSignupPhoneEntry(state);
|
||||
}
|
||||
return executeSignupEmailEntry(state);
|
||||
}
|
||||
|
||||
return { executeStep2 };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user