Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions background/phone-verification-flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -1318,7 +1318,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|Content script .* did not respond|Try refreshing the tab and retry|等待认证页状态检查超时/i.test(message);
}

function buildPhoneRestartStep7Error(phoneNumber = '') {
Expand Down Expand Up @@ -3296,9 +3296,20 @@
}

async function cancelSignupPhoneActivation(state = {}, activation = null) {
const normalizedActivation = normalizeActivation(activation || state?.signupPhoneActivation);
let latestState = state;
try {
latestState = await getState();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的刷新不覆盖本 PR 回归用例实际走到的 completePhoneVerificationFlow 换号释放路径;该路径仍把 while 循环中缓存的 state 传给 provider adapter。请把“获取最新状态”收敛到共用的 cancel / rotate 边界,而不是只补两个旁路调用点。

} catch (_) {
latestState = state;
}
const normalizedActivation = normalizeActivation(activation)
|| normalizeActivation(latestState?.signupPhoneActivation)
|| normalizeActivation(state?.signupPhoneActivation);
if (normalizedActivation) {
await cancelPhoneActivation(state, normalizedActivation);
await cancelPhoneActivation({
...(state || {}),
...(latestState || {}),
}, normalizedActivation);
}
await clearSignupPhoneRuntimeState();
}
Expand Down Expand Up @@ -3670,7 +3681,16 @@
throw new Error(`步骤 ${visibleStep}:登录手机验证码未能成功提交。`);
} catch (error) {
if (shouldCancelActivation && activation) {
await cancelPhoneActivation(state, activation).catch(() => {});
let latestState = state;
try {
latestState = await getState();
} catch (_) {
latestState = state;
}
await cancelPhoneActivation({
...(state || {}),
...(latestState || {}),
}, activation).catch(() => {});
}
await setPhoneRuntimeState({
signupPhoneActivation: null,
Expand Down
115 changes: 115 additions & 0 deletions tests/phone-verification-flow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8161,6 +8161,121 @@ test('phone verification helper propagates stop errors instead of swallowing res
assert.equal(messages.includes('RETURN_TO_ADD_PHONE'), false);
});

test('phone verification helper releases HeroSMS order before recovering from content script no-response', async () => {
const requests = [];
const messages = [];
let navigateCalls = 0;
let acquireCount = 0;
let currentState = {
heroSmsApiKey: 'demo-key',
heroSmsCountryId: 52,
heroSmsCountryLabel: 'Thailand',
verificationResendCount: 0,
phoneVerificationReplacementLimit: 2,
phoneCodeWaitSeconds: 15,
phoneCodeTimeoutWindows: 1,
phoneCodePollIntervalSeconds: 15,
phoneCodePollMaxRounds: 1,
currentPhoneActivation: null,
reusablePhoneActivation: null,
};

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') {
acquireCount += 1;
return {
ok: true,
text: async () => (
acquireCount === 1
? 'ACCESS_NUMBER:hero-old:66950000001'
: 'ACCESS_NUMBER:hero-new:66950000002'
),
};
}
if (action === 'getStatus') {
return {
ok: true,
text: async () => (id === 'hero-new' ? 'STATUS_OK:246810' : 'STATUS_WAIT_CODE'),
};
}
if (action === 'setStatus') {
return { ok: true, text: async () => 'STATUS_UPDATED' };
}
throw new Error(`Unexpected HeroSMS action: ${action}`);
},
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
getState: async () => ({ ...currentState }),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

此夹具中的 currentState 在释放前没有发生配置变化,因此测试无法验证“使用最新状态释放订单”。请模拟等待短信期间 HeroSMS 配置更新,并断言旧订单的 setStatus 使用更新后的配置。

navigateAuthTabToAddPhone: async () => {
navigateCalls += 1;
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') {
return {
phoneVerificationPage: true,
url: 'https://auth.openai.com/phone-verification',
};
}
if (message.type === 'RETURN_TO_ADD_PHONE' || message.type === 'STEP8_GET_STATE') {
throw new Error('Content script on openai-auth did not respond in 30s. Try refreshing the tab and retry.');
}
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 () => {},
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.equal(navigateCalls, 1);
assert.deepStrictEqual(
requests
.filter((requestUrl) => requestUrl.searchParams.get('action') === 'setStatus')
.map((requestUrl) => `${requestUrl.searchParams.get('id')}:${requestUrl.searchParams.get('status')}`),
['hero-old:8', 'hero-new:6']
);
assert.deepStrictEqual(
messages.filter((type) => type === 'SUBMIT_PHONE_NUMBER'),
['SUBMIT_PHONE_NUMBER', 'SUBMIT_PHONE_NUMBER']
);
assert.equal(currentState.currentPhoneActivation, null);
});

test('phone verification helper falls back to the next country after repeated sms timeout on the same country', async () => {
const requests = [];
let currentState = {
Expand Down