fix: handle manual free phone reuse handoff
- 合并 PR #213 的核心改动:手动保存白嫖复用手机号时从本地运行态恢复 HeroSMS activation,并按号码前缀推断提交国家。 - 本地补充修复:无,本轮审查未发现需要额外修改的逻辑错误。 - 影响范围:Step 9 手机号复用保存/手动交接、HeroSMS 国家提交、相关回归测试。
This commit is contained in:
+112
-4
@@ -393,8 +393,18 @@ const FIVE_SIM_COUNTRY_ID = 'vietnam';
|
||||
const FIVE_SIM_COUNTRY_LABEL = '越南 (Vietnam)';
|
||||
const FIVE_SIM_SUPPORTED_COUNTRY_IDS = ['indonesia', 'thailand', 'vietnam'];
|
||||
const FIVE_SIM_SUPPORTED_COUNTRY_ID_SET = new Set(FIVE_SIM_SUPPORTED_COUNTRY_IDS);
|
||||
const HERO_SMS_SUPPORTED_COUNTRY_IDS = [6, 52, 10];
|
||||
const HERO_SMS_SUPPORTED_COUNTRY_IDS = [6, 52, 187, 16, 151, 43, 73, 10];
|
||||
const HERO_SMS_SUPPORTED_COUNTRY_ID_SET = new Set(HERO_SMS_SUPPORTED_COUNTRY_IDS.map(String));
|
||||
const HERO_SMS_COUNTRY_BY_PHONE_PREFIX = Object.freeze([
|
||||
{ prefix: '84', id: 10, label: 'Vietnam' },
|
||||
{ prefix: '66', id: 52, label: 'Thailand' },
|
||||
{ prefix: '62', id: 6, label: 'Indonesia' },
|
||||
{ prefix: '44', id: 16, label: 'United Kingdom' },
|
||||
{ prefix: '81', id: 151, label: 'Japan' },
|
||||
{ prefix: '49', id: 43, label: 'Germany' },
|
||||
{ prefix: '33', id: 73, label: 'France' },
|
||||
{ prefix: '1', id: 187, label: 'USA' },
|
||||
]);
|
||||
const FIVE_SIM_OPERATOR = DEFAULT_FIVE_SIM_OPERATOR;
|
||||
const PLUS_PAYMENT_METHOD_PAYPAL = 'paypal';
|
||||
const PLUS_PAYMENT_METHOD_GOPAY = 'gopay';
|
||||
@@ -6780,19 +6790,117 @@ async function clearFreeReusablePhoneActivation() {
|
||||
return { ok: true, freeReusablePhoneActivation: null };
|
||||
}
|
||||
|
||||
function inferHeroSmsCountryFromPhoneNumber(phoneNumber = '') {
|
||||
const digits = String(phoneNumber || '').replace(/\D+/g, '');
|
||||
if (!digits) {
|
||||
return null;
|
||||
}
|
||||
const match = HERO_SMS_COUNTRY_BY_PHONE_PREFIX.find((entry) => digits.startsWith(entry.prefix));
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id: Math.max(1, Math.floor(Number(match.id) || 0)),
|
||||
label: String(match.label || '').trim() || `Country #${match.id}`,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePhoneDigits(value = '') {
|
||||
return String(value || '').replace(/\D+/g, '');
|
||||
}
|
||||
|
||||
function phoneNumbersMatch(left = '', right = '') {
|
||||
const leftDigits = normalizePhoneDigits(left);
|
||||
const rightDigits = normalizePhoneDigits(right);
|
||||
return Boolean(leftDigits && rightDigits && leftDigits === rightDigits);
|
||||
}
|
||||
|
||||
function normalizeLocalHeroSmsActivation(record) {
|
||||
if (!record || typeof record !== 'object' || Array.isArray(record)) {
|
||||
return null;
|
||||
}
|
||||
const activationId = String(record.activationId ?? record.id ?? record.activation ?? '').trim();
|
||||
const phoneNumber = String(record.phoneNumber ?? record.number ?? record.phone ?? '').trim();
|
||||
if (!activationId || !phoneNumber) {
|
||||
return null;
|
||||
}
|
||||
const rawProvider = String(record.provider ?? record.smsProvider ?? '').trim();
|
||||
const provider = rawProvider ? normalizePhoneSmsProvider(rawProvider) : PHONE_SMS_PROVIDER_HERO;
|
||||
if (provider !== PHONE_SMS_PROVIDER_HERO) {
|
||||
return null;
|
||||
}
|
||||
const countryId = Math.max(
|
||||
0,
|
||||
Math.floor(Number(record.countryId ?? record.country ?? record.countryCode) || 0)
|
||||
);
|
||||
const countryLabel = String(record.countryLabel || record.label || '').trim();
|
||||
const serviceCode = String(record.serviceCode || record.service || HERO_SMS_SERVICE_CODE).trim() || HERO_SMS_SERVICE_CODE;
|
||||
return {
|
||||
...record,
|
||||
provider: PHONE_SMS_PROVIDER_HERO,
|
||||
activationId,
|
||||
phoneNumber,
|
||||
serviceCode,
|
||||
...(countryId > 0 ? { countryId } : {}),
|
||||
...(countryLabel ? { countryLabel } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function findLocalHeroSmsActivationForPhone(state = {}, phoneNumber = '') {
|
||||
const candidates = [
|
||||
state.currentPhoneActivation,
|
||||
state.reusablePhoneActivation,
|
||||
state.pendingPhoneActivationConfirmation,
|
||||
state.signupPhoneActivation,
|
||||
state.signupPhoneCompletedActivation,
|
||||
state.phonePreferredActivation,
|
||||
state.freeReusablePhoneActivation,
|
||||
];
|
||||
if (Array.isArray(state.phoneReusableActivationPool)) {
|
||||
candidates.push(...state.phoneReusableActivationPool);
|
||||
}
|
||||
for (const candidate of candidates) {
|
||||
const normalized = normalizeLocalHeroSmsActivation(candidate);
|
||||
if (normalized && phoneNumbersMatch(normalized.phoneNumber, phoneNumber)) {
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function setFreeReusablePhoneActivation(record = {}) {
|
||||
const phoneNumber = String(record.phoneNumber || record.number || record.phone || '').trim();
|
||||
if (!phoneNumber) {
|
||||
throw new Error('请先填写白嫖复用手机号。');
|
||||
}
|
||||
const state = await getState();
|
||||
const activationId = String(record.activationId || record.id || record.activation || '').trim();
|
||||
const countryId = Math.max(1, Math.floor(Number(record.countryId) || Number(state.heroSmsCountryId) || HERO_SMS_COUNTRY_ID));
|
||||
const localActivation = findLocalHeroSmsActivationForPhone(state, phoneNumber);
|
||||
const activationId = String(
|
||||
record.activationId
|
||||
|| record.id
|
||||
|| record.activation
|
||||
|| localActivation?.activationId
|
||||
|| ''
|
||||
).trim();
|
||||
const inferredCountry = inferHeroSmsCountryFromPhoneNumber(phoneNumber);
|
||||
const hasExplicitCountry = Number.isFinite(Number(record.countryId)) && Number(record.countryId) > 0;
|
||||
const countryId = Math.max(
|
||||
1,
|
||||
Math.floor(
|
||||
Number(record.countryId)
|
||||
|| Number(localActivation?.countryId)
|
||||
|| Number(inferredCountry?.id)
|
||||
|| Number(state.heroSmsCountryId)
|
||||
|| HERO_SMS_COUNTRY_ID
|
||||
)
|
||||
);
|
||||
const stateCountryLabel = Math.floor(Number(state.heroSmsCountryId) || 0) === countryId
|
||||
? String(state.heroSmsCountryLabel || '').trim()
|
||||
: '';
|
||||
const countryLabel = String(
|
||||
record.countryLabel
|
||||
|| (Number(localActivation?.countryId) === countryId ? localActivation?.countryLabel : '')
|
||||
|| (!hasExplicitCountry && inferredCountry?.id === countryId ? inferredCountry.label : '')
|
||||
|| stateCountryLabel
|
||||
|| (countryId === HERO_SMS_COUNTRY_ID ? HERO_SMS_COUNTRY_LABEL : `Country #${countryId}`)
|
||||
).trim();
|
||||
@@ -6800,7 +6908,7 @@ async function setFreeReusablePhoneActivation(record = {}) {
|
||||
...(activationId ? { activationId } : {}),
|
||||
phoneNumber,
|
||||
provider: PHONE_SMS_PROVIDER_HERO,
|
||||
serviceCode: HERO_SMS_SERVICE_CODE,
|
||||
serviceCode: String(record.serviceCode || localActivation?.serviceCode || HERO_SMS_SERVICE_CODE).trim() || HERO_SMS_SERVICE_CODE,
|
||||
countryId,
|
||||
...(countryLabel ? { countryLabel } : {}),
|
||||
successfulUses: Math.max(0, Math.floor(Number(record.successfulUses) || 0)),
|
||||
|
||||
@@ -100,6 +100,16 @@
|
||||
const FREE_PHONE_REUSE_PREPARE_MAX_ROUNDS = 10;
|
||||
const PHONE_SMS_FAILURE_SKIP_THRESHOLD = 2;
|
||||
const MAX_ACTIVATION_PRICE_HINTS = 256;
|
||||
const HERO_SMS_COUNTRY_BY_PHONE_PREFIX = Object.freeze([
|
||||
{ prefix: '84', id: 10, label: 'Vietnam' },
|
||||
{ prefix: '66', id: 52, label: 'Thailand' },
|
||||
{ prefix: '62', id: 6, label: 'Indonesia' },
|
||||
{ prefix: '44', id: 16, label: 'United Kingdom' },
|
||||
{ prefix: '81', id: 151, label: 'Japan' },
|
||||
{ prefix: '49', id: 43, label: 'Germany' },
|
||||
{ prefix: '33', id: 73, label: 'France' },
|
||||
{ prefix: '1', id: 187, label: 'USA' },
|
||||
]);
|
||||
const activationPriceHintsByKey = new Map();
|
||||
let activePhoneVerificationLogStep = null;
|
||||
let activePhoneVerificationLogStepKey = null;
|
||||
@@ -422,6 +432,21 @@
|
||||
return String(value || '').trim() || fallback;
|
||||
}
|
||||
|
||||
function inferHeroSmsCountryFromPhoneNumber(phoneNumber = '') {
|
||||
const digits = String(phoneNumber || '').replace(/\D+/g, '');
|
||||
if (!digits) {
|
||||
return null;
|
||||
}
|
||||
const match = HERO_SMS_COUNTRY_BY_PHONE_PREFIX.find((entry) => digits.startsWith(entry.prefix));
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id: normalizeCountryId(match.id, 0),
|
||||
label: normalizeCountryLabel(match.label, `Country #${match.id}`),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePhoneCodeWaitSeconds(value) {
|
||||
const parsed = Math.floor(Number(value));
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||
@@ -1037,14 +1062,19 @@
|
||||
const activationId = String(
|
||||
record.activationId ?? record.id ?? record.activation ?? ''
|
||||
).trim();
|
||||
const countryLabel = String(record.countryLabel || '').trim();
|
||||
const inferredCountry = inferHeroSmsCountryFromPhoneNumber(phoneNumber);
|
||||
const countryId = normalizeCountryId(record.countryId, inferredCountry?.id || HERO_SMS_COUNTRY_ID);
|
||||
const countryLabel = String(
|
||||
record.countryLabel
|
||||
|| (inferredCountry && inferredCountry.id === countryId ? inferredCountry.label : '')
|
||||
).trim();
|
||||
const statusAction = String(record.statusAction || '').trim();
|
||||
return {
|
||||
...(activationId ? { activationId } : {}),
|
||||
phoneNumber,
|
||||
provider: PHONE_SMS_PROVIDER_HERO,
|
||||
serviceCode: String(record.serviceCode || HERO_SMS_SERVICE_CODE).trim() || HERO_SMS_SERVICE_CODE,
|
||||
countryId: normalizeCountryId(record.countryId, HERO_SMS_COUNTRY_ID),
|
||||
countryId,
|
||||
...(countryLabel ? { countryLabel } : {}),
|
||||
successfulUses: normalizeUseCount(record.successfulUses),
|
||||
maxUses: Math.max(1, Math.floor(Number(record.maxUses) || DEFAULT_PHONE_NUMBER_MAX_USES)),
|
||||
@@ -3935,7 +3965,16 @@
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const countryId = normalizeCountryId(activation.countryId, 0);
|
||||
const inferredCountry = inferHeroSmsCountryFromPhoneNumber(activation.phoneNumber);
|
||||
const rawCountryId = normalizeCountryId(activation.countryId, 0);
|
||||
const hasExplicitCountry = Object.prototype.hasOwnProperty.call(activation, 'countryId')
|
||||
&& Number.isFinite(rawCountryId)
|
||||
&& rawCountryId > 0
|
||||
&& !(activation.manualOnly && rawCountryId === HERO_SMS_COUNTRY_ID && inferredCountry?.id && inferredCountry.id !== rawCountryId);
|
||||
const countryId = hasExplicitCountry ? rawCountryId : normalizeCountryId(inferredCountry?.id, rawCountryId);
|
||||
const countryLabel = hasExplicitCountry
|
||||
? activation.countryLabel
|
||||
: (inferredCountry?.label || activation.countryLabel);
|
||||
if (countryId > 0) {
|
||||
const matched = candidates.find((entry) => entry.id === countryId);
|
||||
if (matched) {
|
||||
@@ -3943,7 +3982,7 @@
|
||||
}
|
||||
return {
|
||||
id: countryId,
|
||||
label: normalizeCountryLabel(activation.countryLabel, `Country #${countryId}`),
|
||||
label: normalizeCountryLabel(countryLabel, `Country #${countryId}`),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4286,7 +4325,11 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
if (normalizeFreePhoneReuseAutoEnabled(state)) {
|
||||
const canPrepareAutomaticFreeReuse = normalizeFreePhoneReuseAutoEnabled(state)
|
||||
&& !freeReusableActivation.manualOnly
|
||||
&& Boolean(String(freeReusableActivation.activationId || '').trim());
|
||||
|
||||
if (canPrepareAutomaticFreeReuse) {
|
||||
await addLog(
|
||||
`步骤 9:准备自动白嫖复用已保存手机号 ${freeReusableActivation.phoneNumber}(${freeReusableActivation.successfulUses + 1}/${freeReusableActivation.maxUses})。`,
|
||||
'info'
|
||||
|
||||
@@ -28,6 +28,47 @@ test('background free reusable phone setter does not depend on module-scoped pho
|
||||
assert.match(setterBlock, /maxUses:\s*Math\.max\(1,\s*Math\.floor\(Number\(record\.maxUses\)\s*\|\|\s*3\)\)/);
|
||||
});
|
||||
|
||||
test('background free reusable phone setter can recover local HeroSMS activation id by phone number', () => {
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
const setterStart = source.indexOf('async function setFreeReusablePhoneActivation');
|
||||
const setterEnd = source.indexOf('// ============================================================\n// Tab Registry', setterStart);
|
||||
const setterBlock = source.slice(setterStart, setterEnd);
|
||||
|
||||
assert.match(source, /function findLocalHeroSmsActivationForPhone\(/);
|
||||
assert.match(source, /state\.currentPhoneActivation/);
|
||||
assert.match(source, /state\.reusablePhoneActivation/);
|
||||
assert.match(source, /state\.signupPhoneActivation/);
|
||||
assert.match(source, /state\.signupPhoneCompletedActivation/);
|
||||
assert.match(source, /state\.phonePreferredActivation/);
|
||||
assert.match(source, /state\.phoneReusableActivationPool/);
|
||||
assert.match(setterBlock, /findLocalHeroSmsActivationForPhone\(state,\s*phoneNumber\)/);
|
||||
assert.match(setterBlock, /activationId = String\(\s*record\.activationId[\s\S]*localActivation\?\.activationId/);
|
||||
assert.match(setterBlock, /manualOnly:\s*!activationId/);
|
||||
});
|
||||
|
||||
test('background HeroSMS phone prefix inference covers built-in major countries', () => {
|
||||
const source = fs.readFileSync('background.js', 'utf8');
|
||||
const supportedStart = source.indexOf('const HERO_SMS_SUPPORTED_COUNTRY_IDS = [');
|
||||
const prefixStart = source.indexOf('const HERO_SMS_COUNTRY_BY_PHONE_PREFIX = Object.freeze([');
|
||||
const prefixEnd = source.indexOf(']);', prefixStart);
|
||||
const supportedBlock = source.slice(supportedStart, source.indexOf('];', supportedStart));
|
||||
const prefixBlock = source.slice(prefixStart, prefixEnd);
|
||||
|
||||
assert.match(supportedBlock, /\[6,\s*52,\s*187,\s*16,\s*151,\s*43,\s*73,\s*10/);
|
||||
[
|
||||
['84', 10, 'Vietnam'],
|
||||
['66', 52, 'Thailand'],
|
||||
['62', 6, 'Indonesia'],
|
||||
['44', 16, 'United Kingdom'],
|
||||
['81', 151, 'Japan'],
|
||||
['49', 43, 'Germany'],
|
||||
['33', 73, 'France'],
|
||||
['1', 187, 'USA'],
|
||||
].forEach(([prefix, id, label]) => {
|
||||
assert.match(prefixBlock, new RegExp(`prefix:\\s*'${prefix}'[\\s\\S]*id:\\s*${id}[\\s\\S]*label:\\s*'${label}'`));
|
||||
});
|
||||
});
|
||||
|
||||
test('message router module exposes a factory', () => {
|
||||
const source = fs.readFileSync('background/message-router.js', 'utf8');
|
||||
const globalScope = {};
|
||||
|
||||
@@ -3569,6 +3569,238 @@ test('phone verification helper gives automatic free reuse priority over paid re
|
||||
assert.equal(currentState.reusablePhoneActivation.activationId, 'paid-reuse');
|
||||
});
|
||||
|
||||
test('phone verification helper hands off manual-only free reuse even when automatic free reuse is enabled', async () => {
|
||||
const requests = [];
|
||||
const messages = [];
|
||||
const stops = [];
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsReuseEnabled: true,
|
||||
freePhoneReuseEnabled: true,
|
||||
freePhoneReuseAutoEnabled: true,
|
||||
verificationResendCount: 0,
|
||||
currentPhoneActivation: null,
|
||||
reusablePhoneActivation: {
|
||||
activationId: 'paid-reuse',
|
||||
phoneNumber: '66950003333',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
countryLabel: 'Thailand',
|
||||
successfulUses: 0,
|
||||
maxUses: 3,
|
||||
},
|
||||
freeReusablePhoneActivation: {
|
||||
phoneNumber: '84943328460',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
countryId: 52,
|
||||
countryLabel: 'Thailand',
|
||||
successfulUses: 1,
|
||||
maxUses: 3,
|
||||
source: 'free-manual-reuse',
|
||||
manualOnly: true,
|
||||
},
|
||||
};
|
||||
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
requests.push(parsedUrl);
|
||||
throw new Error(`HeroSMS should not be called for manual-only free reuse: ${parsedUrl.searchParams.get('action')}`);
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getState: async () => ({ ...currentState }),
|
||||
requestStop: async (payload) => {
|
||||
stops.push(payload);
|
||||
},
|
||||
sendToContentScriptResilient: async (_source, message) => {
|
||||
messages.push(message);
|
||||
if (message.type === 'SUBMIT_PHONE_NUMBER') {
|
||||
assert.equal(message.payload.phoneNumber, '84943328460');
|
||||
return { phoneVerificationPage: true, url: 'https://auth.openai.com/phone-verification' };
|
||||
}
|
||||
throw new Error(`Unexpected content-script message: ${message.type}`);
|
||||
},
|
||||
setState: async (updates) => {
|
||||
currentState = { ...currentState, ...updates };
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
helpers.completePhoneVerificationFlow(1, {
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
}),
|
||||
(error) => {
|
||||
assert.match(error.message, /^PHONE_MANUAL_FREE_REUSE::/);
|
||||
assert.equal(error.result?.manualFreePhoneReuse, true);
|
||||
assert.equal(error.result?.phoneNumber, '84943328460');
|
||||
assert.deepStrictEqual(error.result?.fillResult, {
|
||||
phoneVerificationPage: true,
|
||||
url: 'https://auth.openai.com/phone-verification',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(messages.map((message) => message.type), ['SUBMIT_PHONE_NUMBER']);
|
||||
assert.equal(stops.length, 1);
|
||||
assert.match(stops[0].logMessage, /开始手动复用手机 84943328460/);
|
||||
assert.deepStrictEqual(requests, []);
|
||||
assert.equal(currentState.currentPhoneActivation, null);
|
||||
assert.equal(currentState.reusablePhoneActivation.activationId, 'paid-reuse');
|
||||
assert.equal(currentState.freeReusablePhoneActivation.phoneNumber, '84943328460');
|
||||
});
|
||||
|
||||
test('phone verification helper infers manual free reuse country from phone prefix', async () => {
|
||||
const messages = [];
|
||||
const stops = [];
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsCountryId: 52,
|
||||
heroSmsCountryLabel: 'Thailand',
|
||||
freePhoneReuseEnabled: true,
|
||||
freePhoneReuseAutoEnabled: true,
|
||||
verificationResendCount: 0,
|
||||
currentPhoneActivation: null,
|
||||
freeReusablePhoneActivation: {
|
||||
phoneNumber: '84943328460',
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
successfulUses: 1,
|
||||
maxUses: 3,
|
||||
source: 'free-manual-reuse',
|
||||
manualOnly: true,
|
||||
},
|
||||
};
|
||||
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
throw new Error(`HeroSMS should not be called for manual-only free reuse: ${parsedUrl.searchParams.get('action')}`);
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getState: async () => ({ ...currentState }),
|
||||
requestStop: async (payload) => {
|
||||
stops.push(payload);
|
||||
},
|
||||
sendToContentScriptResilient: async (_source, message) => {
|
||||
messages.push(message);
|
||||
if (message.type === 'SUBMIT_PHONE_NUMBER') {
|
||||
return { phoneVerificationPage: true, url: 'https://auth.openai.com/phone-verification' };
|
||||
}
|
||||
throw new Error(`Unexpected content-script message: ${message.type}`);
|
||||
},
|
||||
setState: async (updates) => {
|
||||
currentState = { ...currentState, ...updates };
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
helpers.completePhoneVerificationFlow(1, {
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
}),
|
||||
(error) => {
|
||||
assert.match(error.message, /^PHONE_MANUAL_FREE_REUSE::/);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(stops.length, 1);
|
||||
assert.deepStrictEqual(messages.map((message) => message.type), ['SUBMIT_PHONE_NUMBER']);
|
||||
assert.equal(messages[0].payload.phoneNumber, '84943328460');
|
||||
assert.equal(messages[0].payload.countryId, 10);
|
||||
assert.equal(messages[0].payload.countryLabel, 'Vietnam');
|
||||
});
|
||||
|
||||
test('phone verification helper infers major HeroSMS countries from phone prefixes', async () => {
|
||||
const cases = [
|
||||
{ phoneNumber: '12025550123', countryId: 187, countryLabel: 'USA' },
|
||||
{ phoneNumber: '447911123456', countryId: 16, countryLabel: 'United Kingdom' },
|
||||
{ phoneNumber: '819012345678', countryId: 151, countryLabel: 'Japan' },
|
||||
{ phoneNumber: '4915112345678', countryId: 43, countryLabel: 'Germany' },
|
||||
{ phoneNumber: '33612345678', countryId: 73, countryLabel: 'France' },
|
||||
{ phoneNumber: '84943328460', countryId: 10, countryLabel: 'Vietnam' },
|
||||
{ phoneNumber: '66950003333', countryId: 52, countryLabel: 'Thailand' },
|
||||
{ phoneNumber: '628111111111', countryId: 6, countryLabel: 'Indonesia' },
|
||||
];
|
||||
|
||||
for (const testCase of cases) {
|
||||
const messages = [];
|
||||
let currentState = {
|
||||
heroSmsApiKey: 'demo-key',
|
||||
heroSmsCountryId: 52,
|
||||
heroSmsCountryLabel: 'Thailand',
|
||||
freePhoneReuseEnabled: true,
|
||||
freePhoneReuseAutoEnabled: true,
|
||||
verificationResendCount: 0,
|
||||
currentPhoneActivation: null,
|
||||
freeReusablePhoneActivation: {
|
||||
phoneNumber: testCase.phoneNumber,
|
||||
provider: 'hero-sms',
|
||||
serviceCode: 'dr',
|
||||
successfulUses: 1,
|
||||
maxUses: 3,
|
||||
source: 'free-manual-reuse',
|
||||
manualOnly: true,
|
||||
},
|
||||
};
|
||||
|
||||
const helpers = api.createPhoneVerificationHelpers({
|
||||
addLog: async () => {},
|
||||
ensureStep8SignupPageReady: async () => {},
|
||||
fetchImpl: async (url) => {
|
||||
const parsedUrl = new URL(url);
|
||||
throw new Error(`HeroSMS should not be called for manual-only free reuse: ${parsedUrl.searchParams.get('action')}`);
|
||||
},
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => defaultTimeoutMs,
|
||||
getState: async () => ({ ...currentState }),
|
||||
requestStop: async () => {},
|
||||
sendToContentScriptResilient: async (_source, message) => {
|
||||
messages.push(message);
|
||||
if (message.type === 'SUBMIT_PHONE_NUMBER') {
|
||||
return { phoneVerificationPage: true, url: 'https://auth.openai.com/phone-verification' };
|
||||
}
|
||||
throw new Error(`Unexpected content-script message: ${message.type}`);
|
||||
},
|
||||
setState: async (updates) => {
|
||||
currentState = { ...currentState, ...updates };
|
||||
},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
helpers.completePhoneVerificationFlow(1, {
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
url: 'https://auth.openai.com/add-phone',
|
||||
}),
|
||||
(error) => {
|
||||
assert.match(error.message, /^PHONE_MANUAL_FREE_REUSE::/);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(messages.length, 1, testCase.phoneNumber);
|
||||
assert.equal(messages[0].payload.phoneNumber, testCase.phoneNumber);
|
||||
assert.equal(messages[0].payload.countryId, testCase.countryId, testCase.phoneNumber);
|
||||
assert.equal(messages[0].payload.countryLabel, testCase.countryLabel, testCase.phoneNumber);
|
||||
}
|
||||
});
|
||||
|
||||
test('phone verification helper accepts HeroSMS WAIT_RETRY as free-reuse ready without using its suffix as code', async () => {
|
||||
const requests = [];
|
||||
const events = [];
|
||||
|
||||
Reference in New Issue
Block a user