fix(flow): harden proxy phone and checkout recovery paths
This commit is contained in:
@@ -48,6 +48,7 @@
|
||||
const HERO_SMS_LAST_PRICE_COUNTRY_LABEL_KEY = 'heroSmsLastPriceCountryLabel';
|
||||
const HERO_SMS_LAST_PRICE_USER_LIMIT_KEY = 'heroSmsLastPriceUserLimit';
|
||||
const HERO_SMS_LAST_PRICE_AT_KEY = 'heroSmsLastPriceAt';
|
||||
const FIVE_SIM_RATE_LIMIT_ERROR_PREFIX = 'FIVE_SIM_RATE_LIMIT::';
|
||||
const PHONE_CODE_WAIT_SECONDS_MIN = 15;
|
||||
const PHONE_CODE_WAIT_SECONDS_MAX = 300;
|
||||
const PHONE_CODE_TIMEOUT_WINDOWS_MIN = 1;
|
||||
@@ -207,11 +208,23 @@
|
||||
}
|
||||
|
||||
function resolveFiveSimCountryCandidates(state = {}) {
|
||||
const codes = normalizeFiveSimCountryOrder(state?.fiveSimCountryOrder);
|
||||
let codes = normalizeFiveSimCountryOrder(state?.fiveSimCountryOrder);
|
||||
if (!codes.length) {
|
||||
const legacyPrimary = normalizeFiveSimCountryCode(state?.fiveSimCountryId, '');
|
||||
const legacyFallback = normalizeFiveSimCountryOrder(state?.fiveSimCountryFallback);
|
||||
codes = normalizeFiveSimCountryOrder([
|
||||
...(legacyPrimary ? [legacyPrimary] : []),
|
||||
...legacyFallback,
|
||||
]);
|
||||
}
|
||||
return codes.map((code) => ({
|
||||
code,
|
||||
id: code,
|
||||
label: code,
|
||||
label: (
|
||||
code === normalizeFiveSimCountryCode(state?.fiveSimCountryId, '')
|
||||
? normalizeCountryLabel(state?.fiveSimCountryLabel, code)
|
||||
: code
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -571,10 +584,12 @@
|
||||
(Array.isArray(payloads) ? payloads : [])
|
||||
.flatMap((payload) => collectHeroSmsPriceCandidatesIncludingZeroStock(payload, []))
|
||||
);
|
||||
const mergedCandidates = buildSortedUniquePriceCandidates([
|
||||
...inStockCandidates,
|
||||
...allCatalogCandidates,
|
||||
]);
|
||||
const mergedCandidates = inStockCandidates.length
|
||||
? buildSortedUniquePriceCandidates([
|
||||
...inStockCandidates,
|
||||
...allCatalogCandidates,
|
||||
])
|
||||
: [];
|
||||
const minCatalogPrice = allCatalogCandidates.length
|
||||
? allCatalogCandidates[0]
|
||||
: (mergedCandidates.length ? mergedCandidates[0] : null);
|
||||
@@ -1127,6 +1142,15 @@
|
||||
);
|
||||
}
|
||||
|
||||
function buildPhoneReplacementLimitError(maxNumberReplacementAttempts, reason = '') {
|
||||
const safeMax = Math.max(0, Math.floor(Number(maxNumberReplacementAttempts) || 0));
|
||||
const safeReason = String(reason || 'unknown').trim() || 'unknown';
|
||||
return new Error(
|
||||
`步骤 9:更换 ${safeMax} 次号码后手机号验证仍未成功。最后原因:${safeReason}. `
|
||||
+ `Step 9: phone verification did not succeed after ${safeMax} number replacements. Last reason: ${safeReason}.`
|
||||
);
|
||||
}
|
||||
|
||||
function sanitizePhoneCodeTimeoutError(error) {
|
||||
const message = String(error?.message || '');
|
||||
if (!message.startsWith(PHONE_CODE_TIMEOUT_ERROR_PREFIX)) {
|
||||
@@ -1357,12 +1381,16 @@
|
||||
if (!apiKey) {
|
||||
throw new Error('5sim API key is missing. Save it in the side panel before running the phone flow.');
|
||||
}
|
||||
const configuredMaxPrice = normalizeHeroSmsPriceLimit(state.fiveSimMaxPrice);
|
||||
return {
|
||||
provider,
|
||||
apiKey,
|
||||
baseUrl: normalizeUrl(state.fiveSimBaseUrl, DEFAULT_FIVE_SIM_BASE_URL).replace(/\/+$/, ''),
|
||||
operator: normalizeFiveSimCountryCode(state.fiveSimOperator, DEFAULT_FIVE_SIM_OPERATOR),
|
||||
product: normalizeFiveSimCountryCode(state.fiveSimProduct, DEFAULT_FIVE_SIM_PRODUCT),
|
||||
maxPriceLimit: configuredMaxPrice !== null
|
||||
? configuredMaxPrice
|
||||
: normalizeHeroSmsPriceLimit(state.heroSmsMaxPrice),
|
||||
countryCandidates: resolveFiveSimCountryCandidates(state),
|
||||
};
|
||||
}
|
||||
@@ -1468,9 +1496,15 @@
|
||||
}
|
||||
|
||||
function resolveHeroSmsStockState(payload = {}) {
|
||||
const physicalCount = Number(payload.physicalCount);
|
||||
if (Number.isFinite(physicalCount)) {
|
||||
return {
|
||||
hasStockField: true,
|
||||
stockCount: physicalCount,
|
||||
};
|
||||
}
|
||||
const stockCandidates = [
|
||||
payload.count,
|
||||
payload.physicalCount,
|
||||
payload.stock,
|
||||
payload.available,
|
||||
payload.quantity,
|
||||
@@ -1869,6 +1903,26 @@
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function collectFiveSimProductPriceCandidates(payload, product = DEFAULT_FIVE_SIM_PRODUCT, candidates = []) {
|
||||
if (Array.isArray(payload)) {
|
||||
payload.forEach((entry) => collectFiveSimProductPriceCandidates(entry, product, candidates));
|
||||
return candidates;
|
||||
}
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
return candidates;
|
||||
}
|
||||
const productPayload = payload[product] || payload[String(product || '').toLowerCase()];
|
||||
if (productPayload && typeof productPayload === 'object') {
|
||||
const price = Number(productPayload.Price ?? productPayload.price ?? productPayload.cost);
|
||||
const qty = Number(productPayload.Qty ?? productPayload.qty ?? productPayload.count);
|
||||
if (Number.isFinite(price) && price > 0 && (!Number.isFinite(qty) || qty > 0)) {
|
||||
candidates.push(Math.round(price * 10000) / 10000);
|
||||
}
|
||||
}
|
||||
Object.values(payload).forEach((entry) => collectFiveSimProductPriceCandidates(entry, product, candidates));
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function findLowestFiveSimPrice(payload, product = DEFAULT_FIVE_SIM_PRODUCT, countryCode = '') {
|
||||
const normalizedProduct = normalizeFiveSimCountryCode(product, DEFAULT_FIVE_SIM_PRODUCT);
|
||||
const normalizedCountryCode = normalizeFiveSimCountryCode(countryCode, '');
|
||||
@@ -1892,6 +1946,21 @@
|
||||
return /no\s+free\s+phones|no\s+phones\s+available|no\s+numbers\s+available/i.test(text);
|
||||
}
|
||||
|
||||
function isFiveSimRateLimitError(payloadOrMessage, status = 0) {
|
||||
if (Number(status) === 429) {
|
||||
return true;
|
||||
}
|
||||
const text = describeFiveSimPayload(payloadOrMessage);
|
||||
return /rate\s*limit|too\s*many\s*requests|request\s*limit|429/i.test(text);
|
||||
}
|
||||
|
||||
function buildFiveSimRateLimitError(details = []) {
|
||||
const suffix = Array.isArray(details) && details.length
|
||||
? `:${details.join(' | ')}。`
|
||||
: '。';
|
||||
return new Error(`${FIVE_SIM_RATE_LIMIT_ERROR_PREFIX}5sim 购买接口触发限流,请稍后再试${suffix}`);
|
||||
}
|
||||
|
||||
function isFiveSimTerminalError(payloadOrMessage, status = 0) {
|
||||
if (Number(status) === 401 || Number(status) === 403) {
|
||||
return true;
|
||||
@@ -1995,7 +2064,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
const maxPriceLimit = normalizeHeroSmsPriceLimit(state.heroSmsMaxPrice);
|
||||
const maxPriceLimit = config.maxPriceLimit === undefined
|
||||
? normalizeHeroSmsPriceLimit(state.heroSmsMaxPrice)
|
||||
: config.maxPriceLimit;
|
||||
const acquirePriority = normalizeHeroSmsAcquirePriority(state?.heroSmsAcquirePriority);
|
||||
const preferredPriceTier = normalizeHeroSmsPriceLimit(state?.heroSmsPreferredPrice);
|
||||
const countryPriceFloorByCountryCode = normalizeCountryPriceFloorMap(
|
||||
@@ -2007,6 +2078,7 @@
|
||||
const retryDelayMs = normalizePhoneActivationRetryDelayMs(state?.heroSmsActivationRetryDelayMs);
|
||||
|
||||
let finalNoNumbersByCountry = [];
|
||||
let finalRateLimitByCountry = [];
|
||||
let finalLastError = null;
|
||||
|
||||
for (let round = 1; round <= maxAcquireRounds; round += 1) {
|
||||
@@ -2017,6 +2089,7 @@
|
||||
);
|
||||
}
|
||||
const noNumbersByCountry = [];
|
||||
const rateLimitByCountry = [];
|
||||
const retryableNoNumberCountries = [];
|
||||
let lastError = null;
|
||||
|
||||
@@ -2068,7 +2141,20 @@
|
||||
const countryLabel = String(countryConfig.label || countryCode).trim() || countryCode;
|
||||
const countryPriceFloor = countryPriceFloorByCountryCode.get(countryCode) ?? null;
|
||||
try {
|
||||
const explicitFiveSimMaxPriceLimit = normalizeHeroSmsPriceLimit(state.fiveSimMaxPrice);
|
||||
let guestPricesPayload = null;
|
||||
let productPricesPayload = null;
|
||||
if (explicitFiveSimMaxPriceLimit !== null) {
|
||||
try {
|
||||
productPricesPayload = await fetchFiveSimPayload(
|
||||
config,
|
||||
`/guest/products/${countryCode}/${config.operator}`,
|
||||
'5sim guest products'
|
||||
);
|
||||
} catch (_) {
|
||||
productPricesPayload = null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
guestPricesPayload = await fetchFiveSimPayload(
|
||||
config,
|
||||
@@ -2086,16 +2172,23 @@
|
||||
}
|
||||
|
||||
const rawPriceCandidates = buildSortedUniquePriceCandidates(
|
||||
collectFiveSimPriceCandidates(
|
||||
(
|
||||
guestPricesPayload
|
||||
&& typeof guestPricesPayload === 'object'
|
||||
&& !Array.isArray(guestPricesPayload)
|
||||
? (guestPricesPayload?.[config.product]?.[countryCode] || guestPricesPayload?.[countryCode] || guestPricesPayload)
|
||||
: guestPricesPayload
|
||||
[
|
||||
...(
|
||||
normalizeHeroSmsPriceLimit(state.fiveSimMaxPrice) !== null
|
||||
? collectFiveSimProductPriceCandidates(productPricesPayload, config.product, [])
|
||||
: []
|
||||
),
|
||||
[]
|
||||
)
|
||||
...collectFiveSimPriceCandidates(
|
||||
(
|
||||
guestPricesPayload
|
||||
&& typeof guestPricesPayload === 'object'
|
||||
&& !Array.isArray(guestPricesPayload)
|
||||
? (guestPricesPayload?.[config.product]?.[countryCode] || guestPricesPayload?.[countryCode] || guestPricesPayload)
|
||||
: guestPricesPayload
|
||||
),
|
||||
[]
|
||||
),
|
||||
]
|
||||
);
|
||||
const boundedPriceCandidates = maxPriceLimit === null
|
||||
? rawPriceCandidates
|
||||
@@ -2106,7 +2199,14 @@
|
||||
preferredPriceTier
|
||||
);
|
||||
const orderedPrices = orderedPricesFromCatalog.length
|
||||
? orderedPricesFromCatalog
|
||||
? (
|
||||
explicitFiveSimMaxPriceLimit !== null
|
||||
? [
|
||||
explicitFiveSimMaxPriceLimit,
|
||||
...orderedPricesFromCatalog.filter((price) => Number(price) !== Number(explicitFiveSimMaxPriceLimit)),
|
||||
]
|
||||
: orderedPricesFromCatalog
|
||||
)
|
||||
: (maxPriceLimit !== null ? [maxPriceLimit] : [null]);
|
||||
const floorFilteredPrices = filterPriceCandidatesAboveFloor(orderedPrices, countryPriceFloor);
|
||||
const hasCountryPriceFloor = (
|
||||
@@ -2181,6 +2281,10 @@
|
||||
break;
|
||||
}
|
||||
const payloadText = describeFiveSimPayload(payload);
|
||||
if (isFiveSimRateLimitError(payload)) {
|
||||
countryNoNumbersText = payloadText || countryNoNumbersText || 'rate limit';
|
||||
continue;
|
||||
}
|
||||
if (isFiveSimNoNumbersError(payload)) {
|
||||
countryNoNumbersText = payloadText || countryNoNumbersText || 'no free phones';
|
||||
continue;
|
||||
@@ -2190,6 +2294,10 @@
|
||||
}
|
||||
lastError = new Error(`5sim buy activation failed: ${payloadText || 'empty response'}`);
|
||||
} catch (error) {
|
||||
if (isFiveSimRateLimitError(error?.payload || error?.message, error?.status)) {
|
||||
countryNoNumbersText = describeFiveSimPayload(error?.payload || error?.message) || countryNoNumbersText || 'rate limit';
|
||||
continue;
|
||||
}
|
||||
if (isFiveSimTerminalError(error?.payload || error?.message, error?.status)) {
|
||||
throw new Error(`5sim buy activation failed: ${describeFiveSimPayload(error?.payload || error?.message) || 'unknown terminal error'}`);
|
||||
}
|
||||
@@ -2210,12 +2318,18 @@
|
||||
noNumbersByCountry.push(
|
||||
`${countryLabel}: no numbers within maxPrice=${maxPriceLimit}; lowest listed=${lowestPrice}`
|
||||
);
|
||||
} else if (isFiveSimRateLimitError(countryNoNumbersText)) {
|
||||
rateLimitByCountry.push(`${countryLabel}: ${countryNoNumbersText || 'rate limit'}`);
|
||||
} else {
|
||||
noNumbersByCountry.push(`${countryLabel}: ${countryNoNumbersText || 'no free phones'}`);
|
||||
retryableNoNumberCountries.push(countryLabel);
|
||||
}
|
||||
continue;
|
||||
} catch (error) {
|
||||
if (isFiveSimRateLimitError(error?.payload || error?.message, error?.status)) {
|
||||
rateLimitByCountry.push(`${countryLabel}: ${describeFiveSimPayload(error?.payload || error?.message) || 'rate limit'}`);
|
||||
continue;
|
||||
}
|
||||
if (isFiveSimTerminalError(error?.payload || error?.message, error?.status)) {
|
||||
throw new Error(`5sim buy activation failed: ${describeFiveSimPayload(error?.payload || error?.message) || 'unknown terminal error'}`);
|
||||
}
|
||||
@@ -2236,8 +2350,13 @@
|
||||
}
|
||||
|
||||
finalNoNumbersByCountry = noNumbersByCountry;
|
||||
finalRateLimitByCountry = rateLimitByCountry;
|
||||
finalLastError = lastError;
|
||||
|
||||
if (rateLimitByCountry.length) {
|
||||
throw buildFiveSimRateLimitError(rateLimitByCountry);
|
||||
}
|
||||
|
||||
if (
|
||||
noNumbersByCountry.length
|
||||
&& round < maxAcquireRounds
|
||||
@@ -2259,6 +2378,9 @@
|
||||
`5sim no numbers available across ${countryCandidates.length} country candidate(s): ${finalNoNumbersByCountry.join(' | ')}.`
|
||||
);
|
||||
}
|
||||
if (finalRateLimitByCountry.length) {
|
||||
throw buildFiveSimRateLimitError(finalRateLimitByCountry);
|
||||
}
|
||||
if (finalLastError) {
|
||||
throw finalLastError;
|
||||
}
|
||||
@@ -2714,6 +2836,10 @@
|
||||
`步骤 9:HeroSMS 正在获取手机号(第 ${round}/${maxAcquireRounds} 轮)...`,
|
||||
'info'
|
||||
);
|
||||
await addLog(
|
||||
`步骤 9:HeroSMS 正在获取手机号(第 ${round}/${maxAcquireRounds} 轮)...`,
|
||||
'info'
|
||||
);
|
||||
}
|
||||
|
||||
const countryAttempts = countryCandidates.map((countryConfig, index) => ({
|
||||
@@ -2935,6 +3061,10 @@
|
||||
`步骤 9:HeroSMS 暂无可用号码(第 ${round}/${maxAcquireRounds} 轮);${Math.ceil(retryDelayMs / 1000)} 秒后重试。国家:${retryableNoNumberCountries.join(', ')}。`,
|
||||
'warn'
|
||||
);
|
||||
await addLog(
|
||||
`步骤 9:HeroSMS 暂无可用号码(第 ${round}/${maxAcquireRounds} 轮),${Math.ceil(retryDelayMs / 1000)} 秒后重试。国家:${retryableNoNumberCountries.join(', ')}。`,
|
||||
'warn'
|
||||
);
|
||||
await sleepWithStop(retryDelayMs);
|
||||
continue;
|
||||
}
|
||||
@@ -2945,6 +3075,7 @@
|
||||
if (finalNoNumbersByCountry.length) {
|
||||
throw new Error(
|
||||
`HeroSMS 已尝试 ${countryCandidates.length} 个候选国家,均无可用号码:${finalNoNumbersByCountry.join(' | ')}。`
|
||||
+ ` HeroSMS no numbers available across ${countryCandidates.length} country candidate(s): ${finalNoNumbersByCountry.join(' | ')}.`
|
||||
);
|
||||
}
|
||||
if (finalLastError) {
|
||||
@@ -3606,7 +3737,9 @@
|
||||
const successfulUses = normalizedActivation.successfulUses + 1;
|
||||
const nextReusableActivation = {
|
||||
...normalizedActivation,
|
||||
successfulUses,
|
||||
successfulUses: normalizedActivation.provider === PHONE_SMS_PROVIDER_5SIM
|
||||
? 1
|
||||
: successfulUses,
|
||||
};
|
||||
await upsertReusableActivationPool(nextReusableActivation, { state });
|
||||
if (!normalizeHeroSmsReuseEnabled(state?.heroSmsReuseEnabled)) {
|
||||
@@ -3907,10 +4040,147 @@
|
||||
countryPriceFloorByKey.delete(countryKey);
|
||||
};
|
||||
|
||||
const getBlockedCountryIds = () => Array.from(countrySmsFailureCounts.entries())
|
||||
.filter(([, count]) => Number(count) >= PHONE_SMS_FAILURE_SKIP_THRESHOLD)
|
||||
.map(([countryId]) => countryId)
|
||||
.filter(Boolean);
|
||||
const getBlockedCountryIds = () => {
|
||||
const activeProvider = normalizePhoneSmsProvider(
|
||||
state?.phoneSmsProvider || activation?.provider || DEFAULT_PHONE_SMS_PROVIDER
|
||||
);
|
||||
return Array.from(countrySmsFailureCounts.entries())
|
||||
.filter(([, count]) => Number(count) >= PHONE_SMS_FAILURE_SKIP_THRESHOLD)
|
||||
.map(([countryKey]) => splitCountryFailureKey(countryKey, activeProvider))
|
||||
.filter((entry) => entry.provider === activeProvider)
|
||||
.map((entry) => String(entry.countryKey || '').trim())
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
const getCountryPriceFloorById = () => {
|
||||
const activeProvider = normalizePhoneSmsProvider(
|
||||
state?.phoneSmsProvider || activation?.provider || DEFAULT_PHONE_SMS_PROVIDER
|
||||
);
|
||||
const floorById = {};
|
||||
countryPriceFloorByKey.forEach((price, compoundCountryKey) => {
|
||||
const numeric = normalizeHeroSmsPrice(price);
|
||||
if (numeric === null || numeric <= 0) {
|
||||
return;
|
||||
}
|
||||
const parsed = splitCountryFailureKey(compoundCountryKey, activeProvider);
|
||||
if (parsed.provider !== activeProvider) {
|
||||
return;
|
||||
}
|
||||
const keyPart = String(parsed.countryKey || '').trim();
|
||||
if (!keyPart) {
|
||||
return;
|
||||
}
|
||||
floorById[keyPart] = Math.round(numeric * 10000) / 10000;
|
||||
});
|
||||
return floorById;
|
||||
};
|
||||
|
||||
const setCountryPriceFloorFromActivation = async (activationCandidate, reason = '') => {
|
||||
const normalizedActivation = normalizeActivation(activationCandidate);
|
||||
if (!normalizedActivation) {
|
||||
return;
|
||||
}
|
||||
const countryKey = normalizeCountryFailureKey(
|
||||
normalizedActivation.countryId,
|
||||
normalizedActivation.provider
|
||||
);
|
||||
if (!countryKey) {
|
||||
return;
|
||||
}
|
||||
const floorPrice = normalizeHeroSmsPrice(
|
||||
normalizedActivation.price
|
||||
?? normalizedActivation.maxPrice
|
||||
?? normalizedActivation.selectedPrice
|
||||
?? getActivationAcquiredPriceHint(normalizedActivation)
|
||||
);
|
||||
if (floorPrice === null || floorPrice <= 0) {
|
||||
return;
|
||||
}
|
||||
const currentFloor = normalizeHeroSmsPrice(countryPriceFloorByKey.get(countryKey));
|
||||
if (currentFloor !== null && currentFloor >= floorPrice) {
|
||||
return;
|
||||
}
|
||||
const normalizedFloor = Math.round(floorPrice * 10000) / 10000;
|
||||
countryPriceFloorByKey.set(countryKey, normalizedFloor);
|
||||
const countryLabel = resolveCountryLabelByFailureKey(countryKey, normalizedActivation.provider);
|
||||
await addLog(
|
||||
`Step 9: ${countryLabel} will try a higher price tier (> ${normalizedFloor}) due to ${reason || 'sms timeout'}.`,
|
||||
'warn'
|
||||
);
|
||||
};
|
||||
|
||||
const isPreferredActivation = (activationCandidate, stateSnapshot = {}) => (
|
||||
isSameActivation(
|
||||
stateSnapshot?.[PREFERRED_PHONE_ACTIVATION_STATE_KEY],
|
||||
activationCandidate
|
||||
)
|
||||
);
|
||||
|
||||
const markPreferredActivationExhausted = async (reason = '') => {
|
||||
if (preferredActivationExhausted || !activation || !isPreferredActivation(activation, state)) {
|
||||
return;
|
||||
}
|
||||
preferredActivationExhausted = true;
|
||||
await addLog(
|
||||
`Step 9: preferred number ${activation.phoneNumber} failed (${reason || 'unknown reason'}), falling back to a new number.`,
|
||||
'warn'
|
||||
);
|
||||
};
|
||||
|
||||
const rotateActivationAfterAddPhoneFailure = async (failureReason, failureCode, submitState = {}) => {
|
||||
await markPreferredActivationExhausted(failureCode || failureReason);
|
||||
usedNumberReplacementAttempts += 1;
|
||||
if (usedNumberReplacementAttempts > maxNumberReplacementAttempts) {
|
||||
throw buildPhoneReplacementLimitError(maxNumberReplacementAttempts, failureCode || 'add_phone_rejected');
|
||||
}
|
||||
await addLog(
|
||||
`Step 9: replacing number after add-phone failure (${failureReason}) (${usedNumberReplacementAttempts}/${maxNumberReplacementAttempts}).`,
|
||||
'warn'
|
||||
);
|
||||
if (shouldCancelActivation && activation) {
|
||||
await cancelPhoneActivation(state, activation);
|
||||
}
|
||||
await clearCurrentActivation();
|
||||
activation = null;
|
||||
shouldCancelActivation = false;
|
||||
preferReuseExistingActivationOnAddPhone = false;
|
||||
addPhoneReentryWithSameActivation = 0;
|
||||
let addPhoneSnapshot = {
|
||||
...pageState,
|
||||
...submitState,
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
};
|
||||
try {
|
||||
const returned = await returnToAddPhone(tabId);
|
||||
addPhoneSnapshot = {
|
||||
...addPhoneSnapshot,
|
||||
...returned,
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
};
|
||||
} catch (returnError) {
|
||||
await addLog(
|
||||
`Step 9: failed to return to add-phone page after rejection, will continue with best-effort state. ${returnError.message}`,
|
||||
'warn'
|
||||
);
|
||||
}
|
||||
try {
|
||||
const verified = await ensureAddPhonePageBeforeSubmit('after add-phone rejection');
|
||||
addPhoneSnapshot = {
|
||||
...addPhoneSnapshot,
|
||||
...verified,
|
||||
addPhonePage: true,
|
||||
phoneVerificationPage: false,
|
||||
};
|
||||
} catch (verifyError) {
|
||||
await addLog(
|
||||
`Step 9: failed to verify add-phone state after rejection. ${verifyError.message}`,
|
||||
'warn'
|
||||
);
|
||||
}
|
||||
pageState = addPhoneSnapshot;
|
||||
};
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
@@ -3941,9 +4211,7 @@
|
||||
if (addPhoneReentryWithSameActivation > 1) {
|
||||
usedNumberReplacementAttempts += 1;
|
||||
if (usedNumberReplacementAttempts > maxNumberReplacementAttempts) {
|
||||
throw new Error(
|
||||
`步骤 9:更换 ${maxNumberReplacementAttempts} 次号码后手机号验证仍未成功。最后原因:${formatStep9Reason('returned_to_add_phone_loop')}。`
|
||||
);
|
||||
throw buildPhoneReplacementLimitError(maxNumberReplacementAttempts, 'returned_to_add_phone_loop');
|
||||
}
|
||||
await addLog(
|
||||
`步骤 9:当前号码 ${activation.phoneNumber} 反复返回添加手机号页,正在更换号码(${usedNumberReplacementAttempts}/${maxNumberReplacementAttempts})。`,
|
||||
@@ -4191,9 +4459,7 @@
|
||||
|
||||
usedNumberReplacementAttempts += 1;
|
||||
if (usedNumberReplacementAttempts > maxNumberReplacementAttempts) {
|
||||
throw new Error(
|
||||
`步骤 9:更换 ${maxNumberReplacementAttempts} 次号码后手机号验证仍未成功。最后原因:${formatStep9Reason(replaceReason)}。`
|
||||
);
|
||||
throw buildPhoneReplacementLimitError(maxNumberReplacementAttempts, replaceReason || 'unknown');
|
||||
}
|
||||
|
||||
if (shouldCancelActivation && activation) {
|
||||
|
||||
Reference in New Issue
Block a user