fix: defer HeroSMS reuse confirmation until final cleanup

This commit is contained in:
zhangkun
2026-04-28 21:49:58 +08:00
parent 59663c4ca3
commit b8d8847ce5
5 changed files with 89 additions and 7 deletions
+1 -1
View File
@@ -6691,7 +6691,6 @@ async function handleStepData(step, payload) {
excludeLocalhostCallbacks: true, excludeLocalhostCallbacks: true,
}); });
} }
await finalizePhoneActivationAfterSuccessfulFlow(latestState);
await finalizeIcloudAliasAfterSuccessfulFlow(latestState); await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
const shouldClearCustomPoolEmail = String(latestState?.emailGenerator || '').trim().toLowerCase() === ( const shouldClearCustomPoolEmail = String(latestState?.emailGenerator || '').trim().toLowerCase() === (
typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string' typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
@@ -6701,6 +6700,7 @@ async function handleStepData(step, payload) {
if ((shouldUseCustomRegistrationEmail(latestState) || shouldClearCustomPoolEmail) && latestState.email) { if ((shouldUseCustomRegistrationEmail(latestState) || shouldClearCustomPoolEmail) && latestState.email) {
await setEmailStateSilently(null); await setEmailStateSilently(null);
} }
await finalizePhoneActivationAfterSuccessfulFlow(latestState);
break; break;
} }
} }
+1 -1
View File
@@ -203,10 +203,10 @@
excludeLocalhostCallbacks: true, excludeLocalhostCallbacks: true,
}); });
} }
await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
if (typeof finalizePhoneActivationAfterSuccessfulFlow === 'function') { if (typeof finalizePhoneActivationAfterSuccessfulFlow === 'function') {
await finalizePhoneActivationAfterSuccessfulFlow(latestState); await finalizePhoneActivationAfterSuccessfulFlow(latestState);
} }
await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
} }
async function handleStepData(step, payload) { async function handleStepData(step, payload) {
+5 -4
View File
@@ -248,18 +248,19 @@
} }
} }
function resolvePhoneConfig(state = {}) { function resolvePhoneConfig(state = {}, options = {}) {
const apiKey = normalizeApiKey(state.heroSmsApiKey); const apiKey = normalizeApiKey(state.heroSmsApiKey);
if (!apiKey) { if (!apiKey) {
throw new Error('HeroSMS API key is missing. Save it in the side panel before running the phone flow.'); throw new Error('HeroSMS API key is missing. Save it in the side panel before running the phone flow.');
} }
const requireMaxPrice = Boolean(options.requireMaxPrice);
const maxPrice = normalizeManualHeroSmsMaxPrice(state.heroSmsMaxPrice); const maxPrice = normalizeManualHeroSmsMaxPrice(state.heroSmsMaxPrice);
if (!maxPrice) { if (requireMaxPrice && !maxPrice) {
throw new Error('HeroSMS maxPrice is missing. Fill it in below the country selector before running the phone flow.'); throw new Error('HeroSMS maxPrice is missing. Fill it in below the country selector before running the phone flow.');
} }
return { return {
apiKey, apiKey,
maxPrice, ...(maxPrice ? { maxPrice } : {}),
baseUrl: normalizeUrl(state.heroSmsBaseUrl, DEFAULT_HERO_SMS_BASE_URL), baseUrl: normalizeUrl(state.heroSmsBaseUrl, DEFAULT_HERO_SMS_BASE_URL),
}; };
} }
@@ -324,7 +325,7 @@
} }
async function requestPhoneActivation(state = {}) { async function requestPhoneActivation(state = {}) {
const config = resolvePhoneConfig(state); const config = resolvePhoneConfig(state, { requireMaxPrice: true });
const countryConfig = resolveCountryConfig(state); const countryConfig = resolveCountryConfig(state);
const maxPrice = config.maxPrice; const maxPrice = config.maxPrice;
const buildFallbackActivation = (requestAction) => ({ const buildFallbackActivation = (requestAction) => ({
@@ -56,7 +56,7 @@ function createRouter(overrides = {}) {
finalizeStep3Completion: overrides.finalizeStep3Completion || (async (payload) => { finalizeStep3Completion: overrides.finalizeStep3Completion || (async (payload) => {
events.finalizePayloads.push(payload); events.finalizePayloads.push(payload);
}), }),
finalizeIcloudAliasAfterSuccessfulFlow: async () => {}, finalizeIcloudAliasAfterSuccessfulFlow: overrides.finalizeIcloudAliasAfterSuccessfulFlow || (async () => {}),
findHotmailAccount: async () => null, findHotmailAccount: async () => null,
flushCommand: async () => {}, flushCommand: async () => {},
getCurrentLuckmailPurchase: () => null, getCurrentLuckmailPurchase: () => null,
@@ -294,6 +294,40 @@ test('message router finalizes pending phone activation on platform verify succe
assert.deepStrictEqual(events.phoneFinalizations, [state]); assert.deepStrictEqual(events.phoneFinalizations, [state]);
}); });
test('message router does not finalize pending phone activation when icloud finalization fails', async () => {
const state = {
stepStatuses: { 10: 'pending' },
reusablePhoneActivation: {
activationId: '123456',
phoneNumber: '66959916439',
successfulUses: 0,
maxUses: 3,
},
pendingPhoneActivationConfirmation: {
activationId: '123456',
phoneNumber: '66959916439',
successfulUses: 0,
maxUses: 3,
},
};
const { router, events } = createRouter({
state,
getStepDefinitionForState: (step) => ({ id: step, key: step === 10 ? 'platform-verify' : '' }),
finalizeIcloudAliasAfterSuccessfulFlow: async () => {
throw new Error('icloud finalize failed');
},
});
await assert.rejects(
() => router.handleStepData(10, {
localhostUrl: 'http://localhost:1455/auth/callback?code=ok',
}),
/icloud finalize failed/
);
assert.deepStrictEqual(events.phoneFinalizations, []);
});
test('message router marks step 3 failed when post-submit finalize fails', async () => { test('message router marks step 3 failed when post-submit finalize fails', async () => {
const { router, events } = createRouter({ const { router, events } = createRouter({
finalizeStep3Completion: async () => { finalizeStep3Completion: async () => {
+47
View File
@@ -95,6 +95,53 @@ test('phone verification helper requires manual HeroSMS maxPrice', async () => {
); );
}); });
test('phone verification helper still clears existing activation when maxPrice is missing', async () => {
const requests = [];
let currentState = {
heroSmsApiKey: 'demo-key',
heroSmsMaxPrice: '',
currentPhoneActivation: {
activationId: '123456',
phoneNumber: '66959916439',
successfulUses: 0,
maxUses: 3,
},
reusablePhoneActivation: null,
pendingPhoneActivationConfirmation: null,
};
const helpers = api.createPhoneVerificationHelpers({
addLog: async () => {},
ensureStep8SignupPageReady: async () => {},
fetchImpl: async (url) => {
const parsedUrl = new URL(url);
requests.push(parsedUrl);
return {
ok: true,
text: async () => 'ACCESS_CANCEL',
};
},
getState: async () => currentState,
sendToContentScriptResilient: async () => ({}),
setState: async (updates) => {
currentState = { ...currentState, ...updates };
},
sleepWithStop: async () => {},
throwIfStopped: () => {},
});
await assert.rejects(
() => helpers.completePhoneVerificationFlow(1, { addPhonePage: true }),
/HeroSMS maxPrice is missing/i
);
assert.equal(requests.length, 1);
assert.equal(requests[0].searchParams.get('action'), 'setStatus');
assert.equal(requests[0].searchParams.get('id'), '123456');
assert.equal(requests[0].searchParams.get('status'), '8');
assert.equal(requests[0].searchParams.has('maxPrice'), false);
assert.equal(currentState.currentPhoneActivation, null);
});
test('phone verification helper retries with HeroSMS getNumberV2 when getNumber reports NO_NUMBERS', async () => { test('phone verification helper retries with HeroSMS getNumberV2 when getNumber reports NO_NUMBERS', async () => {
const requests = []; const requests = [];
const helpers = api.createPhoneVerificationHelpers({ const helpers = api.createPhoneVerificationHelpers({