fix: handle manual free phone reuse handoff

- 合并 PR #213 的核心改动:手动保存白嫖复用手机号时从本地运行态恢复 HeroSMS activation,并按号码前缀推断提交国家。
- 本地补充修复:无,本轮审查未发现需要额外修改的逻辑错误。
- 影响范围:Step 9 手机号复用保存/手动交接、HeroSMS 国家提交、相关回归测试。
This commit is contained in:
QLHazyCoder
2026-05-08 17:56:04 +08:00
committed by GitHub
4 changed files with 433 additions and 9 deletions
@@ -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 = {};
+232
View File
@@ -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 = [];