fix: align Plus no-payment flow and step5 completion

This commit is contained in:
QLHazyCoder
2026-05-22 01:35:42 +08:00
parent a7b35ee11a
commit 3f6fc3e1e8
17 changed files with 911 additions and 96 deletions
+13 -2
View File
@@ -34,6 +34,7 @@
setStep8PendingReject,
setStep8TabUpdatedListener,
shouldDeferStep9CallbackTimeout,
getStepIdByKeyForState = null,
} = deps;
const LOCALHOST_CALLBACK_LOCAL_TIMEOUT_MS = 240000;
@@ -45,7 +46,17 @@
}
function getAuthLoginStepForVisibleStep(visibleStep) {
return visibleStep >= 12 ? 10 : 7;
return visibleStep >= 12 ? Math.max(1, visibleStep - 3) : 7;
}
function getAuthLoginStepForState(state = {}, visibleStep = 9) {
const authStep = typeof getStepIdByKeyForState === 'function'
? Number(getStepIdByKeyForState('oauth-login', state))
: 0;
if (Number.isInteger(authStep) && authStep > 0) {
return authStep;
}
return getAuthLoginStepForVisibleStep(visibleStep);
}
function addStepLog(step, message, level = 'info') {
@@ -57,7 +68,7 @@
let activeState = state;
if (!activeState.oauthUrl) {
const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep);
const authLoginStep = getAuthLoginStepForState(activeState, visibleStep);
throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${authLoginStep}`);
}
@@ -36,6 +36,7 @@
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS,
throwIfStopped,
getStepIdByKeyForState = null,
} = deps;
let activeFetchLoginCodeStep = null;
let activeFetchLoginCodeStepKey = 'fetch-login-code';
@@ -93,7 +94,17 @@
}
function getAuthLoginStepForVisibleStep(visibleStep) {
return visibleStep >= 11 ? 10 : 7;
return visibleStep >= 11 ? Math.max(1, visibleStep - 1) : 7;
}
function getAuthLoginStepForState(state = {}, visibleStep = 8) {
const authStep = typeof getStepIdByKeyForState === 'function'
? Number(getStepIdByKeyForState('oauth-login', state))
: 0;
if (Number.isInteger(authStep) && authStep > 0) {
return authStep;
}
return getAuthLoginStepForVisibleStep(visibleStep);
}
async function getStep8ReadyTimeoutMs(actionLabel, expectedOauthUrl = '', visibleStep = 8) {
@@ -331,7 +342,7 @@
}
async function recoverStep8PollingFailure(currentState, visibleStep) {
const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep);
const authLoginStep = getAuthLoginStepForState(currentState, visibleStep);
try {
const pageState = await ensureStep8VerificationPageReady({
visibleStep,
@@ -771,7 +782,7 @@
await chrome.tabs.update(authTabId, { active: true });
} else {
if (!state.oauthUrl) {
throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${getAuthLoginStepForVisibleStep(visibleStep)}`);
throw new Error(`缺少登录用 OAuth 链接,请先完成步骤 ${getAuthLoginStepForState(state, visibleStep)}`);
}
await reuseOrCreateTab('openai-auth', state.oauthUrl);
}
@@ -779,7 +790,7 @@
throwIfStopped();
let pageState = await ensureStep8VerificationPageReady({
visibleStep,
authLoginStep: getAuthLoginStepForVisibleStep(visibleStep),
authLoginStep: getAuthLoginStepForState(state, visibleStep),
allowPhoneVerificationPage: true,
allowAddEmailPage: true,
timeoutMs: await getStep8ReadyTimeoutMs('确认登录验证码页已就绪', state?.oauthUrl || '', visibleStep),
@@ -848,7 +859,7 @@
return;
} catch (err) {
const visibleStep = getVisibleStep(currentState, 8);
const authLoginStep = getAuthLoginStepForVisibleStep(visibleStep);
const authLoginStep = getAuthLoginStepForState(currentState, visibleStep);
let currentError = err;
let retryWithoutStep7 = false;
+96 -38
View File
@@ -2961,6 +2961,27 @@ function isLikelyLoggedInChatgptHomeUrl(rawUrl = location.href) {
}
}
function isStep5CompletionChatgptUrl(rawUrl = location.href) {
const url = String(rawUrl || '').trim();
if (!url) {
return false;
}
try {
const parsed = new URL(url);
const protocol = String(parsed.protocol || '').toLowerCase();
const host = String(parsed.hostname || '').toLowerCase();
if (protocol !== 'https:' || !['chatgpt.com', 'www.chatgpt.com'].includes(host)) {
return false;
}
const path = String(parsed.pathname || '');
return !/^\/(?:auth\/|create-account\/|email-verification|log-in|add-phone)(?:[/?#]|$)/i.test(path);
} catch {
return false;
}
}
function getStep4PostVerificationState(options = {}) {
const { ignoreVerificationVisibility = false } = options;
// Newer auth flows can briefly render profile fields before the email-verification
@@ -6693,43 +6714,13 @@ function getStep5PostSubmitSuccessState() {
return null;
}
if (isLikelyLoggedInChatgptHomeUrl()) {
if (isStep5CompletionChatgptUrl()) {
return {
state: 'logged_in_home',
url: location.href,
};
}
if (typeof isOAuthConsentPage === 'function' && isOAuthConsentPage()) {
return {
state: 'oauth_consent',
url: location.href,
};
}
if (typeof isAddPhonePageReady === 'function' && isAddPhonePageReady()) {
return {
state: 'add_phone',
url: location.href,
};
}
if (!isStep5ProfileStillVisible()) {
try {
const parsed = new URL(String(location.href || '').trim());
const host = String(parsed.hostname || '').toLowerCase();
if (['auth.openai.com', 'auth0.openai.com', 'accounts.openai.com'].includes(host)) {
return null;
}
} catch {
// Fall through to the generic "left_profile" success state.
}
return {
state: 'left_profile',
url: location.href,
};
}
return null;
}
@@ -6764,6 +6755,29 @@ function getStep5SubmitState() {
};
}
function logStep5SubmitDebug(message, options = {}) {
const resolvedState = options?.state && typeof options.state === 'object'
? options.state
: getStep5SubmitState();
const summary = [
`url=${resolvedState?.url || location.href}`,
`retryPage=${Boolean(resolvedState?.retryPage)}`,
`retryEnabled=${Boolean(resolvedState?.retryEnabled)}`,
`successState=${resolvedState?.successState || 'none'}`,
`profileVisible=${Boolean(resolvedState?.profileVisible)}`,
`unknownAuthPage=${Boolean(resolvedState?.unknownAuthPage)}`,
`maxCheckAttemptsBlocked=${Boolean(resolvedState?.maxCheckAttemptsBlocked)}`,
`userAlreadyExistsBlocked=${Boolean(resolvedState?.userAlreadyExistsBlocked)}`,
resolvedState?.errorText ? `errorText=${resolvedState.errorText}` : null,
]
.filter(Boolean)
.join(' | ');
log(`步骤 5 [调试] ${message} | ${summary}`, options?.level || 'info', {
step: 5,
stepKey: 'fill-profile',
});
}
async function recoverStep5SubmitRetryPage(payload = {}) {
return recoverCurrentAuthRetryPage({
...payload,
@@ -6780,14 +6794,21 @@ function installStep5NavigationCompletionReporter(completeOnce) {
if (typeof window === 'undefined' || typeof window.addEventListener !== 'function') {
return () => {};
}
const debugLog = typeof logStep5SubmitDebug === 'function'
? logStep5SubmitDebug
: (message, options = {}) => {
if (typeof log === 'function') {
log(`步骤 5 [调试] ${message}`, options?.level || 'info', {
step: 5,
stepKey: 'fill-profile',
});
}
};
const onNavigationStarted = () => {
completeOnce({
navigationStarted: true,
outcome: {
state: 'navigation_started',
url: location.href,
},
const onNavigationStarted = (event) => {
const eventType = String(event?.type || 'navigation').trim() || 'navigation';
debugLog(`检测到页面开始导航(event=${eventType})。`, {
level: 'warn',
});
};
@@ -6801,6 +6822,16 @@ function installStep5NavigationCompletionReporter(completeOnce) {
}
async function waitForStep5SubmitOutcome(options = {}) {
const debugLog = typeof logStep5SubmitDebug === 'function'
? logStep5SubmitDebug
: (message, logOptions = {}) => {
if (typeof log === 'function') {
log(`步骤 5 [调试] ${message}`, logOptions?.level || 'info', {
step: 5,
stepKey: 'fill-profile',
});
}
};
const {
timeoutMs = 120000,
maxAuthRetryRecoveries = 2,
@@ -6828,6 +6859,9 @@ async function waitForStep5SubmitOutcome(options = {}) {
throw new Error(`步骤 5:资料提交后连续进入认证重试页 ${maxAuthRetryRecoveries} 次,页面仍未恢复。URL: ${location.href}`);
}
authRetryRecoveryCount += 1;
debugLog(`检测到资料提交后的认证重试页,准备执行恢复(${authRetryRecoveryCount}/${maxAuthRetryRecoveries})。`, {
level: 'warn',
});
log(`步骤 5:资料提交后进入认证重试页,正在自动恢复(${authRetryRecoveryCount}/${maxAuthRetryRecoveries}...`, 'warn');
await recoverCurrentAuthRetryPage({
flow: 'signup',
@@ -6837,12 +6871,18 @@ async function waitForStep5SubmitOutcome(options = {}) {
step: 5,
timeoutMs: 12000,
});
debugLog('认证重试页恢复动作已完成,准备继续等待最终结果。', {
level: 'info',
});
lastSubmitClickAt = Date.now();
continue;
}
const successState = getStep5PostSubmitSuccessState();
if (successState) {
debugLog(`检测到资料提交成功状态:${successState.state || 'unknown'}`, {
level: 'ok',
});
return successState;
}
@@ -7146,8 +7186,23 @@ async function step5_fillNameBirthday(payload) {
}
let reportedCompletionPayload = null;
const debugLog = typeof logStep5SubmitDebug === 'function'
? logStep5SubmitDebug
: (message, logOptions = {}) => {
if (typeof log === 'function') {
log(`步骤 5 [调试] ${message}`, logOptions?.level || 'info', {
step: 5,
stepKey: 'fill-profile',
});
}
};
function completeStep5Once(extra = {}) {
const completionReason = extra?.outcome?.state
|| (extra?.navigationStarted ? `navigation_started:${extra?.navigationEventType || 'unknown'}` : 'direct_completion');
if (reportedCompletionPayload) {
debugLog(`忽略重复完成信号(reason=${completionReason})。`, {
level: 'warn',
});
return reportedCompletionPayload;
}
@@ -7156,6 +7211,9 @@ async function step5_fillNameBirthday(payload) {
navigationStarted: Boolean(extra.navigationStarted),
outcome: extra.outcome || null,
});
debugLog(`准备发送完成信号(reason=${completionReason}isAgeMode=${isAgeMode})。`, {
level: extra?.navigationStarted ? 'warn' : 'info',
});
reportedCompletionPayload = completionPayload;
reportComplete(5, completionPayload);
return completionPayload;
+94 -3
View File
@@ -5,12 +5,14 @@
const SIGNUP_METHOD_PHONE = 'phone';
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
const PLUS_PAYMENT_METHOD_PAYPAL_HOSTED = 'paypal-hosted';
const PLUS_PAYMENT_METHOD_NONE = 'none';
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper';
const PLUS_ACCOUNT_ACCESS_STRATEGY_OAUTH = 'oauth';
const PLUS_ACCOUNT_ACCESS_STRATEGY_SUB2API_CODEX_SESSION = 'sub2api_codex_session';
const PLUS_ACCOUNT_ACCESS_STRATEGY_CPA_CODEX_SESSION = 'cpa_codex_session';
const PLUS_PAYMENT_STEP_KEY = 'paypal-approve';
const PLUS_REGISTRATION_WAIT_STEP_KEY = 'wait-registration-success';
function freezeDeep(entry) {
if (!entry || typeof entry !== 'object' || Object.isFrozen(entry)) {
@@ -3014,12 +3016,87 @@
]
});
const PLUS_PAYMENT_CHAIN_STEP_KEYS = Object.freeze([
'plus-checkout-create',
'plus-checkout-billing',
'paypal-approve',
'plus-checkout-return',
'paypal-hosted-email',
'paypal-hosted-card',
'paypal-hosted-create-account',
'paypal-hosted-review',
'gopay-subscription-confirm',
]);
function omitPlusPaymentChainSteps(steps = []) {
return steps.filter((step) => !PLUS_PAYMENT_CHAIN_STEP_KEYS.includes(String(step?.key || '').trim()));
}
function reindexModeStepDefinitions(steps = []) {
return (Array.isArray(steps) ? steps : []).map((step, index) => ({
...step,
id: index + 1,
order: (index + 1) * 10,
}));
}
function getPlusRegistrationWaitStep() {
const sourceStep = STEP_VARIANTS.normal.find((step) => step.key === PLUS_REGISTRATION_WAIT_STEP_KEY);
return {
...(sourceStep || {
key: PLUS_REGISTRATION_WAIT_STEP_KEY,
title: '等待注册成功',
sourceId: 'chatgpt',
driverId: null,
command: PLUS_REGISTRATION_WAIT_STEP_KEY,
flowId: 'openai',
}),
id: 6,
order: 60,
};
}
function shiftPlusStepAfterRegistrationWait(step = {}) {
const nextStep = { ...step };
const id = Number(step.id);
const order = Number(step.order);
if (Number.isFinite(id)) {
nextStep.id = id + 1;
}
if (Number.isFinite(order)) {
nextStep.order = order + 10;
}
return nextStep;
}
function insertPlusRegistrationWaitStep(steps = []) {
if (!Array.isArray(steps) || steps.some((step) => step.key === PLUS_REGISTRATION_WAIT_STEP_KEY)) {
return steps;
}
const fillProfileIndex = steps.findIndex((step) => step.key === 'fill-profile');
if (fillProfileIndex < 0) {
return steps;
}
return steps.flatMap((step, index) => {
if (index < fillProfileIndex) {
return [step];
}
if (index === fillProfileIndex) {
return [step, getPlusRegistrationWaitStep()];
}
return [shiftPlusStepAfterRegistrationWait(step)];
});
}
function isPlusModeEnabled(options = {}) {
return Boolean(options?.plusModeEnabled || options?.plusMode);
}
function normalizePlusPaymentMethod(value = '') {
const normalized = String(value || '').trim().toLowerCase();
if (normalized === PLUS_PAYMENT_METHOD_NONE || normalized === 'no-payment' || normalized === 'skip-payment') {
return PLUS_PAYMENT_METHOD_NONE;
}
if (normalized === PLUS_PAYMENT_METHOD_PAYPAL_HOSTED || normalized === 'paypal_direct' || normalized === 'paypal-direct') {
return PLUS_PAYMENT_METHOD_PAYPAL_HOSTED;
}
@@ -3118,13 +3195,27 @@
}
function getModeStepDefinitions(options = {}) {
return getVariantStepDefinitions(resolveVariantKey(options));
const isPlusMode = isPlusModeEnabled(options);
let steps = getVariantStepDefinitions(resolveVariantKey(options));
if (isPlusMode) {
steps = insertPlusRegistrationWaitStep(steps);
}
if (
isPlusMode
&& normalizePlusPaymentMethod(options?.plusPaymentMethod || options?.paymentMethod) === PLUS_PAYMENT_METHOD_NONE
) {
steps = omitPlusPaymentChainSteps(steps);
}
return reindexModeStepDefinitions(steps);
}
function getAllSteps() {
const keyed = new Map();
Object.values(STEP_VARIANTS).forEach((steps) => {
(Array.isArray(steps) ? steps : []).forEach((step) => {
Object.entries(STEP_VARIANTS).forEach(([variantKey, steps]) => {
const variantSteps = String(variantKey || '').startsWith('plus')
? insertPlusRegistrationWaitStep(steps)
: steps;
reindexModeStepDefinitions(variantSteps).forEach((step) => {
keyed.set(`${step.id}:${step.key}`, step);
});
});