新增手机号绑定邮箱后邮箱模式重登流程

This commit is contained in:
QLHazyCoder
2026-05-16 03:23:28 +08:00
parent 727d1523dd
commit efad1f94d7
16 changed files with 715 additions and 35 deletions
+18 -6
View File
@@ -270,6 +270,8 @@
13: 'confirm-oauth',
14: 'platform-verify',
15: 'platform-verify',
16: 'confirm-oauth',
17: 'platform-verify',
});
function getStepKeyForState(step, state = {}) {
@@ -613,12 +615,18 @@
const stateForStep = await getState();
const stepKey = getStepKeyForState(step, stateForStep);
if (stepKey === 'oauth-login') {
await syncStepAccountIdentityFromPayload(payload);
if (stepKey === 'oauth-login' || stepKey === 'relogin-bound-email') {
if (stepKey === 'oauth-login') {
await syncStepAccountIdentityFromPayload(payload);
}
if (payload.skipLoginVerificationStep) {
await setState({ loginVerificationRequestedAt: null });
const latestState = await getState();
const loginCodeStep = findStepByKeyAfter(step, 'fetch-login-code', latestState);
const loginCodeStep = findStepByKeyAfter(
step,
stepKey === 'relogin-bound-email' ? 'fetch-bound-email-login-code' : 'fetch-login-code',
latestState
);
if (loginCodeStep) {
const currentStatus = getNodeStatusByStep(loginCodeStep, latestState);
if (!isStepProtectedFromAutoSkip(currentStatus)) {
@@ -635,7 +643,7 @@
return;
}
if (stepKey === 'fetch-login-code') {
if (stepKey === 'fetch-login-code' || stepKey === 'fetch-bound-email-login-code') {
await setState({
...(payload.phoneVerification || payload.loginPhoneVerification ? {
currentPhoneVerificationCode: '',
@@ -649,7 +657,7 @@
return;
}
if (stepKey === 'post-login-phone-verification') {
if (stepKey === 'post-login-phone-verification' || stepKey === 'post-bound-email-phone-verification') {
await setState({
currentPhoneVerificationCode: '',
signupPhoneVerificationRequestedAt: null,
@@ -1333,10 +1341,14 @@
const plusPaymentChanged = Object.prototype.hasOwnProperty.call(updates, 'plusPaymentMethod')
&& normalizePlusPaymentMethodForDisplay(currentState?.plusPaymentMethod || 'paypal')
!== normalizePlusPaymentMethodForDisplay(updates.plusPaymentMethod || 'paypal');
const phoneSignupReloginAfterBindEmailChanged = Object.prototype.hasOwnProperty.call(updates, 'phoneSignupReloginAfterBindEmailEnabled')
&& Boolean(currentState?.phoneSignupReloginAfterBindEmailEnabled) !== Boolean(updates.phoneSignupReloginAfterBindEmailEnabled);
const nextPlusModeEnabled = Object.prototype.hasOwnProperty.call(updates, 'plusModeEnabled')
? Boolean(updates.plusModeEnabled)
: Boolean(currentState?.plusModeEnabled);
const stepModeChanged = modeChanged || (nextPlusModeEnabled && plusPaymentChanged);
const stepModeChanged = modeChanged
|| (nextPlusModeEnabled && plusPaymentChanged)
|| phoneSignupReloginAfterBindEmailChanged;
const oauthFlowTimeoutDisabled = Object.prototype.hasOwnProperty.call(updates, 'oauthFlowTimeoutEnabled')
&& updates.oauthFlowTimeoutEnabled === false;
await setPersistentSettings(updates);
+105 -8
View File
@@ -124,6 +124,34 @@
return String(value || '').trim().toLowerCase();
}
function resolveBoundEmailLoginTarget(state = {}, visibleStep = 0) {
const email = String(
state?.step8VerificationTargetEmail
|| state?.email
|| state?.registrationEmailState?.current
|| ''
).trim();
if (!email) {
throw new Error(`步骤 ${visibleStep || 0}:缺少绑定邮箱,无法使用邮箱模式重新发起 OAuth 登录。`);
}
return email;
}
function buildBoundEmailLoginState(state = {}, visibleStep = 0) {
const email = resolveBoundEmailLoginTarget(state, visibleStep);
return {
...state,
forceLoginIdentifierType: 'email',
forceEmailLogin: true,
signupMethod: 'email',
resolvedSignupMethod: 'email',
accountIdentifierType: 'email',
accountIdentifier: email,
email,
step8VerificationTargetEmail: normalizeStep8VerificationTargetEmail(email),
};
}
async function getLoginAuthStateFromContent(visibleStep, options = {}) {
if (typeof sendToContentScriptResilient !== 'function') {
return {};
@@ -142,7 +170,7 @@
retryDelayMs: 600,
logMessage: options.logMessage || `步骤 ${visibleStep}:认证页正在切换,等待页面重新就绪...`,
logStep: visibleStep,
logStepKey: 'fetch-login-code',
logStepKey: options.logStepKey || activeFetchLoginCodeStepKey || 'fetch-login-code',
}
);
if (result?.error) {
@@ -240,9 +268,11 @@
loginVerificationRequestedAt: null,
});
const fromRecovery = Boolean(options.fromRecovery);
const stepKey = options.stepKey || activeFetchLoginCodeStepKey || 'fetch-login-code';
await addLog(
`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页${fromRecovery ? '(轮询失败后复核)' : ''},跳过登录验证码拉取并继续后续流程。`,
'warn'
'warn',
{ step: visibleStep, stepKey }
);
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(options.nodeId || 'fetch-login-code', {
@@ -258,9 +288,11 @@
step8VerificationTargetEmail: '',
loginVerificationRequestedAt: null,
});
const stepKey = options.stepKey || activeFetchLoginCodeStepKey || 'fetch-login-code';
await addLog(
`步骤 ${visibleStep}:当前认证页已进入手机号验证流程,跳过登录邮箱验证码,交给后续“手机号验证”步骤处理。`,
'warn'
'warn',
{ step: visibleStep, stepKey }
);
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(options.nodeId || 'fetch-login-code', {
@@ -416,9 +448,10 @@
}
async function completePostLoginPhoneVerificationSkippedOnOauth(visibleStep, options = {}) {
const stepKey = options.stepKey || 'post-login-phone-verification';
await addLog(`步骤 ${visibleStep}:当前认证页已进入 OAuth 授权页,跳过手机号验证步骤。`, 'warn', {
step: visibleStep,
stepKey: 'post-login-phone-verification',
stepKey,
});
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(options.nodeId || 'post-login-phone-verification', {
@@ -428,18 +461,22 @@
}
}
async function executePostLoginPhoneVerification(state) {
async function executePostLoginPhoneVerification(state, runtime = {}) {
const visibleStep = getVisibleStep(state, 9);
activeFetchLoginCodeStep = visibleStep;
activeFetchLoginCodeStepKey = 'post-login-phone-verification';
activeFetchLoginCodeStepKey = runtime.stepKey || 'post-login-phone-verification';
const authTabId = await ensureAuthTabForPostLoginStep(state, visibleStep);
const pageState = await getLoginAuthStateFromContent(visibleStep, {
timeoutMs: await getStep8ReadyTimeoutMs('确认手机号验证页或 OAuth 授权页已就绪', state?.oauthUrl || '', visibleStep),
logMessage: `步骤 ${visibleStep}:正在确认是否需要手机号验证...`,
logStepKey: activeFetchLoginCodeStepKey,
});
if (pageState?.state === 'oauth_consent_page') {
await completePostLoginPhoneVerificationSkippedOnOauth(visibleStep, { nodeId: state?.nodeId });
await completePostLoginPhoneVerificationSkippedOnOauth(visibleStep, {
nodeId: state?.nodeId || runtime.fallbackNodeId,
stepKey: activeFetchLoginCodeStepKey,
});
return;
}
if (pageState?.state !== 'add_phone_page' && pageState?.state !== 'phone_verification_page') {
@@ -457,7 +494,7 @@
visibleStep,
});
if (typeof completeNodeFromBackground === 'function') {
await completeNodeFromBackground(state?.nodeId || 'post-login-phone-verification', {
await completeNodeFromBackground(state?.nodeId || runtime.fallbackNodeId || 'post-login-phone-verification', {
phoneVerification: true,
postLoginPhoneVerification: true,
code: result?.code || '',
@@ -663,6 +700,64 @@
});
}
async function executeBoundEmailLoginCode(state) {
const visibleStep = getVisibleStep(state, 11);
activeFetchLoginCodeStep = visibleStep;
activeFetchLoginCodeStepKey = 'fetch-bound-email-login-code';
const preparedState = buildBoundEmailLoginState(state, visibleStep);
const authTabId = await getTabId('signup-page');
if (authTabId) {
await chrome.tabs.update(authTabId, { active: true });
} else {
if (!preparedState.oauthUrl) {
throw new Error(`步骤 ${visibleStep}:缺少登录用 OAuth 链接,请先完成绑定邮箱后刷新 OAuth 并登录。`);
}
await reuseOrCreateTab('signup-page', preparedState.oauthUrl);
}
throwIfStopped();
const pageState = await ensureStep8VerificationPageReady({
visibleStep,
authLoginStep: Math.max(1, visibleStep - 1),
allowPhoneVerificationPage: true,
allowAddEmailPage: false,
timeoutMs: await getStep8ReadyTimeoutMs('确认绑定邮箱登录验证码页已就绪', preparedState?.oauthUrl || '', visibleStep),
});
if (pageState?.state === 'oauth_consent_page') {
await completeStep8WhenAuthAlreadyOnOauthConsent(visibleStep, {
nodeId: state?.nodeId || 'fetch-bound-email-login-code',
stepKey: 'fetch-bound-email-login-code',
});
return;
}
if (pageState?.state === 'add_phone_page' || pageState?.state === 'phone_verification_page') {
await completeStep8WhenDeferredToPostLoginPhone(visibleStep, pageState, {
nodeId: state?.nodeId || 'fetch-bound-email-login-code',
stepKey: 'fetch-bound-email-login-code',
});
return;
}
if (pageState?.state === 'add_email_page') {
throw new Error(`步骤 ${visibleStep}:绑定邮箱后邮箱模式登录不应再进入添加邮箱页。URL: ${pageState?.url || ''}`.trim());
}
if (pageState?.state !== 'verification_page') {
throw new Error(`步骤 ${visibleStep}:绑定邮箱后获取登录验证码只处理邮箱登录验证码页,当前状态:${pageState?.state || 'unknown'}。URL: ${pageState?.url || ''}`.trim());
}
return pollEmailVerificationCode(preparedState, pageState, visibleStep, {
stickyLastResendAt: Number(preparedState?.loginVerificationRequestedAt) || 0,
});
}
async function executeBoundEmailPostLoginPhoneVerification(state) {
return executePostLoginPhoneVerification(state, {
stepKey: 'post-bound-email-phone-verification',
fallbackNodeId: 'post-bound-email-phone-verification',
});
}
async function runStep8Attempt(state, runtime = {}) {
const visibleStep = getVisibleStep(state, 8);
activeFetchLoginCodeStep = visibleStep;
@@ -855,6 +950,8 @@
executePostLoginPhoneVerification,
executeBindEmail,
executeFetchBindEmailCode,
executeBoundEmailLoginCode,
executeBoundEmailPostLoginPhoneVerification,
};
}
+22 -1
View File
@@ -50,6 +50,11 @@
return String(value || '').trim().toLowerCase() === 'phone' ? 'phone' : 'email';
}
function shouldForceStep7EmailLogin(state = {}) {
return normalizeStep7IdentifierType(state?.forceLoginIdentifierType) === 'email'
|| Boolean(state?.forceEmailLogin);
}
function canUseConfiguredPhoneSignup(state = {}) {
return normalizeStep7SignupMethod(state?.signupMethod) === 'phone'
&& Boolean(state?.phoneVerificationEnabled)
@@ -75,6 +80,10 @@
}
function resolveStep7LoginIdentifierType(state = {}, fallbackType = '') {
if (shouldForceStep7EmailLogin(state)) {
return 'email';
}
if (shouldPreferStep7PhoneSignupIdentity(state)) {
return 'phone';
}
@@ -234,7 +243,19 @@
throwIfStopped();
attempt += 1;
try {
const currentState = attempt === 1 ? state : await getState();
const rawCurrentState = attempt === 1 ? state : await getState();
const currentState = shouldForceStep7EmailLogin(state)
? {
...rawCurrentState,
forceLoginIdentifierType: 'email',
forceEmailLogin: true,
signupMethod: 'email',
resolvedSignupMethod: 'email',
accountIdentifierType: 'email',
accountIdentifier: email,
email,
}
: rawCurrentState;
const password = currentState.password || currentState.customPassword || '';
const currentIdentifierType = resolveStep7LoginIdentifierType(currentState, resolvedIdentifierType);
const currentPhoneNumber = currentIdentifierType === 'phone'