fix: rotate unusable phone verification numbers
This commit is contained in:
@@ -406,6 +406,31 @@
|
||||
return /phone\s+number\s+is\s+not\s+valid|invalid\s+phone\s+number|invalid\s+phone|not\s+a\s+valid\s+phone|号码.*无效|手机号.*无效|电话号码.*无效/i.test(text);
|
||||
}
|
||||
|
||||
function isPhoneNumberDeliveryRefusedError(value) {
|
||||
const text = String(value || '').trim();
|
||||
if (!text) {
|
||||
return false;
|
||||
}
|
||||
return /无法向此电话号码发送验证码|无法向.*(?:电话号码|手机号|号码).*发送(?:验证码|短信)|(?:不能|无法).*发送.*(?:验证码|短信).*(?:电话号码|手机号|号码)|(?:cannot|can't|could\s*not|couldn't|unable\s+to)\s+(?:send|deliver).{0,80}(?:verification\s+code|code|sms|text(?:\s+message)?).{0,80}(?:phone|number)|(?:verification\s+code|sms|text(?:\s+message)?).{0,80}(?:cannot|can't|could\s*not|couldn't|unable\s+to).{0,80}(?:send|deliver)/i.test(text);
|
||||
}
|
||||
|
||||
function isWhatsAppPhoneResendResult(value) {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
const text = typeof value === 'string'
|
||||
? value
|
||||
: [
|
||||
value.channel,
|
||||
value.channelText,
|
||||
value.text,
|
||||
value.buttonText,
|
||||
value.label,
|
||||
value.message,
|
||||
].filter(Boolean).join(' ');
|
||||
return /whats\s*app/i.test(String(text || ''));
|
||||
}
|
||||
|
||||
function isRecoverableAddPhoneSubmitError(value) {
|
||||
const text = String(value || '').trim();
|
||||
if (!text) {
|
||||
@@ -4080,7 +4105,7 @@
|
||||
return result || {};
|
||||
}
|
||||
|
||||
async function resendPhoneVerificationCode(tabId) {
|
||||
async function resendPhoneVerificationCode(tabId, options = {}) {
|
||||
const visibleStep = normalizeLogStep(activePhoneVerificationLogStep) || 9;
|
||||
const timeoutMs = typeof getOAuthFlowStepTimeoutMs === 'function'
|
||||
? await getOAuthFlowStepTimeoutMs(65000, { step: visibleStep, actionLabel: 'resend phone verification code' })
|
||||
@@ -4088,7 +4113,7 @@
|
||||
const result = await sendToContentScriptResilient('signup-page', {
|
||||
type: 'RESEND_PHONE_VERIFICATION_CODE',
|
||||
source: 'background',
|
||||
payload: {},
|
||||
payload: options || {},
|
||||
}, {
|
||||
timeoutMs,
|
||||
responseTimeoutMs: timeoutMs,
|
||||
@@ -5001,7 +5026,6 @@
|
||||
);
|
||||
continue;
|
||||
}
|
||||
await requestAdditionalPhoneSms(state, normalizedActivation);
|
||||
if (resendTriggeredForCurrentNumber) {
|
||||
await addLog(
|
||||
`步骤 9:号码 ${normalizedActivation.phoneNumber} 已触发过一次页面重发;为避免限流,将继续轮询不再点击重发。`,
|
||||
@@ -5010,7 +5034,35 @@
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
await resendPhoneVerificationCode(tabId);
|
||||
const resendProbeResult = await resendPhoneVerificationCode(tabId, { probeOnly: true });
|
||||
if (isWhatsAppPhoneResendResult(resendProbeResult)) {
|
||||
await addLog(
|
||||
`步骤 9:页面重发入口显示 WhatsApp 通道(${resendProbeResult.channelText || resendProbeResult.text || 'WhatsApp'}),当前接码平台无法读取 WhatsApp 消息,立即更换号码。`,
|
||||
'warn'
|
||||
);
|
||||
await clearPhoneRuntimeCountdown();
|
||||
return {
|
||||
code: '',
|
||||
replaceNumber: true,
|
||||
reason: 'whatsapp_resend_channel',
|
||||
};
|
||||
}
|
||||
await requestAdditionalPhoneSms(state, normalizedActivation);
|
||||
if (resendProbeResult?.probed) {
|
||||
const resendResult = await resendPhoneVerificationCode(tabId);
|
||||
if (isWhatsAppPhoneResendResult(resendResult)) {
|
||||
await addLog(
|
||||
`步骤 9:页面重发入口切换为 WhatsApp 通道(${resendResult.channelText || resendResult.text || 'WhatsApp'}),当前接码平台无法读取 WhatsApp 消息,立即更换号码。`,
|
||||
'warn'
|
||||
);
|
||||
await clearPhoneRuntimeCountdown();
|
||||
return {
|
||||
code: '',
|
||||
replaceNumber: true,
|
||||
reason: 'whatsapp_resend_channel',
|
||||
};
|
||||
}
|
||||
}
|
||||
resendTriggeredForCurrentNumber = true;
|
||||
await addLog('步骤 9:已点击手机验证码页面的“重新发送短信”。', 'info');
|
||||
} catch (resendError) {
|
||||
@@ -5982,10 +6034,10 @@
|
||||
submitResult = await submitPhoneNumber(tabId, activation.phoneNumber, activation);
|
||||
} catch (submitError) {
|
||||
const submitErrorText = String(submitError?.message || submitError || 'unknown error');
|
||||
if (isRecoverableAddPhoneSubmitError(submitErrorText)) {
|
||||
if (isPhoneNumberDeliveryRefusedError(submitErrorText) || isRecoverableAddPhoneSubmitError(submitErrorText)) {
|
||||
await rotateActivationAfterAddPhoneFailure(
|
||||
submitErrorText,
|
||||
'add_phone_submit_failed',
|
||||
isPhoneNumberDeliveryRefusedError(submitErrorText) ? 'phone_delivery_refused' : 'add_phone_submit_failed',
|
||||
{ url: pageState?.url || '' }
|
||||
);
|
||||
continue;
|
||||
@@ -6032,6 +6084,14 @@
|
||||
};
|
||||
continue;
|
||||
}
|
||||
if (isPhoneNumberDeliveryRefusedError(addPhoneRejectText)) {
|
||||
await rotateActivationAfterAddPhoneFailure(
|
||||
addPhoneRejectText,
|
||||
'phone_delivery_refused',
|
||||
submitResult || {}
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
await addLog(
|
||||
`步骤 9:添加手机号页面拒绝当前号码,但未明确提示已使用(${addPhoneRejectText}),将用同一号码再试一次。`,
|
||||
@@ -6050,10 +6110,16 @@
|
||||
|| submitResult?.url
|
||||
|| 'unknown error'
|
||||
);
|
||||
if (isPhoneNumberUsedError(retryRejectText) || isRecoverableAddPhoneSubmitError(retryRejectText)) {
|
||||
if (
|
||||
isPhoneNumberUsedError(retryRejectText)
|
||||
|| isPhoneNumberDeliveryRefusedError(retryRejectText)
|
||||
|| isRecoverableAddPhoneSubmitError(retryRejectText)
|
||||
) {
|
||||
await rotateActivationAfterAddPhoneFailure(
|
||||
`add-phone keeps rejecting ${activation.phoneNumber} (${retryRejectText})`,
|
||||
isPhoneNumberUsedError(retryRejectText) ? 'phone_number_used' : 'add_phone_rejected',
|
||||
isPhoneNumberUsedError(retryRejectText)
|
||||
? 'phone_number_used'
|
||||
: (isPhoneNumberDeliveryRefusedError(retryRejectText) ? 'phone_delivery_refused' : 'add_phone_rejected'),
|
||||
submitResult || {}
|
||||
);
|
||||
continue;
|
||||
@@ -6170,9 +6236,30 @@
|
||||
|
||||
if (remainingResendRequests > 0) {
|
||||
remainingResendRequests -= 1;
|
||||
await requestAdditionalPhoneSms(state, activation);
|
||||
try {
|
||||
await resendPhoneVerificationCode(tabId);
|
||||
const resendProbeResult = await resendPhoneVerificationCode(tabId, { probeOnly: true });
|
||||
if (isWhatsAppPhoneResendResult(resendProbeResult)) {
|
||||
shouldReplaceNumber = true;
|
||||
replaceReason = 'whatsapp_resend_channel';
|
||||
await addLog(
|
||||
`步骤 9:验证码被拒后的重发入口显示 WhatsApp 通道(${resendProbeResult.channelText || resendProbeResult.text || 'WhatsApp'}),当前接码平台无法读取 WhatsApp 消息,将更换号码。`,
|
||||
'warn'
|
||||
);
|
||||
break;
|
||||
}
|
||||
await requestAdditionalPhoneSms(state, activation);
|
||||
if (resendProbeResult?.probed) {
|
||||
const resendResult = await resendPhoneVerificationCode(tabId);
|
||||
if (isWhatsAppPhoneResendResult(resendResult)) {
|
||||
shouldReplaceNumber = true;
|
||||
replaceReason = 'whatsapp_resend_channel';
|
||||
await addLog(
|
||||
`步骤 9:验证码被拒后的重发入口切换为 WhatsApp 通道(${resendResult.channelText || resendResult.text || 'WhatsApp'}),当前接码平台无法读取 WhatsApp 消息,将更换号码。`,
|
||||
'warn'
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
await addLog('步骤 9:手机验证码被拒后已点击“重新发送短信”。', 'info');
|
||||
} catch (resendError) {
|
||||
await addLog(`步骤 9:验证码被拒后点击重发失败。${resendError.message}`, 'warn');
|
||||
|
||||
+52
-2
@@ -403,6 +403,33 @@
|
||||
}) || buttons.find((button) => isVisibleElement(button));
|
||||
}
|
||||
|
||||
function getPhoneVerificationResendActionText(button) {
|
||||
if (!button) return '';
|
||||
return [
|
||||
button.getAttribute?.('value'),
|
||||
button.getAttribute?.('aria-label'),
|
||||
button.getAttribute?.('title'),
|
||||
getActionText(button),
|
||||
button.textContent,
|
||||
].filter(Boolean).join(' ').replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function isWhatsAppResendText(value) {
|
||||
return /whats\s*app/i.test(String(value || ''));
|
||||
}
|
||||
|
||||
function getPhoneVerificationResendActionInfo(button) {
|
||||
const text = getPhoneVerificationResendActionText(button);
|
||||
const channel = isWhatsAppResendText(text)
|
||||
? 'whatsapp'
|
||||
: (/(?:sms|text\s+message|短信)/i.test(text) ? 'sms' : '');
|
||||
return {
|
||||
channel,
|
||||
channelText: text,
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
function getPhoneVerificationResendButton(options = {}) {
|
||||
const { allowDisabled = false } = options;
|
||||
const form = getPhoneVerificationForm();
|
||||
@@ -413,7 +440,7 @@
|
||||
if (!allowDisabled && !isActionEnabled(button)) return false;
|
||||
const intent = String(button.getAttribute('value') || '').trim().toLowerCase();
|
||||
if (intent === 'resend') return true;
|
||||
return /resend/i.test(getActionText(button));
|
||||
return /resend|重新发送|再次发送|whats\s*app/i.test(getPhoneVerificationResendActionText(button));
|
||||
}) || null;
|
||||
}
|
||||
|
||||
@@ -829,7 +856,7 @@
|
||||
return waitForPhoneVerificationOutcome();
|
||||
}
|
||||
|
||||
async function resendPhoneVerificationCode(timeout = 45000) {
|
||||
async function resendPhoneVerificationCode(timeout = 45000, options = {}) {
|
||||
if (activePhoneResendPromise) {
|
||||
return activePhoneResendPromise;
|
||||
}
|
||||
@@ -872,6 +899,26 @@
|
||||
}
|
||||
const resendButton = getPhoneVerificationResendButton({ allowDisabled: true });
|
||||
if (resendButton && isActionEnabled(resendButton)) {
|
||||
const resendInfo = getPhoneVerificationResendActionInfo(resendButton);
|
||||
if (resendInfo.channel === 'whatsapp') {
|
||||
return {
|
||||
resent: false,
|
||||
channel: 'whatsapp',
|
||||
channelText: resendInfo.channelText,
|
||||
text: resendInfo.text,
|
||||
url: location.href,
|
||||
};
|
||||
}
|
||||
if (options?.probeOnly) {
|
||||
return {
|
||||
resent: false,
|
||||
probed: true,
|
||||
channel: resendInfo.channel || 'unknown',
|
||||
channelText: resendInfo.channelText,
|
||||
text: resendInfo.text,
|
||||
url: location.href,
|
||||
};
|
||||
}
|
||||
await humanPause(250, 700);
|
||||
await performOperationWithDelay({ stepKey: 'phone-auth', kind: 'click', label: 'phone-verification-resend' }, async () => {
|
||||
simulateClick(resendButton);
|
||||
@@ -891,6 +938,9 @@
|
||||
}
|
||||
return {
|
||||
resent: true,
|
||||
channel: resendInfo.channel || 'sms',
|
||||
channelText: resendInfo.channelText,
|
||||
text: resendInfo.text,
|
||||
url: location.href,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ async function handleCommand(message) {
|
||||
}
|
||||
return await submitPhoneVerificationCodeWithProfileFallback(message.payload);
|
||||
case 'RESEND_PHONE_VERIFICATION_CODE':
|
||||
return await phoneAuthHelpers.resendPhoneVerificationCode();
|
||||
return await phoneAuthHelpers.resendPhoneVerificationCode(undefined, message.payload || {});
|
||||
case 'CHECK_PHONE_RESEND_ERROR':
|
||||
return phoneAuthHelpers.checkPhoneResendError();
|
||||
case 'RETURN_TO_ADD_PHONE':
|
||||
|
||||
@@ -449,6 +449,80 @@ test('phone auth resend stops with PHONE_ROUTE_405_RECOVERY_FAILED instead of en
|
||||
}
|
||||
});
|
||||
|
||||
test('phone auth probes WhatsApp resend channel without clicking', async () => {
|
||||
const originalDocument = global.document;
|
||||
const originalLocation = global.location;
|
||||
const originalWindow = global.window;
|
||||
let clickCount = 0;
|
||||
const fakeWhatsAppButton = {
|
||||
disabled: false,
|
||||
textContent: '重新发送 WhatsApp 消息',
|
||||
getAttribute() {
|
||||
return '';
|
||||
},
|
||||
click() {
|
||||
clickCount += 1;
|
||||
},
|
||||
};
|
||||
const fakePhoneForm = {
|
||||
querySelectorAll(selector) {
|
||||
if (selector === 'button, input[type="submit"], input[type="button"]') {
|
||||
return [fakeWhatsAppButton];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
||||
global.document = {
|
||||
querySelector(selector) {
|
||||
if (selector === 'form[action*="/phone-verification" i]') {
|
||||
return fakePhoneForm;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
querySelectorAll() {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
global.location = {
|
||||
href: 'https://auth.openai.com/phone-verification',
|
||||
pathname: '/phone-verification',
|
||||
};
|
||||
global.window = global;
|
||||
|
||||
const helpers = api.createPhoneAuthHelpers({
|
||||
fillInput: () => {},
|
||||
getActionText: (element) => String(element?.textContent || ''),
|
||||
getPageTextSnapshot: () => '重新发送 WhatsApp 消息',
|
||||
getVerificationErrorText: () => '',
|
||||
humanPause: async () => {},
|
||||
isActionEnabled: () => true,
|
||||
isAddPhonePageReady: () => false,
|
||||
isConsentReady: () => false,
|
||||
isPhoneVerificationPageReady: () => true,
|
||||
isVisibleElement: () => true,
|
||||
simulateClick: (element) => {
|
||||
element?.click?.();
|
||||
},
|
||||
sleep: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
waitForElement: async () => null,
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await helpers.resendPhoneVerificationCode(1000, { probeOnly: true });
|
||||
|
||||
assert.equal(result.resent, false);
|
||||
assert.equal(result.channel, 'whatsapp');
|
||||
assert.match(result.channelText, /WhatsApp/i);
|
||||
assert.equal(clickCount, 0);
|
||||
} finally {
|
||||
global.document = originalDocument;
|
||||
global.location = originalLocation;
|
||||
global.window = originalWindow;
|
||||
}
|
||||
});
|
||||
|
||||
test('phone auth exposes resend page error checks for banned numbers', () => {
|
||||
const originalLocation = global.location;
|
||||
const originalDocument = global.document;
|
||||
|
||||
@@ -2938,6 +2938,284 @@ test('phone verification helper immediately replaces number when page says the p
|
||||
);
|
||||
});
|
||||
|
||||
test('phone verification helper rotates number when verification code cannot be sent from add-phone rejection', async () => {
|
||||
const requests = [];
|
||||
const messages = [];
|
||||
const submittedNumbers = [];
|
||||
const refusedText = '无法向此电话号码发送验证码。请稍后重试或使用其他号码。';
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
verificationResendCount: 0,
|
||||
phoneVerificationReplacementLimit: 2,
|
||||
currentPhoneActivation: null,
|
||||
reusablePhoneActivation: null,
|
||||
};
|
||||
|
||||
const numbers = [
|
||||
{ activationId: '331111', phoneNumber: '66950003111' },
|
||||
{ activationId: '332222', phoneNumber: '66950003222' },
|
||||
];
|
||||
let numberIndex = 0;
|
||||
|
||||
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') {
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => (id === '332222' ? 'STATUS_OK:654321' : 'STATUS_WAIT_CODE'),
|
||||
};
|
||||
}
|
||||
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 }),
|
||||
sendToContentScriptResilient: async (_source, message) => {
|
||||
messages.push(message.type);
|
||||
if (message.type === 'SUBMIT_PHONE_NUMBER') {
|
||||
const phoneNumber = message.payload.phoneNumber;
|
||||
submittedNumbers.push(phoneNumber);
|
||||
if (phoneNumber === '66950003111') {
|
||||
return {
|
||||
addPhoneRejected: true,
|
||||
addPhonePage: true,
|
||||
errorText: refusedText,
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
};
|
||||
}
|
||||
return {
|
||||
phoneVerificationPage: true,
|
||||
url: 'https://auth.openai.com/phone-verification',
|
||||
};
|
||||
}
|
||||
if (message.type === 'RETURN_TO_ADD_PHONE') {
|
||||
return {
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
};
|
||||
}
|
||||
if (message.type === 'STEP8_GET_STATE') {
|
||||
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',
|
||||
};
|
||||
}
|
||||
if (message.type === 'RESEND_PHONE_VERIFICATION_CODE') {
|
||||
throw new Error('delivery-refused add-phone rejection should rotate before resend.');
|
||||
}
|
||||
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.deepStrictEqual(submittedNumbers, ['66950003111', '66950003222']);
|
||||
assert.equal(numberIndex, 2);
|
||||
assert.equal(messages.includes('RESEND_PHONE_VERIFICATION_CODE'), false);
|
||||
assert.equal(
|
||||
requests.filter((requestUrl) => requestUrl.searchParams.get('action') === 'getNumber').length,
|
||||
2
|
||||
);
|
||||
});
|
||||
|
||||
test('phone verification helper keeps used-number cleanup when add-phone rejection asks to use a different phone number', async () => {
|
||||
const messages = [];
|
||||
const submittedNumbers = [];
|
||||
const usedText = 'This phone number is already linked to the maximum number of accounts. Please use a different phone number.';
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
verificationResendCount: 0,
|
||||
phoneVerificationReplacementLimit: 2,
|
||||
currentPhoneActivation: null,
|
||||
reusablePhoneActivation: null,
|
||||
phoneReusableActivationPool: [
|
||||
{
|
||||
activationId: 'pool-used-different-match',
|
||||
phoneNumber: '+66 95 000 4111',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
successfulUses: 1,
|
||||
maxUses: 3,
|
||||
},
|
||||
],
|
||||
freeReusablePhoneActivation: {
|
||||
activationId: 'free-used-different-match',
|
||||
phoneNumber: '+66 95 000 4111',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
countryLabel: 'Thailand',
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
source: 'free-manual-reuse',
|
||||
},
|
||||
};
|
||||
|
||||
const numbers = [
|
||||
{ activationId: '341111', phoneNumber: '66950004111' },
|
||||
{ activationId: '342222', phoneNumber: '66950004222' },
|
||||
];
|
||||
let numberIndex = 0;
|
||||
|
||||
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 === '342222' ? 'STATUS_OK:654321' : 'STATUS_WAIT_CODE'),
|
||||
};
|
||||
}
|
||||
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 }),
|
||||
sendToContentScriptResilient: async (_source, message) => {
|
||||
messages.push(message.type);
|
||||
if (message.type === 'SUBMIT_PHONE_NUMBER') {
|
||||
const phoneNumber = message.payload.phoneNumber;
|
||||
submittedNumbers.push(phoneNumber);
|
||||
if (phoneNumber === '66950004111') {
|
||||
return {
|
||||
addPhoneRejected: true,
|
||||
addPhonePage: true,
|
||||
errorText: usedText,
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
};
|
||||
}
|
||||
return {
|
||||
phoneVerificationPage: true,
|
||||
url: 'https://auth.openai.com/phone-verification',
|
||||
};
|
||||
}
|
||||
if (message.type === 'RETURN_TO_ADD_PHONE') {
|
||||
return {
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
};
|
||||
}
|
||||
if (message.type === 'STEP8_GET_STATE') {
|
||||
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',
|
||||
};
|
||||
}
|
||||
if (message.type === 'RESEND_PHONE_VERIFICATION_CODE') {
|
||||
throw new Error('used add-phone rejection should rotate before resend.');
|
||||
}
|
||||
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.deepStrictEqual(submittedNumbers, ['66950004111', '66950004222']);
|
||||
assert.equal(currentState.freeReusablePhoneActivation, null);
|
||||
assert.deepStrictEqual(
|
||||
currentState.phoneReusableActivationPool.filter((activation) => activation.phoneNumber === '+66 95 000 4111'),
|
||||
[]
|
||||
);
|
||||
assert.equal(messages.includes('RESEND_PHONE_VERIFICATION_CODE'), false);
|
||||
});
|
||||
|
||||
test('phone verification helper treats phone_max_usage_exceeded as used-number and rotates immediately', async () => {
|
||||
const requests = [];
|
||||
const messages = [];
|
||||
@@ -5103,6 +5381,149 @@ test('phone verification helper skips page resend for 5sim timeouts and rotates
|
||||
);
|
||||
});
|
||||
|
||||
test('phone verification helper rotates number when resend action is WhatsApp in SMS provider mode', async () => {
|
||||
const requests = [];
|
||||
const messages = [];
|
||||
const submittedNumbers = [];
|
||||
const statusCallsById = {};
|
||||
const heroSmsResendStatusCalls = [];
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsCountryId: 52,
|
||||
heroSmsCountryLabel: 'Thailand',
|
||||
verificationResendCount: 1,
|
||||
phoneVerificationReplacementLimit: 2,
|
||||
phoneCodeWaitSeconds: 60,
|
||||
phoneCodeTimeoutWindows: 2,
|
||||
phoneCodePollIntervalSeconds: 1,
|
||||
phoneCodePollMaxRounds: 1,
|
||||
currentPhoneActivation: null,
|
||||
reusablePhoneActivation: null,
|
||||
};
|
||||
const numbers = [
|
||||
{ activationId: '520001', phoneNumber: '66952220001' },
|
||||
{ activationId: '520002', phoneNumber: '66952220002' },
|
||||
];
|
||||
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');
|
||||
const status = parsedUrl.searchParams.get('status');
|
||||
|
||||
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 === '520001' ? 'STATUS_WAIT_CODE' : 'STATUS_OK:123456'),
|
||||
};
|
||||
}
|
||||
if (action === 'setStatus') {
|
||||
if (id === '520001' && status === '3') {
|
||||
heroSmsResendStatusCalls.push(parsedUrl);
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
text: async () => `STATUS_UPDATED:${id}`,
|
||||
};
|
||||
}
|
||||
throw new Error(`Unexpected HeroSMS action: ${action}`);
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getState: async () => ({ ...currentState }),
|
||||
sendToContentScriptResilient: async (_source, message) => {
|
||||
messages.push(message.type);
|
||||
if (message.type === 'SUBMIT_PHONE_NUMBER') {
|
||||
submittedNumbers.push(message.payload.phoneNumber);
|
||||
return {
|
||||
phoneVerificationPage: true,
|
||||
url: 'https://auth.openai.com/phone-verification',
|
||||
};
|
||||
}
|
||||
if (message.type === 'RESEND_PHONE_VERIFICATION_CODE') {
|
||||
return {
|
||||
resent: false,
|
||||
channel: 'whatsapp',
|
||||
channelText: '重新发送 WhatsApp 消息',
|
||||
text: '重新发送 WhatsApp 消息',
|
||||
url: 'https://auth.openai.com/phone-verification',
|
||||
};
|
||||
}
|
||||
if (message.type === 'RETURN_TO_ADD_PHONE') {
|
||||
return {
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
};
|
||||
}
|
||||
if (message.type === 'STEP8_GET_STATE') {
|
||||
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, ['66952220001', '66952220002']);
|
||||
assert.equal(heroSmsResendStatusCalls.length, 0, 'WhatsApp resend must not call HeroSMS setStatus(3)');
|
||||
assert.equal(statusCallsById['520001'], 1, 'WhatsApp resend should replace after the first SMS wait window');
|
||||
assert.equal(messages.includes('RESEND_PHONE_VERIFICATION_CODE'), true);
|
||||
} finally {
|
||||
Date.now = realDateNow;
|
||||
}
|
||||
});
|
||||
|
||||
test('phone verification helper rotates number immediately when 5sim activation is missing (order not found)', async () => {
|
||||
const requests = [];
|
||||
let currentState = {
|
||||
|
||||
Reference in New Issue
Block a user