Merge commit 'c98715a4bc17b249639a7844ad8561aec261da87' into dev
# Conflicts: # sidepanel/sidepanel.js
This commit is contained in:
+421
-12
@@ -1,6 +1,6 @@
|
||||
// background.js — Service Worker: orchestration, state, tab management, message routing
|
||||
|
||||
importScripts('data/names.js', 'hotmail-utils.js', 'content/activation-utils.js');
|
||||
importScripts('data/names.js', 'hotmail-utils.js', 'cloudflare-temp-email-utils.js', 'content/activation-utils.js');
|
||||
|
||||
const {
|
||||
buildHotmailMailApiLatestUrl,
|
||||
@@ -17,6 +17,17 @@ const {
|
||||
pickVerificationMessageWithTimeFallback,
|
||||
shouldClearHotmailCurrentSelection,
|
||||
} = self.HotmailUtils;
|
||||
const {
|
||||
DEFAULT_MAIL_PAGE_SIZE: CLOUDFLARE_TEMP_EMAIL_DEFAULT_PAGE_SIZE,
|
||||
buildCloudflareTempEmailHeaders,
|
||||
getCloudflareTempEmailAddressFromResponse,
|
||||
joinCloudflareTempEmailUrl,
|
||||
normalizeCloudflareTempEmailAddress,
|
||||
normalizeCloudflareTempEmailBaseUrl,
|
||||
normalizeCloudflareTempEmailDomain,
|
||||
normalizeCloudflareTempEmailDomains,
|
||||
normalizeCloudflareTempEmailMailApiMessages,
|
||||
} = self.CloudflareTempEmailUtils;
|
||||
const {
|
||||
isRecoverableStep9AuthFailure,
|
||||
} = self.MultiPageActivationUtils;
|
||||
@@ -24,6 +35,8 @@ const {
|
||||
const LOG_PREFIX = '[MultiPage:bg]';
|
||||
const DUCK_AUTOFILL_URL = 'https://duckduckgo.com/email/settings/autofill';
|
||||
const HOTMAIL_PROVIDER = 'hotmail-api';
|
||||
const CLOUDFLARE_TEMP_EMAIL_PROVIDER = 'cloudflare-temp-email';
|
||||
const CLOUDFLARE_TEMP_EMAIL_GENERATOR = 'cloudflare-temp-email';
|
||||
const HOTMAIL_MAILBOXES = ['INBOX', 'Junk'];
|
||||
const STOP_ERROR_MESSAGE = '流程已被用户停止。';
|
||||
const HUMAN_STEP_DELAY_MIN = 700;
|
||||
@@ -85,6 +98,11 @@ const PERSISTED_SETTING_DEFAULTS = {
|
||||
hotmailLocalBaseUrl: DEFAULT_HOTMAIL_LOCAL_BASE_URL,
|
||||
cloudflareDomain: '',
|
||||
cloudflareDomains: [],
|
||||
cloudflareTempEmailBaseUrl: '',
|
||||
cloudflareTempEmailAdminAuth: '',
|
||||
cloudflareTempEmailCustomAuth: '',
|
||||
cloudflareTempEmailDomain: '',
|
||||
cloudflareTempEmailDomains: [],
|
||||
hotmailAccounts: [],
|
||||
};
|
||||
|
||||
@@ -222,9 +240,8 @@ function normalizeEmailGenerator(value = '') {
|
||||
if (normalized === 'custom' || normalized === 'manual') {
|
||||
return 'custom';
|
||||
}
|
||||
if (normalized === 'cloudflare') {
|
||||
return 'cloudflare';
|
||||
}
|
||||
if (normalized === 'cloudflare') return 'cloudflare';
|
||||
if (normalized === CLOUDFLARE_TEMP_EMAIL_GENERATOR) return CLOUDFLARE_TEMP_EMAIL_GENERATOR;
|
||||
return 'duck';
|
||||
}
|
||||
|
||||
@@ -237,6 +254,7 @@ function normalizeMailProvider(value = '') {
|
||||
switch (normalized) {
|
||||
case 'custom':
|
||||
case HOTMAIL_PROVIDER:
|
||||
case CLOUDFLARE_TEMP_EMAIL_PROVIDER:
|
||||
case '163':
|
||||
case '163-vip':
|
||||
case 'qq':
|
||||
@@ -340,6 +358,16 @@ function getHotmailServiceSettings(state = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function getCloudflareTempEmailConfig(state = {}) {
|
||||
return {
|
||||
baseUrl: normalizeCloudflareTempEmailBaseUrl(state.cloudflareTempEmailBaseUrl),
|
||||
adminAuth: String(state.cloudflareTempEmailAdminAuth || ''),
|
||||
customAuth: String(state.cloudflareTempEmailCustomAuth || ''),
|
||||
domain: normalizeCloudflareTempEmailDomain(state.cloudflareTempEmailDomain),
|
||||
domains: normalizeCloudflareTempEmailDomains(state.cloudflareTempEmailDomains),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePersistentSettingValue(key, value) {
|
||||
switch (key) {
|
||||
case 'panelMode':
|
||||
@@ -391,6 +419,15 @@ function normalizePersistentSettingValue(key, value) {
|
||||
return normalizeCloudflareDomain(value);
|
||||
case 'cloudflareDomains':
|
||||
return normalizeCloudflareDomains(value);
|
||||
case 'cloudflareTempEmailBaseUrl':
|
||||
return normalizeCloudflareTempEmailBaseUrl(value);
|
||||
case 'cloudflareTempEmailAdminAuth':
|
||||
case 'cloudflareTempEmailCustomAuth':
|
||||
return String(value || '');
|
||||
case 'cloudflareTempEmailDomain':
|
||||
return normalizeCloudflareTempEmailDomain(value);
|
||||
case 'cloudflareTempEmailDomains':
|
||||
return normalizeCloudflareTempEmailDomains(value);
|
||||
case 'hotmailAccounts':
|
||||
return normalizeHotmailAccounts(value);
|
||||
default:
|
||||
@@ -434,6 +471,13 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
|
||||
}
|
||||
payload.cloudflareDomains = domains;
|
||||
}
|
||||
if (payload.cloudflareTempEmailDomains) {
|
||||
const domains = normalizeCloudflareTempEmailDomains(payload.cloudflareTempEmailDomains);
|
||||
if (payload.cloudflareTempEmailDomain && !domains.includes(payload.cloudflareTempEmailDomain)) {
|
||||
domains.unshift(payload.cloudflareTempEmailDomain);
|
||||
}
|
||||
payload.cloudflareTempEmailDomains = domains;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
@@ -1327,6 +1371,120 @@ function buildGeneratedAliasEmail(state) {
|
||||
throw new Error(`未支持的别名邮箱类型:${provider}`);
|
||||
}
|
||||
|
||||
function summarizeCloudflareTempEmailMessagesForLog(messages) {
|
||||
return (messages || [])
|
||||
.slice()
|
||||
.sort((left, right) => {
|
||||
const leftTime = Date.parse(left.receivedDateTime || '') || 0;
|
||||
const rightTime = Date.parse(right.receivedDateTime || '') || 0;
|
||||
return rightTime - leftTime;
|
||||
})
|
||||
.slice(0, 3)
|
||||
.map((message) => {
|
||||
const receivedAt = message?.receivedDateTime || '未知时间';
|
||||
const sender = message?.from?.emailAddress?.address || '未知发件人';
|
||||
const subject = message?.subject || '(无主题)';
|
||||
const preview = String(message?.bodyPreview || '').replace(/\s+/g, ' ').trim().slice(0, 80);
|
||||
const address = message?.address || '未知地址';
|
||||
return `[${address}] ${receivedAt} | ${sender} | ${subject} | ${preview}`;
|
||||
})
|
||||
.join(' || ');
|
||||
}
|
||||
|
||||
async function deleteCloudflareTempEmailMail(config, mailId) {
|
||||
const normalizedMailId = String(mailId || '').trim();
|
||||
if (!normalizedMailId) return false;
|
||||
|
||||
await requestCloudflareTempEmailJson(config, `/admin/mails/${encodeURIComponent(normalizedMailId)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
async function listCloudflareTempEmailMessages(state, options = {}) {
|
||||
const config = ensureCloudflareTempEmailConfig(state, { requireAdminAuth: true });
|
||||
const address = normalizeCloudflareTempEmailAddress(options.address);
|
||||
const payload = await requestCloudflareTempEmailJson(config, '/admin/mails', {
|
||||
method: 'GET',
|
||||
searchParams: {
|
||||
limit: Number(options.limit) || CLOUDFLARE_TEMP_EMAIL_DEFAULT_PAGE_SIZE,
|
||||
offset: Number(options.offset) || 0,
|
||||
address,
|
||||
},
|
||||
});
|
||||
|
||||
const messages = normalizeCloudflareTempEmailMailApiMessages(payload).filter((message) => {
|
||||
if (!address) return true;
|
||||
return !message.address || normalizeCloudflareTempEmailAddress(message.address) === address;
|
||||
});
|
||||
|
||||
return { config, messages };
|
||||
}
|
||||
|
||||
async function pollCloudflareTempEmailVerificationCode(step, state, pollPayload = {}) {
|
||||
const targetEmail = normalizeCloudflareTempEmailAddress(pollPayload.targetEmail || state.email);
|
||||
if (!targetEmail) {
|
||||
throw new Error('Cloudflare Temp Email 轮询前缺少目标邮箱地址。');
|
||||
}
|
||||
|
||||
await addLog(`步骤 ${step}:正在轮询 Cloudflare Temp Email 邮件(${targetEmail})...`, 'info');
|
||||
const maxAttempts = Number(pollPayload.maxAttempts) || 5;
|
||||
const intervalMs = Number(pollPayload.intervalMs) || 3000;
|
||||
let lastError = null;
|
||||
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
throwIfStopped();
|
||||
try {
|
||||
const { config, messages } = await listCloudflareTempEmailMessages(state, {
|
||||
address: targetEmail,
|
||||
limit: pollPayload.limit || CLOUDFLARE_TEMP_EMAIL_DEFAULT_PAGE_SIZE,
|
||||
offset: pollPayload.offset || 0,
|
||||
});
|
||||
const matchResult = pickVerificationMessageWithTimeFallback(messages, {
|
||||
afterTimestamp: pollPayload.filterAfterTimestamp || 0,
|
||||
senderFilters: pollPayload.senderFilters || [],
|
||||
subjectFilters: pollPayload.subjectFilters || [],
|
||||
excludeCodes: pollPayload.excludeCodes || [],
|
||||
});
|
||||
const match = matchResult.match;
|
||||
|
||||
if (match?.code) {
|
||||
if (matchResult.usedRelaxedFilters) {
|
||||
const fallbackLabel = matchResult.usedTimeFallback ? '宽松匹配 + 时间回退' : '宽松匹配';
|
||||
await addLog(`步骤 ${step}:严格规则未命中,已改用 ${fallbackLabel} 并命中 Cloudflare Temp Email 验证码。`, 'warn');
|
||||
}
|
||||
try {
|
||||
await deleteCloudflareTempEmailMail(config, match.message?.id);
|
||||
} catch (err) {
|
||||
await addLog(`步骤 ${step}:删除 Cloudflare Temp Email 邮件失败:${err.message}`, 'warn');
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
code: match.code,
|
||||
emailTimestamp: match.receivedAt || Date.now(),
|
||||
mailId: match.message?.id || '',
|
||||
};
|
||||
}
|
||||
|
||||
lastError = new Error(`步骤 ${step}:暂未在 Cloudflare Temp Email 中找到匹配验证码(${attempt}/${maxAttempts})。`);
|
||||
await addLog(lastError.message, attempt === maxAttempts ? 'warn' : 'info');
|
||||
const sample = summarizeCloudflareTempEmailMessagesForLog(messages);
|
||||
if (sample) {
|
||||
await addLog(`步骤 ${step}:最近邮件样本:${sample}`, 'info');
|
||||
}
|
||||
} catch (err) {
|
||||
lastError = err;
|
||||
await addLog(`步骤 ${step}:Cloudflare Temp Email 轮询失败:${err.message}`, 'warn');
|
||||
}
|
||||
|
||||
if (attempt < maxAttempts) {
|
||||
await sleepWithStop(intervalMs);
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError || new Error(`步骤 ${step}:未在 Cloudflare Temp Email 中找到新的匹配验证码。`);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Tab Registry
|
||||
// ============================================================
|
||||
@@ -1557,11 +1715,14 @@ function buildLocalhostCleanupPrefix(rawUrl) {
|
||||
async function closeTabsByUrlPrefix(prefix, options = {}) {
|
||||
if (!prefix) return 0;
|
||||
|
||||
const { excludeTabIds = [] } = options;
|
||||
const { excludeTabIds = [], excludeUrls = [], excludeLocalhostCallbacks = false } = options;
|
||||
const excluded = new Set(excludeTabIds.filter(id => Number.isInteger(id)));
|
||||
const excludedUrls = new Set((Array.isArray(excludeUrls) ? excludeUrls : []).filter(Boolean));
|
||||
const tabs = await chrome.tabs.query({});
|
||||
const matchedIds = tabs
|
||||
.filter((tab) => Number.isInteger(tab.id) && !excluded.has(tab.id))
|
||||
.filter((tab) => typeof tab.url === 'string' && !excludedUrls.has(tab.url))
|
||||
.filter((tab) => !(excludeLocalhostCallbacks && isLocalhostOAuthCallbackUrl(tab.url)))
|
||||
.filter((tab) => typeof tab.url === 'string' && tab.url.startsWith(prefix))
|
||||
.map((tab) => tab.id);
|
||||
|
||||
@@ -2149,6 +2310,7 @@ function getSourceLabel(source) {
|
||||
'inbucket-mail': 'Inbucket 邮箱',
|
||||
'duck-mail': 'Duck 邮箱',
|
||||
'hotmail-api': 'Hotmail(远程/本地)',
|
||||
'cloudflare-temp-email': 'Cloudflare Temp Email',
|
||||
};
|
||||
return labels[source] || source || '未知来源';
|
||||
}
|
||||
@@ -3166,7 +3328,10 @@ async function handleStepData(step, payload) {
|
||||
}
|
||||
const localhostPrefix = buildLocalhostCleanupPrefix(payload.localhostUrl);
|
||||
if (localhostPrefix) {
|
||||
await closeTabsByUrlPrefix(localhostPrefix);
|
||||
await closeTabsByUrlPrefix(localhostPrefix, {
|
||||
excludeUrls: [payload.localhostUrl],
|
||||
excludeLocalhostCallbacks: true,
|
||||
});
|
||||
}
|
||||
if (shouldUseCustomRegistrationEmail(latestState) && latestState.email) {
|
||||
await setEmailStateSilently(null);
|
||||
@@ -3459,7 +3624,9 @@ function getEmailGeneratorLabel(generator) {
|
||||
if (generator === 'custom') {
|
||||
return '自定义邮箱';
|
||||
}
|
||||
return generator === 'cloudflare' ? 'Cloudflare 邮箱' : 'Duck 邮箱';
|
||||
if (generator === 'cloudflare') return 'Cloudflare 邮箱';
|
||||
if (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR) return 'Cloudflare Temp Email';
|
||||
return 'Duck 邮箱';
|
||||
}
|
||||
|
||||
function generateCloudflareAliasLocalPart() {
|
||||
@@ -3499,6 +3666,107 @@ async function fetchCloudflareEmail(state, options = {}) {
|
||||
return aliasEmail;
|
||||
}
|
||||
|
||||
function ensureCloudflareTempEmailConfig(state, options = {}) {
|
||||
const {
|
||||
requireAdminAuth = false,
|
||||
requireDomain = false,
|
||||
} = options;
|
||||
const config = getCloudflareTempEmailConfig(state);
|
||||
if (!config.baseUrl) {
|
||||
throw new Error('Cloudflare Temp Email 服务地址为空或格式无效。');
|
||||
}
|
||||
if (requireAdminAuth && !config.adminAuth) {
|
||||
throw new Error('Cloudflare Temp Email 缺少 Admin Auth。');
|
||||
}
|
||||
if (requireDomain && !config.domain) {
|
||||
throw new Error('Cloudflare Temp Email 域名为空或格式无效。');
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
async function requestCloudflareTempEmailJson(config, path, options = {}) {
|
||||
const {
|
||||
method = 'GET',
|
||||
payload,
|
||||
searchParams,
|
||||
timeoutMs = 20000,
|
||||
} = options;
|
||||
|
||||
const url = new URL(joinCloudflareTempEmailUrl(config.baseUrl, path));
|
||||
if (searchParams && typeof searchParams === 'object') {
|
||||
for (const [key, value] of Object.entries(searchParams)) {
|
||||
if (value === undefined || value === null || value === '') continue;
|
||||
url.searchParams.set(key, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs);
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(url.toString(), {
|
||||
method,
|
||||
headers: buildCloudflareTempEmailHeaders(config, {
|
||||
json: payload !== undefined,
|
||||
}),
|
||||
body: payload !== undefined ? JSON.stringify(payload) : undefined,
|
||||
signal: controller.signal,
|
||||
});
|
||||
} catch (err) {
|
||||
const errorMessage = err?.name === 'AbortError'
|
||||
? `Cloudflare Temp Email 请求超时(>${Math.round(timeoutMs / 1000)} 秒)`
|
||||
: `Cloudflare Temp Email 请求失败:${err.message}`;
|
||||
throw new Error(errorMessage);
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
let parsed;
|
||||
try {
|
||||
parsed = text ? JSON.parse(text) : {};
|
||||
} catch {
|
||||
parsed = text;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const payloadError = typeof parsed === 'object' && parsed
|
||||
? (parsed.message || parsed.error || parsed.msg)
|
||||
: '';
|
||||
throw new Error(`Cloudflare Temp Email 请求失败:${payloadError || text || `HTTP ${response.status}`}`);
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
async function fetchCloudflareTempEmailAddress(state, options = {}) {
|
||||
throwIfStopped();
|
||||
const latestState = state || await getState();
|
||||
const config = ensureCloudflareTempEmailConfig(latestState, {
|
||||
requireAdminAuth: true,
|
||||
requireDomain: true,
|
||||
});
|
||||
const requestedName = String(options.localPart || options.name || '').trim().toLowerCase() || generateCloudflareAliasLocalPart();
|
||||
const payload = {
|
||||
enablePrefix: true,
|
||||
name: requestedName,
|
||||
domain: config.domain,
|
||||
};
|
||||
const result = await requestCloudflareTempEmailJson(config, '/admin/new_address', {
|
||||
method: 'POST',
|
||||
payload,
|
||||
});
|
||||
const address = normalizeCloudflareTempEmailAddress(getCloudflareTempEmailAddressFromResponse(result));
|
||||
if (!address) {
|
||||
throw new Error('Cloudflare Temp Email 未返回可用邮箱地址。');
|
||||
}
|
||||
|
||||
await setEmailState(address);
|
||||
await addLog(`Cloudflare Temp Email:已生成 ${address}`, 'ok');
|
||||
return address;
|
||||
}
|
||||
|
||||
async function fetchDuckEmail(options = {}) {
|
||||
throwIfStopped();
|
||||
const { generateNew = true } = options;
|
||||
@@ -3533,6 +3801,9 @@ async function fetchGeneratedEmail(state, options = {}) {
|
||||
if (generator === 'cloudflare') {
|
||||
return fetchCloudflareEmail(currentState, options);
|
||||
}
|
||||
if (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR) {
|
||||
return fetchCloudflareTempEmailAddress(currentState, options);
|
||||
}
|
||||
return fetchDuckEmail(options);
|
||||
}
|
||||
|
||||
@@ -3546,6 +3817,7 @@ let autoRunTotalRuns = 1;
|
||||
let autoRunAttemptRun = 0;
|
||||
const EMAIL_FETCH_MAX_ATTEMPTS = 5;
|
||||
const VERIFICATION_POLL_MAX_ROUNDS = 5;
|
||||
const STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS = 25000;
|
||||
const AUTO_STEP_DELAYS = {
|
||||
1: 2000,
|
||||
2: 2000,
|
||||
@@ -3635,7 +3907,10 @@ async function ensureAutoEmailReady(targetRun, totalRuns, attemptRuns) {
|
||||
} catch (err) {
|
||||
lastError = err;
|
||||
await addLog(`${generatorLabel}自动获取失败(${attempt}/${EMAIL_FETCH_MAX_ATTEMPTS}):${err.message}`, 'warn');
|
||||
if (generator === 'cloudflare' && /域名/.test(String(err.message || ''))) {
|
||||
if (
|
||||
(generator === 'cloudflare' && /域名/.test(String(err.message || '')))
|
||||
|| (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR && /(服务地址|Admin Auth|域名)/.test(String(err.message || '')))
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -4757,6 +5032,9 @@ function getMailConfig(state) {
|
||||
if (provider === HOTMAIL_PROVIDER) {
|
||||
return { provider: HOTMAIL_PROVIDER, label: 'Hotmail(远程/本地)' };
|
||||
}
|
||||
if (provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER) {
|
||||
return { provider: CLOUDFLARE_TEMP_EMAIL_PROVIDER, label: 'Cloudflare Temp Email' };
|
||||
}
|
||||
if (provider === '163') {
|
||||
return { source: 'mail-163', url: 'https://mail.163.com/js6/main.jsp?df=mail163_letter#module=mbox.ListModule%7C%7B%22fid%22%3A1%2C%22order%22%3A%22date%22%2C%22desc%22%3Atrue%7D', label: '163 邮箱' };
|
||||
}
|
||||
@@ -4921,6 +5199,16 @@ async function pollFreshVerificationCode(step, state, mail, pollOverrides = {})
|
||||
...pollOverrides,
|
||||
});
|
||||
}
|
||||
if (mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER) {
|
||||
return pollCloudflareTempEmailVerificationCode(step, state, {
|
||||
...getVerificationPollPayload(step, state),
|
||||
...pollOverrides,
|
||||
});
|
||||
}
|
||||
|
||||
if (Number(pollOverrides.resendIntervalMs) > 0) {
|
||||
return pollFreshVerificationCodeWithResendInterval(step, state, mail, pollOverrides);
|
||||
}
|
||||
|
||||
const stateKey = getVerificationCodeStateKey(step);
|
||||
const rejectedCodes = new Set();
|
||||
@@ -4990,6 +5278,109 @@ async function pollFreshVerificationCode(step, state, mail, pollOverrides = {})
|
||||
throw lastError || new Error(`步骤 ${step}:无法获取新的${getVerificationCodeLabel(step)}验证码。`);
|
||||
}
|
||||
|
||||
async function pollFreshVerificationCodeWithResendInterval(step, state, mail, pollOverrides = {}) {
|
||||
const stateKey = getVerificationCodeStateKey(step);
|
||||
const rejectedCodes = new Set();
|
||||
if (state[stateKey]) {
|
||||
rejectedCodes.add(state[stateKey]);
|
||||
}
|
||||
for (const code of (pollOverrides.excludeCodes || [])) {
|
||||
if (code) rejectedCodes.add(code);
|
||||
}
|
||||
|
||||
const {
|
||||
maxRounds: _ignoredMaxRounds,
|
||||
resendIntervalMs: _ignoredResendIntervalMs,
|
||||
lastResendAt: _ignoredLastResendAt,
|
||||
...payloadOverrides
|
||||
} = pollOverrides;
|
||||
let lastError = null;
|
||||
const filterAfterTimestamp = payloadOverrides.filterAfterTimestamp ?? getVerificationPollPayload(step, state).filterAfterTimestamp;
|
||||
const maxRounds = pollOverrides.maxRounds || VERIFICATION_POLL_MAX_ROUNDS;
|
||||
const resendIntervalMs = Math.max(0, Number(pollOverrides.resendIntervalMs) || 0);
|
||||
let lastResendAt = Number(pollOverrides.lastResendAt) || 0;
|
||||
|
||||
for (let round = 1; round <= maxRounds; round++) {
|
||||
throwIfStopped();
|
||||
if (round > 1) {
|
||||
lastResendAt = await requestVerificationCodeResend(step);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
throwIfStopped();
|
||||
const payload = getVerificationPollPayload(step, state, {
|
||||
...payloadOverrides,
|
||||
filterAfterTimestamp,
|
||||
excludeCodes: [...rejectedCodes],
|
||||
});
|
||||
|
||||
if (lastResendAt > 0) {
|
||||
const remainingBeforeResendMs = Math.max(0, resendIntervalMs - (Date.now() - lastResendAt));
|
||||
const baseMaxAttempts = Math.max(1, Number(payload.maxAttempts) || 5);
|
||||
const intervalMs = Math.max(1, Number(payload.intervalMs) || 3000);
|
||||
payload.maxAttempts = Math.max(1, Math.min(baseMaxAttempts, Math.floor(remainingBeforeResendMs / intervalMs) + 1));
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await sendToMailContentScriptResilient(
|
||||
mail,
|
||||
{
|
||||
type: 'POLL_EMAIL',
|
||||
step,
|
||||
source: 'background',
|
||||
payload,
|
||||
},
|
||||
{
|
||||
timeoutMs: 45000,
|
||||
maxRecoveryAttempts: 2,
|
||||
}
|
||||
);
|
||||
|
||||
if (result && result.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
if (!result || !result.code) {
|
||||
throw new Error(`步骤 ${step}:邮箱轮询结束,但未获取到验证码。`);
|
||||
}
|
||||
|
||||
if (rejectedCodes.has(result.code)) {
|
||||
throw new Error(`步骤 ${step}:再次收到了相同的${getVerificationCodeLabel(step)}验证码:${result.code}`);
|
||||
}
|
||||
|
||||
return {
|
||||
...result,
|
||||
lastResendAt,
|
||||
};
|
||||
} catch (err) {
|
||||
if (isStopError(err)) {
|
||||
throw err;
|
||||
}
|
||||
lastError = err;
|
||||
await addLog(`步骤 ${step}:${err.message}`, 'warn');
|
||||
}
|
||||
|
||||
const remainingBeforeResendMs = lastResendAt > 0
|
||||
? Math.max(0, resendIntervalMs - (Date.now() - lastResendAt))
|
||||
: 0;
|
||||
if (remainingBeforeResendMs > 0) {
|
||||
await addLog(
|
||||
`步骤 ${step}:距离下次重新发送验证码还差 ${Math.ceil(remainingBeforeResendMs / 1000)} 秒,继续刷新邮箱(第 ${round}/${maxRounds} 轮)...`,
|
||||
'info'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (round < maxRounds) {
|
||||
await addLog(`步骤 ${step}:已到 25 秒重发间隔,准备重新发送验证码(第 ${round + 1}/${maxRounds} 轮)...`, 'warn');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError || new Error(`步骤 ${step}:无法获取新的${getVerificationCodeLabel(step)}验证码。`);
|
||||
}
|
||||
|
||||
async function submitVerificationCode(step, code) {
|
||||
const signupTabId = await getTabId('signup-page');
|
||||
if (!signupTabId) {
|
||||
@@ -5034,10 +5425,12 @@ async function resolveVerificationStep(step, state, mail, options = {}) {
|
||||
? Boolean(options.requestFreshCodeFirst)
|
||||
: (hotmailPollConfig?.requestFreshCodeFirst ?? false);
|
||||
const maxSubmitAttempts = 3;
|
||||
const resendIntervalMs = Math.max(0, Number(options.resendIntervalMs) || 0);
|
||||
let lastResendAt = Number(options.lastResendAt) || 0;
|
||||
|
||||
if (requestFreshCodeFirst) {
|
||||
try {
|
||||
await requestVerificationCodeResend(step);
|
||||
lastResendAt = await requestVerificationCodeResend(step);
|
||||
await addLog(`步骤 ${step}:已先请求一封新的${getVerificationCodeLabel(step)}验证码,再开始轮询邮箱。`, 'warn');
|
||||
} catch (err) {
|
||||
if (isStopError(err) || (step === 7 && isStep7RestartFromStep6Error(err))) {
|
||||
@@ -5059,7 +5452,10 @@ async function resolveVerificationStep(step, state, mail, options = {}) {
|
||||
const result = await pollFreshVerificationCode(step, state, mail, {
|
||||
excludeCodes: [...rejectedCodes],
|
||||
filterAfterTimestamp: nextFilterAfterTimestamp ?? undefined,
|
||||
resendIntervalMs,
|
||||
lastResendAt,
|
||||
});
|
||||
lastResendAt = Number(result?.lastResendAt) || lastResendAt;
|
||||
|
||||
throwIfStopped();
|
||||
await addLog(`步骤 ${step}:已获取${getVerificationCodeLabel(step)}验证码:${result.code}`);
|
||||
@@ -5074,7 +5470,18 @@ async function resolveVerificationStep(step, state, mail, options = {}) {
|
||||
throw new Error(`步骤 ${step}:验证码连续失败,已达到 ${maxSubmitAttempts} 次重试上限。`);
|
||||
}
|
||||
|
||||
await requestVerificationCodeResend(step);
|
||||
const remainingBeforeResendMs = resendIntervalMs > 0 && lastResendAt > 0
|
||||
? Math.max(0, resendIntervalMs - (Date.now() - lastResendAt))
|
||||
: 0;
|
||||
if (remainingBeforeResendMs > 0) {
|
||||
await addLog(
|
||||
`步骤 ${step}:提交失败后距离下次重新发送验证码还差 ${Math.ceil(remainingBeforeResendMs / 1000)} 秒,先继续刷新邮箱(${attempt + 1}/${maxSubmitAttempts})...`,
|
||||
'warn'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
lastResendAt = await requestVerificationCodeResend(step);
|
||||
await addLog(`步骤 ${step}:提交失败后已请求新验证码(${attempt + 1}/${maxSubmitAttempts})...`, 'warn');
|
||||
continue;
|
||||
}
|
||||
@@ -5136,7 +5543,7 @@ async function executeStep4(state) {
|
||||
}
|
||||
|
||||
throwIfStopped();
|
||||
if (mail.provider === HOTMAIL_PROVIDER) {
|
||||
if (mail.provider === HOTMAIL_PROVIDER || mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER) {
|
||||
await addLog(`步骤 4:正在通过 ${mail.label} 轮询验证码...`);
|
||||
} else {
|
||||
await addLog(`步骤 4:正在打开${mail.label}...`);
|
||||
@@ -5164,6 +5571,7 @@ async function executeStep4(state) {
|
||||
await resolveVerificationStep(4, state, mail, {
|
||||
filterAfterTimestamp: mail.provider === HOTMAIL_PROVIDER ? undefined : stepStartedAt,
|
||||
requestFreshCodeFirst: mail.provider === HOTMAIL_PROVIDER ? false : true,
|
||||
resendIntervalMs: mail.provider === HOTMAIL_PROVIDER ? 0 : STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -5271,7 +5679,7 @@ async function runStep7Attempt(state) {
|
||||
}
|
||||
|
||||
throwIfStopped();
|
||||
if (mail.provider === HOTMAIL_PROVIDER) {
|
||||
if (mail.provider === HOTMAIL_PROVIDER || mail.provider === CLOUDFLARE_TEMP_EMAIL_PROVIDER) {
|
||||
await addLog(`步骤 7:正在通过 ${mail.label} 轮询验证码...`);
|
||||
} else {
|
||||
await addLog(`步骤 7:正在打开${mail.label}...`);
|
||||
@@ -5298,6 +5706,7 @@ async function runStep7Attempt(state) {
|
||||
await resolveVerificationStep(7, state, mail, {
|
||||
filterAfterTimestamp: mail.provider === HOTMAIL_PROVIDER ? undefined : stepStartedAt,
|
||||
requestFreshCodeFirst: mail.provider === HOTMAIL_PROVIDER ? false : true,
|
||||
resendIntervalMs: mail.provider === HOTMAIL_PROVIDER ? 0 : STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user