fix: recover add-phone flow from auth error pages
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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})。`,
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
Reference in New Issue
Block a user