fix: prevent hung phone page-state probes from stalling SMS polling
This commit is contained in:
@@ -1459,7 +1459,7 @@
|
|||||||
|
|
||||||
function isAuthContentScriptUnreachableError(error) {
|
function isAuthContentScriptUnreachableError(error) {
|
||||||
const message = String(error?.message || error || '').trim();
|
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 = '') {
|
function buildPhoneRestartStep7Error(phoneNumber = '') {
|
||||||
@@ -4011,29 +4011,46 @@
|
|||||||
|
|
||||||
async function readPhonePageState(tabId, timeoutMs = 10000) {
|
async function readPhonePageState(tabId, timeoutMs = 10000) {
|
||||||
const visibleStep = normalizeLogStep(activePhoneVerificationLogStep) || 9;
|
const visibleStep = normalizeLogStep(activePhoneVerificationLogStep) || 9;
|
||||||
await ensureStep8SignupPageReady(tabId, {
|
const deadlineMs = Math.max(1, Math.floor(Number(timeoutMs) || 0));
|
||||||
timeoutMs,
|
let timeoutId = null;
|
||||||
logMessage: '步骤 9:等待认证页脚本恢复后继续手机号验证。',
|
const timeoutPromise = new Promise((_, reject) => {
|
||||||
visibleStep,
|
timeoutId = setTimeout(() => {
|
||||||
logStepKey: 'phone-verification',
|
reject(new Error(`步骤 ${visibleStep}:等待认证页状态检查超时。`));
|
||||||
});
|
}, deadlineMs);
|
||||||
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 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) {
|
if (result?.error) {
|
||||||
throw new Error(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)) {
|
function resolveCountryCandidatesForProvider(state = {}, providerId = normalizePhoneSmsProvider(state?.phoneSmsProvider)) {
|
||||||
@@ -5388,13 +5405,14 @@
|
|||||||
return withPhoneVerificationLogContext({ step: 4, stepKey: 'fetch-signup-code' }, async () => {
|
return withPhoneVerificationLogContext({ step: 4, stepKey: 'fetch-signup-code' }, async () => {
|
||||||
let state = options?.state || await getState();
|
let state = options?.state || await getState();
|
||||||
const activation = normalizeActivation(options?.activation || state?.signupPhoneActivation);
|
const activation = normalizeActivation(options?.activation || state?.signupPhoneActivation);
|
||||||
|
const pageStateCheckTimeoutMs = Math.max(1, Math.floor(Number(options?.pageStateCheckTimeoutMs) || 5000));
|
||||||
if (!activation) {
|
if (!activation) {
|
||||||
throw new Error('步骤 4:未找到当前注册手机号激活记录,请重新执行步骤 2。');
|
throw new Error('步骤 4:未找到当前注册手机号激活记录,请重新执行步骤 2。');
|
||||||
}
|
}
|
||||||
|
|
||||||
const assertSignupPhoneStillApplicable = async (phaseLabel) => {
|
const assertSignupPhoneStillApplicable = async (phaseLabel) => {
|
||||||
try {
|
try {
|
||||||
const pageState = await readPhonePageState(tabId, 5000);
|
const pageState = await readPhonePageState(tabId, pageStateCheckTimeoutMs);
|
||||||
if (isSignupEmailVerificationPageState(pageState)) {
|
if (isSignupEmailVerificationPageState(pageState)) {
|
||||||
throw buildSignupPhoneStaleEmailVerificationError(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']);
|
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 () => {
|
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 fiveSimSource = fs.readFileSync('phone-sms/providers/five-sim.js', 'utf8');
|
||||||
const fiveSimModule = new Function('self', `${fiveSimSource}; return self.PhoneSmsFiveSimProvider;`)({});
|
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);
|
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 requests = [];
|
||||||
const messages = [];
|
const messages = [];
|
||||||
const navigationCalls = [];
|
const navigationCalls = [];
|
||||||
@@ -5458,13 +5557,24 @@ test('phone verification helper directly navigates back to add-phone when replac
|
|||||||
];
|
];
|
||||||
let numberIndex = 0;
|
let numberIndex = 0;
|
||||||
const realDateNow = Date.now;
|
const realDateNow = Date.now;
|
||||||
|
const realSetTimeout = global.setTimeout;
|
||||||
let fakeNow = 0;
|
let fakeNow = 0;
|
||||||
Date.now = () => fakeNow;
|
Date.now = () => fakeNow;
|
||||||
|
global.setTimeout = (callback, ms, ...args) => realSetTimeout(
|
||||||
|
callback,
|
||||||
|
Number(ms) >= 12000 ? 1 : ms,
|
||||||
|
...args
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const helpers = api.createPhoneVerificationHelpers({
|
const helpers = api.createPhoneVerificationHelpers({
|
||||||
addLog: async () => {},
|
addLog: async () => {},
|
||||||
ensureStep8SignupPageReady: async () => {},
|
ensureStep8SignupPageReady: async () => {
|
||||||
|
if (!addPhoneRouteReady) {
|
||||||
|
return new Promise(() => {});
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
fetchImpl: async (url) => {
|
fetchImpl: async (url) => {
|
||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
requests.push(parsedUrl);
|
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') {
|
if (message.type === 'RETURN_TO_ADD_PHONE') {
|
||||||
addPhoneRouteReady = false;
|
addPhoneRouteReady = false;
|
||||||
throw new Error('Could not establish connection. Receiving end does not exist.');
|
return {};
|
||||||
}
|
}
|
||||||
if (message.type === 'STEP8_GET_STATE') {
|
if (message.type === 'STEP8_GET_STATE') {
|
||||||
if (!addPhoneRouteReady) {
|
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);
|
assert.equal(messages.includes('RETURN_TO_ADD_PHONE'), true);
|
||||||
} finally {
|
} finally {
|
||||||
Date.now = realDateNow;
|
Date.now = realDateNow;
|
||||||
|
global.setTimeout = realSetTimeout;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user