fix: recover add-phone flow from auth error pages

This commit is contained in:
root
2026-05-09 03:55:45 -04:00
parent 6f67a00980
commit a8e77396e1
3 changed files with 382 additions and 22 deletions
+22
View File
@@ -10802,6 +10802,28 @@ const phoneVerificationHelpers = self.MultiPageBackgroundPhoneVerification?.crea
DEFAULT_PHONE_CODE_POLL_INTERVAL_SECONDS,
DEFAULT_PHONE_CODE_POLL_ROUNDS,
ensureStep8SignupPageReady,
navigateAuthTabToAddPhone: async (tabId, options = {}) => {
const visibleStep = Math.floor(Number(options.visibleStep || options.step) || 0) || 9;
const requestedTimeoutMs = Number(options.timeoutMs);
const timeoutMs = Number.isFinite(requestedTimeoutMs) && requestedTimeoutMs > 0
? requestedTimeoutMs
: await getOAuthFlowStepTimeoutMs(30000, {
step: visibleStep,
actionLabel: 'direct add-phone navigation',
});
await chrome.tabs.update(tabId, { url: 'https://auth.openai.com/add-phone', active: true });
await ensureStep8SignupPageReady(tabId, {
timeoutMs,
visibleStep,
logStepKey: options.logStepKey || 'phone-verification',
logMessage: options.logMessage || '步骤 9:认证页已失联,直接打开添加手机号页面后等待脚本恢复。',
});
return {
addPhonePage: true,
phoneVerificationPage: false,
url: 'https://auth.openai.com/add-phone',
};
},
generateRandomBirthday,
generateRandomName,
getOAuthFlowRemainingMs,
+72 -22
View File
@@ -13,6 +13,7 @@
requestStop = null,
sendToContentScript,
sendToContentScriptResilient,
navigateAuthTabToAddPhone = null,
setState,
broadcastDataUpdate = null,
sleepWithStop,
@@ -1381,6 +1382,11 @@
|| /flow\s+was\s+stopped|stopped\s+by\s+user/i.test(message);
}
function isAuthContentScriptUnreachableError(error) {
const message = String(error?.message || error || '').trim();
return /Receiving end does not exist|Could not establish connection|Frame with ID \d+ is showing error page/i.test(message);
}
function buildPhoneRestartStep7Error(phoneNumber = '') {
const suffix = phoneNumber ? ` 当前号码:${phoneNumber}` : '';
return new Error(
@@ -5627,11 +5633,37 @@
return matched?.label || `Country #${normalizedCountryId}`;
};
const ensureAddPhonePageBeforeSubmit = async (attemptLabel = 'before submit') => {
const directNavigateToAddPhone = async (attemptLabel = 'after replace-number rotation') => {
if (typeof navigateAuthTabToAddPhone !== 'function') {
return null;
}
const visibleStep = normalizeLogStep(activePhoneVerificationLogStep) || 9;
const result = await navigateAuthTabToAddPhone(tabId, {
visibleStep,
timeoutMs: 30000,
logMessage: '步骤 9:认证页已失联,直接打开添加手机号页面后等待脚本恢复。',
logStepKey: 'phone-verification',
attemptLabel,
});
if (result?.error) {
throw new Error(result.error);
}
return {
addPhonePage: true,
phoneVerificationPage: false,
url: 'https://auth.openai.com/add-phone',
...(result || {}),
};
};
const ensureAddPhonePageBeforeSubmit = async (attemptLabel = 'before submit', options = {}) => {
const allowDirectNavigation = Boolean(options.allowDirectNavigation);
let snapshot = null;
let snapshotError = null;
try {
snapshot = await readPhonePageState(tabId, 12000);
} catch (error) {
snapshotError = error;
await addLog(
`Step 9: failed to inspect auth page ${attemptLabel}. ${error.message}`,
'warn'
@@ -5643,6 +5675,7 @@
return snapshot;
}
let returnError = null;
try {
const returned = await returnToAddPhone(tabId);
const merged = {
@@ -5653,13 +5686,38 @@
return merged;
}
} catch (error) {
returnError = error;
await addLog(
`Step 9: failed to return to add-phone page ${attemptLabel}. ${error.message}`,
'warn'
);
}
const latest = await readPhonePageState(tabId, 15000);
if (
allowDirectNavigation
&& (
isAuthContentScriptUnreachableError(snapshotError)
|| isAuthContentScriptUnreachableError(returnError)
)
) {
const navigated = await directNavigateToAddPhone(attemptLabel);
if (navigated) {
return navigated;
}
}
let latest = null;
try {
latest = await readPhonePageState(tabId, 15000);
} catch (error) {
if (allowDirectNavigation && isAuthContentScriptUnreachableError(error)) {
const navigated = await directNavigateToAddPhone(attemptLabel);
if (navigated) {
return navigated;
}
}
throw error;
}
if (!latest?.addPhonePage) {
throw new Error(
`Step 9: auth page is not on add-phone before phone submit (${attemptLabel}). URL: ${latest?.url || 'unknown'}`
@@ -6205,11 +6263,7 @@
shouldCancelActivation = false;
addPhoneReentryWithSameActivation = 0;
let returnResult = {
addPhonePage: true,
phoneVerificationPage: false,
url: 'https://auth.openai.com/add-phone',
};
let returnResult = null;
try {
returnResult = await returnToAddPhone(tabId);
} catch (returnError) {
@@ -6221,7 +6275,7 @@
const stateSnapshot = await readPhonePageState(tabId, 12000);
if (stateSnapshot?.addPhonePage) {
returnResult = {
...returnResult,
...(returnResult || {}),
...stateSnapshot,
addPhonePage: true,
phoneVerificationPage: false,
@@ -6231,20 +6285,16 @@
// Best effort: keep fallback state for compatibility with tests and older flows.
}
}
try {
const verifiedAddPhoneState = await ensureAddPhonePageBeforeSubmit('after replace-number rotation');
returnResult = {
...returnResult,
...verifiedAddPhoneState,
addPhonePage: true,
phoneVerificationPage: false,
};
} catch (verifyError) {
await addLog(
`Step 9: failed to verify add-phone page after number replacement. ${verifyError.message}`,
'warn'
);
}
const verifiedAddPhoneState = await ensureAddPhonePageBeforeSubmit(
'after replace-number rotation',
{ allowDirectNavigation: true }
);
returnResult = {
...(returnResult || {}),
...verifiedAddPhoneState,
addPhonePage: true,
phoneVerificationPage: false,
};
await addLog(
`步骤 9:正在更换号码并在步骤 9 内重试(${usedNumberReplacementAttempts}/${maxNumberReplacementAttempts})。`,
+288
View File
@@ -2435,6 +2435,12 @@ test('phone verification helper replaces numbers in step 9 and stops after repla
url: 'https://auth.openai.com/phone-verification',
};
}
if (message.type === 'RETURN_TO_ADD_PHONE') {
return {
addPhonePage: true,
url: 'https://auth.openai.com/add-phone',
};
}
throw new Error(`Unexpected content-script message: ${message.type}`);
},
setState: async (updates) => {
@@ -4677,6 +4683,288 @@ test('phone verification helper replaces number immediately when phone-verificat
assert.equal(messages.includes('RETURN_TO_ADD_PHONE'), true);
});
test('phone verification helper directly navigates back to add-phone when replace-number recovery loses the auth page', async () => {
const requests = [];
const messages = [];
const navigationCalls = [];
const submittedNumbers = [];
const statusCallsById = {};
let addPhoneRouteReady = false;
let currentState = {
heroSmsApiKey: 'demo-key',
heroSmsCountryId: 52,
heroSmsCountryLabel: 'Thailand',
verificationResendCount: 0,
phoneVerificationReplacementLimit: 2,
phoneCodeWaitSeconds: 60,
phoneCodeTimeoutWindows: 2,
phoneCodePollIntervalSeconds: 1,
phoneCodePollMaxRounds: 1,
currentPhoneActivation: null,
reusablePhoneActivation: null,
};
const numbers = [
{ activationId: '930001', phoneNumber: '66953330001' },
{ activationId: '930002', phoneNumber: '66953330002' },
];
let numberIndex = 0;
const realDateNow = Date.now;
let fakeNow = 0;
Date.now = () => fakeNow;
try {
const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {},
ensureStep8SignupPageReady: async () => {},
fetchImpl: async (url) => {
const parsedUrl = new URL(url);
requests.push(parsedUrl);
const action = parsedUrl.searchParams.get('action');
const id = parsedUrl.searchParams.get('id');
if (action === 'getPrices') {
return {
ok: true,
text: async () => buildHeroSmsPricesPayload(),
};
}
if (action === 'getNumber') {
const nextNumber = numbers[numberIndex];
numberIndex += 1;
return {
ok: true,
text: async () => `ACCESS_NUMBER:${nextNumber.activationId}:${nextNumber.phoneNumber}`,
};
}
if (action === 'getStatus') {
statusCallsById[id] = (statusCallsById[id] || 0) + 1;
return {
ok: true,
text: async () => (id === '930001' ? 'STATUS_WAIT_CODE' : 'STATUS_OK:654321'),
};
}
if (action === 'setStatus') {
return {
ok: true,
text: async () => `STATUS_UPDATED:${id}`,
};
}
throw new Error(`Unexpected HeroSMS action: ${action}`);
},
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
getState: async () => ({ ...currentState }),
navigateAuthTabToAddPhone: async (tabId) => {
navigationCalls.push(tabId);
addPhoneRouteReady = true;
return {
addPhonePage: true,
phoneVerificationPage: false,
url: 'https://auth.openai.com/add-phone',
};
},
sendToContentScriptResilient: async (_source, message) => {
messages.push(message.type);
if (message.type === 'SUBMIT_PHONE_NUMBER') {
submittedNumbers.push(message.payload.phoneNumber);
if (submittedNumbers.length === 1) {
return {
phoneVerificationPage: true,
url: 'https://auth.openai.com/phone-verification',
};
}
if (!addPhoneRouteReady) {
throw new Error('Could not establish connection. Receiving end does not exist.');
}
return {
phoneVerificationPage: true,
url: 'https://auth.openai.com/phone-verification',
};
}
if (message.type === 'RESEND_PHONE_VERIFICATION_CODE') {
addPhoneRouteReady = false;
throw new Error('Could not establish connection. Receiving end does not exist.');
}
if (message.type === 'RETURN_TO_ADD_PHONE') {
addPhoneRouteReady = false;
throw new Error('Could not establish connection. Receiving end does not exist.');
}
if (message.type === 'STEP8_GET_STATE') {
if (!addPhoneRouteReady) {
throw new Error('Frame with ID 0 is showing error page.');
}
return {
addPhonePage: true,
phoneVerificationPage: false,
url: 'https://auth.openai.com/add-phone',
};
}
if (message.type === 'SUBMIT_PHONE_VERIFICATION_CODE') {
return {
success: true,
consentReady: true,
url: 'https://auth.openai.com/authorize',
};
}
throw new Error(`Unexpected content-script message: ${message.type}`);
},
setState: async (updates) => {
currentState = { ...currentState, ...updates };
},
sleepWithStop: async () => {
fakeNow += 61000;
},
throwIfStopped: () => {},
});
const result = await helpers.completePhoneVerificationFlow(1, {
addPhonePage: true,
phoneVerificationPage: false,
url: 'https://auth.openai.com/add-phone',
});
assert.deepStrictEqual(result, {
success: true,
consentReady: true,
url: 'https://auth.openai.com/authorize',
});
assert.deepStrictEqual(submittedNumbers, ['66953330001', '66953330002']);
assert.deepStrictEqual(navigationCalls, [1]);
assert.ok(statusCallsById['930001'] >= 2, 'first number should be polled across both timeout windows');
assert.equal(messages.includes('RETURN_TO_ADD_PHONE'), true);
} finally {
Date.now = realDateNow;
}
});
test('phone verification helper stops when add-phone recovery cannot be verified after number replacement', async () => {
const messages = [];
const submittedNumbers = [];
let currentState = {
heroSmsApiKey: 'demo-key',
heroSmsCountryId: 52,
heroSmsCountryLabel: 'Thailand',
verificationResendCount: 0,
phoneVerificationReplacementLimit: 2,
phoneCodeWaitSeconds: 60,
phoneCodeTimeoutWindows: 2,
phoneCodePollIntervalSeconds: 1,
phoneCodePollMaxRounds: 1,
currentPhoneActivation: null,
reusablePhoneActivation: null,
};
const numbers = [
{ activationId: '940001', phoneNumber: '66954440001' },
{ activationId: '940002', phoneNumber: '66954440002' },
];
let numberIndex = 0;
const realDateNow = Date.now;
let fakeNow = 0;
Date.now = () => fakeNow;
try {
const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {},
ensureStep8SignupPageReady: async () => {},
fetchImpl: async (url) => {
const parsedUrl = new URL(url);
const action = parsedUrl.searchParams.get('action');
const id = parsedUrl.searchParams.get('id');
if (action === 'getPrices') {
return {
ok: true,
text: async () => buildHeroSmsPricesPayload(),
};
}
if (action === 'getNumber') {
const nextNumber = numbers[numberIndex];
numberIndex += 1;
return {
ok: true,
text: async () => `ACCESS_NUMBER:${nextNumber.activationId}:${nextNumber.phoneNumber}`,
};
}
if (action === 'getStatus') {
return {
ok: true,
text: async () => (id === '940001' ? 'STATUS_WAIT_CODE' : 'STATUS_OK:654321'),
};
}
if (action === 'setStatus') {
return {
ok: true,
text: async () => `STATUS_UPDATED:${id}`,
};
}
throw new Error(`Unexpected HeroSMS action: ${action}`);
},
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
getState: async () => ({ ...currentState }),
navigateAuthTabToAddPhone: async () => {
throw new Error('direct navigation failed');
},
sendToContentScriptResilient: async (_source, message) => {
messages.push(message.type);
if (message.type === 'SUBMIT_PHONE_NUMBER') {
submittedNumbers.push(message.payload.phoneNumber);
if (submittedNumbers.length === 1) {
return {
phoneVerificationPage: true,
url: 'https://auth.openai.com/phone-verification',
};
}
throw new Error('Could not establish connection. Receiving end does not exist.');
}
if (message.type === 'RESEND_PHONE_VERIFICATION_CODE') {
throw new Error('Could not establish connection. Receiving end does not exist.');
}
if (message.type === 'RETURN_TO_ADD_PHONE') {
throw new Error('Could not establish connection. Receiving end does not exist.');
}
if (message.type === 'STEP8_GET_STATE') {
throw new Error('Frame with ID 0 is showing error page.');
}
if (message.type === 'SUBMIT_PHONE_VERIFICATION_CODE') {
return {
success: true,
consentReady: true,
url: 'https://auth.openai.com/authorize',
};
}
throw new Error(`Unexpected content-script message: ${message.type}`);
},
setState: async (updates) => {
currentState = { ...currentState, ...updates };
},
sleepWithStop: async () => {
fakeNow += 61000;
},
throwIfStopped: () => {},
});
await assert.rejects(
helpers.completePhoneVerificationFlow(1, {
addPhonePage: true,
phoneVerificationPage: false,
url: 'https://auth.openai.com/add-phone',
}),
/direct navigation failed/
);
assert.deepStrictEqual(submittedNumbers, ['66954440001']);
assert.equal(messages.includes('RETURN_TO_ADD_PHONE'), true);
} finally {
Date.now = realDateNow;
}
});
test('phone verification helper skips page resend for 5sim timeouts and rotates number directly', async () => {
const requests = [];
const messages = [];