fix: prevent hung phone page-state probes from stalling SMS polling
This commit is contained in:
@@ -1459,7 +1459,7 @@
|
||||
|
||||
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);
|
||||
return /Receiving end does not exist|Could not establish connection|Frame with ID \d+ is showing error page|等待认证页状态检查超时/i.test(message);
|
||||
}
|
||||
|
||||
function buildPhoneRestartStep7Error(phoneNumber = '') {
|
||||
@@ -4011,29 +4011,46 @@
|
||||
|
||||
async function readPhonePageState(tabId, timeoutMs = 10000) {
|
||||
const visibleStep = normalizeLogStep(activePhoneVerificationLogStep) || 9;
|
||||
await ensureStep8SignupPageReady(tabId, {
|
||||
timeoutMs,
|
||||
logMessage: '步骤 9:等待认证页脚本恢复后继续手机号验证。',
|
||||
visibleStep,
|
||||
logStepKey: 'phone-verification',
|
||||
});
|
||||
const result = await sendToContentScriptResilient('signup-page', {
|
||||
type: 'STEP8_GET_STATE',
|
||||
source: 'background',
|
||||
payload: { visibleStep },
|
||||
}, {
|
||||
timeoutMs,
|
||||
responseTimeoutMs: timeoutMs,
|
||||
retryDelayMs: 600,
|
||||
logMessage: '步骤 9:认证页正在切换,等待后重新检查手机号验证状态...',
|
||||
logStep: visibleStep,
|
||||
logStepKey: 'phone-verification',
|
||||
const deadlineMs = Math.max(1, Math.floor(Number(timeoutMs) || 0));
|
||||
let timeoutId = null;
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
timeoutId = setTimeout(() => {
|
||||
reject(new Error(`步骤 ${visibleStep}:等待认证页状态检查超时。`));
|
||||
}, deadlineMs);
|
||||
});
|
||||
const readPromise = (async () => {
|
||||
await ensureStep8SignupPageReady(tabId, {
|
||||
timeoutMs: deadlineMs,
|
||||
logMessage: '步骤 9:等待认证页脚本恢复后继续手机号验证。',
|
||||
visibleStep,
|
||||
logStepKey: 'phone-verification',
|
||||
});
|
||||
const result = await sendToContentScriptResilient('signup-page', {
|
||||
type: 'STEP8_GET_STATE',
|
||||
source: 'background',
|
||||
payload: { visibleStep },
|
||||
}, {
|
||||
timeoutMs: deadlineMs,
|
||||
responseTimeoutMs: deadlineMs,
|
||||
retryDelayMs: 600,
|
||||
logMessage: '步骤 9:认证页正在切换,等待后重新检查手机号验证状态...',
|
||||
logStep: visibleStep,
|
||||
logStepKey: 'phone-verification',
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
throw new Error(result.error);
|
||||
if (result?.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
return result || {};
|
||||
})();
|
||||
|
||||
try {
|
||||
return await Promise.race([readPromise, timeoutPromise]);
|
||||
} finally {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
return result || {};
|
||||
}
|
||||
|
||||
function resolveCountryCandidatesForProvider(state = {}, providerId = normalizePhoneSmsProvider(state?.phoneSmsProvider)) {
|
||||
@@ -5388,13 +5405,14 @@
|
||||
return withPhoneVerificationLogContext({ step: 4, stepKey: 'fetch-signup-code' }, async () => {
|
||||
let state = options?.state || await getState();
|
||||
const activation = normalizeActivation(options?.activation || state?.signupPhoneActivation);
|
||||
const pageStateCheckTimeoutMs = Math.max(1, Math.floor(Number(options?.pageStateCheckTimeoutMs) || 5000));
|
||||
if (!activation) {
|
||||
throw new Error('步骤 4:未找到当前注册手机号激活记录,请重新执行步骤 2。');
|
||||
}
|
||||
|
||||
const assertSignupPhoneStillApplicable = async (phaseLabel) => {
|
||||
try {
|
||||
const pageState = await readPhonePageState(tabId, 5000);
|
||||
const pageState = await readPhonePageState(tabId, pageStateCheckTimeoutMs);
|
||||
if (isSignupEmailVerificationPageState(pageState)) {
|
||||
throw buildSignupPhoneStaleEmailVerificationError(pageState);
|
||||
}
|
||||
|
||||
@@ -568,6 +568,105 @@ test('signup phone helper fails stale email-verification that appears during SMS
|
||||
assert.deepStrictEqual(contentMessages.map((message) => message.type), ['STEP8_GET_STATE', 'STEP8_GET_STATE']);
|
||||
});
|
||||
|
||||
test('signup phone helper does not let a hung page-state probe stall HeroSMS polling', async () => {
|
||||
let smsPollCount = 0;
|
||||
let pageReadyCalls = 0;
|
||||
const statusActions = [];
|
||||
const contentMessages = [];
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsReuseEnabled: false,
|
||||
phoneCodeWaitSeconds: 15,
|
||||
phoneCodeTimeoutWindows: 1,
|
||||
phoneCodePollIntervalSeconds: 1,
|
||||
phoneCodePollMaxRounds: 1,
|
||||
signupPhoneNumber: '66959916439',
|
||||
signupPhoneVerificationPurpose: 'signup',
|
||||
signupPhoneActivation: {
|
||||
activationId: 'signup-123',
|
||||
phoneNumber: '66959916439',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
},
|
||||
};
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {
|
||||
pageReadyCalls += 1;
|
||||
if (pageReadyCalls >= 2) {
|
||||
return new Promise(() => {});
|
||||
}
|
||||
},
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
const action = parsedUrl.searchParams.get('action');
|
||||
if (action === 'getStatus') {
|
||||
smsPollCount += 1;
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => 'STATUS_WAIT_CODE',
|
||||
};
|
||||
}
|
||||
if (action === 'setStatus') {
|
||||
statusActions.push(parsedUrl.searchParams.get('status'));
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => 'ACCESS_READY',
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (fallback) => fallback,
|
||||
getState: async () => currentState,
|
||||
sendToContentScriptResilient: async (_source, message) => {
|
||||
contentMessages.push(message);
|
||||
if (message.type === 'STEP8_GET_STATE') {
|
||||
return {
|
||||
emailVerificationPage: false,
|
||||
phoneVerificationPage: true,
|
||||
url: 'https://auth.openai.com/phone-verification',
|
||||
};
|
||||
}
|
||||
if (message.type === 'SUBMIT_PHONE_VERIFICATION_CODE') {
|
||||
throw new Error('SMS timeout should fail before submitting a code');
|
||||
}
|
||||
return {};
|
||||
},
|
||||
setState: async (updates) => {
|
||||
currentState = { ...currentState, ...updates };
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
let caughtError = null;
|
||||
try {
|
||||
await Promise.race([
|
||||
helpers.completeSignupPhoneVerificationFlow(77, {
|
||||
state: currentState,
|
||||
pageStateCheckTimeoutMs: 1,
|
||||
}),
|
||||
new Promise((_, reject) => setTimeout(
|
||||
() => reject(new Error('hung waiting for signup phone page-state probe')),
|
||||
50
|
||||
)),
|
||||
]);
|
||||
} catch (error) {
|
||||
caughtError = error;
|
||||
}
|
||||
|
||||
assert.equal(smsPollCount, 1, 'HeroSMS polling should continue after the first waiting status');
|
||||
assert.equal(pageReadyCalls, 2, 'should attempt the page-state probe during SMS polling');
|
||||
assert.deepStrictEqual(contentMessages.map((message) => message.type), ['STEP8_GET_STATE']);
|
||||
assert.deepStrictEqual(statusActions, ['8']);
|
||||
assert.ok(caughtError, 'expected SMS timeout rather than a stalled page-state probe');
|
||||
assert.doesNotMatch(caughtError.message, /hung waiting for signup phone page-state probe/);
|
||||
assert.match(caughtError.message, /等待手机验证码超时/);
|
||||
});
|
||||
|
||||
test('signup phone helper fails stale email-verification on 5sim RECEIVED without code during SMS polling', async () => {
|
||||
const fiveSimSource = fs.readFileSync('phone-sms/providers/five-sim.js', 'utf8');
|
||||
const fiveSimModule = new Function('self', `${fiveSimSource}; return self.PhoneSmsFiveSimProvider;`)({});
|
||||
@@ -5432,7 +5531,7 @@ 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 () => {
|
||||
test('phone verification helper directly navigates back to add-phone when replace-number recovery page-state probe hangs', async () => {
|
||||
const requests = [];
|
||||
const messages = [];
|
||||
const navigationCalls = [];
|
||||
@@ -5458,13 +5557,24 @@ test('phone verification helper directly navigates back to add-phone when replac
|
||||
];
|
||||
let numberIndex = 0;
|
||||
const realDateNow = Date.now;
|
||||
const realSetTimeout = global.setTimeout;
|
||||
let fakeNow = 0;
|
||||
Date.now = () => fakeNow;
|
||||
global.setTimeout = (callback, ms, ...args) => realSetTimeout(
|
||||
callback,
|
||||
Number(ms) >= 12000 ? 1 : ms,
|
||||
...args
|
||||
);
|
||||
|
||||
try {
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {
|
||||
if (!addPhoneRouteReady) {
|
||||
return new Promise(() => {});
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
@@ -5539,7 +5649,7 @@ test('phone verification helper directly navigates back to add-phone when replac
|
||||
}
|
||||
if (message.type === 'RETURN_TO_ADD_PHONE') {
|
||||
addPhoneRouteReady = false;
|
||||
throw new Error('Could not establish connection. Receiving end does not exist.');
|
||||
return {};
|
||||
}
|
||||
if (message.type === 'STEP8_GET_STATE') {
|
||||
if (!addPhoneRouteReady) {
|
||||
@@ -5586,6 +5696,7 @@ test('phone verification helper directly navigates back to add-phone when replac
|
||||
assert.equal(messages.includes('RETURN_TO_ADD_PHONE'), true);
|
||||
} finally {
|
||||
Date.now = realDateNow;
|
||||
global.setTimeout = realSetTimeout;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user