diff --git a/background.js b/background.js index 03045ad..19b0caf 100644 --- a/background.js +++ b/background.js @@ -1,15 +1,23 @@ // background.js — Service Worker: orchestration, state, tab management, message routing -importScripts('data/names.js', 'hotmail-utils.js', 'luckmail-utils.js', 'cloudflare-temp-email-utils.js', 'icloud-utils.js', 'content/activation-utils.js'); +importScripts( + 'data/names.js', + 'hotmail-utils.js', + 'microsoft-email.js', + 'luckmail-utils.js', + 'cloudflare-temp-email-utils.js', + 'icloud-utils.js', + 'content/activation-utils.js' +); const { - buildHotmailMailApiLatestUrl, extractVerificationCodeFromMessage, filterHotmailAccountsByUsage, getLatestHotmailMessage, getHotmailMailApiRequestConfig, getHotmailVerificationPollConfig, getHotmailVerificationRequestTimestamp, + normalizeHotmailServiceMode, normalizeHotmailMailApiMessages, pickHotmailAccountForRun, pickVerificationMessage, @@ -17,6 +25,9 @@ const { pickVerificationMessageWithTimeFallback, shouldClearHotmailCurrentSelection, } = self.HotmailUtils; +const { + fetchMicrosoftMailboxMessages, +} = self.MultiPageMicrosoftEmail; const { DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME, DEFAULT_LUCKMAIL_BASE_URL, @@ -114,9 +125,37 @@ const DEFAULT_HOTMAIL_LOCAL_BASE_URL = 'http://127.0.0.1:17373'; const HOTMAIL_LOCAL_HELPER_TIMEOUT_MS = 45000; const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai'; const DISPLAY_TIMEZONE = 'Asia/Shanghai'; +const MICROSOFT_TOKEN_DNR_RULE_ID = 1001; const PERSISTENT_ALIAS_STATE_KEYS = ['manualAliasUsage', 'preservedAliases']; initializeSessionStorageAccess(); +setupDeclarativeNetRequestRules(); + +function setupDeclarativeNetRequestRules() { + if (!chrome.declarativeNetRequest?.updateDynamicRules) { + return; + } + + chrome.declarativeNetRequest.updateDynamicRules({ + removeRuleIds: [MICROSOFT_TOKEN_DNR_RULE_ID], + addRules: [{ + id: MICROSOFT_TOKEN_DNR_RULE_ID, + priority: 1, + action: { + type: 'modifyHeaders', + requestHeaders: [ + { header: 'Origin', operation: 'remove' }, + ], + }, + condition: { + urlFilter: 'login.microsoftonline.com/*/oauth2/v2.0/token', + resourceTypes: ['xmlhttprequest'], + }, + }], + }).catch((error) => { + console.warn(LOG_PREFIX, 'Failed to setup declarativeNetRequest rules:', error?.message || error); + }); +} // ============================================================ // 状态管理(chrome.storage.session + chrome.storage.local) @@ -412,10 +451,6 @@ function normalizeCloudflareDomains(values) { return normalizedDomains; } -function normalizeHotmailServiceMode(rawValue = '') { - return HOTMAIL_SERVICE_MODE_LOCAL; -} - function normalizeHotmailRemoteBaseUrl(rawValue = '') { const value = String(rawValue || '').trim(); if (!value) return DEFAULT_HOTMAIL_REMOTE_BASE_URL; @@ -1180,11 +1215,6 @@ async function ensureHotmailAccountForFlow(options = {}) { return setCurrentHotmailAccount(account.id, { markUsed, syncEmail: true }); } -function buildHotmailRemoteEndpoint(baseUrl, path) { - const normalizedBaseUrl = normalizeHotmailRemoteBaseUrl(baseUrl); - return new URL(path, `${normalizedBaseUrl}/`).toString(); -} - function buildHotmailLocalEndpoint(baseUrl, path) { const normalizedBaseUrl = normalizeHotmailLocalBaseUrl(baseUrl); return new URL(path, `${normalizedBaseUrl}/`).toString(); @@ -1201,55 +1231,40 @@ async function requestHotmailRemoteMailbox(account, mailbox = 'INBOX') { throw new Error(`Hotmail 账号 ${account.email || account.id} 缺少刷新令牌(refresh token)。`); } - const serviceSettings = getHotmailServiceSettings(await getState()); - const url = buildHotmailMailApiLatestUrl({ - apiUrl: buildHotmailRemoteEndpoint(serviceSettings.remoteBaseUrl, '/api/mail-new'), - clientId: account.clientId, - email: account.email, - refreshToken: account.refreshToken, - mailbox, - responseType: 'json', - }); const { timeoutMs } = getHotmailMailApiRequestConfig(); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs); - let response; try { - response = await fetch(url, { method: 'GET', signal: controller.signal }); + const result = await fetchMicrosoftMailboxMessages({ + clientId: account.clientId, + refreshToken: account.refreshToken, + mailbox, + top: 10, + signal: controller.signal, + }); + + return { + mailbox, + payload: { + source: 'microsoft-api', + transport: result.transport, + tokenStrategy: result.tokenStrategy, + }, + messages: normalizeHotmailMailApiMessages(result.messages).map((message) => ({ + ...message, + mailbox: message?.mailbox || mailbox, + })), + nextRefreshToken: result.nextRefreshToken, + }; } catch (err) { if (err?.name === 'AbortError') { - throw new Error(`Hotmail API 请求超时(>${Math.round(timeoutMs / 1000)} 秒):${mailbox}`); + throw new Error(`Hotmail API 对接请求超时(>${Math.round(timeoutMs / 1000)} 秒):${mailbox}`); } - throw new Error(`Hotmail API 请求失败:${err.message}`); + throw new Error(`Hotmail API 对接请求失败:${err.message}`); } finally { clearTimeout(timeoutId); } - - const text = await response.text(); - let payload = {}; - try { - payload = text ? JSON.parse(text) : {}; - } catch { - payload = { raw: text }; - } - - if (!response.ok) { - const errorText = payload?.message || payload?.error || payload?.msg || text || `HTTP ${response.status}`; - throw new Error(`Hotmail API 请求失败:${errorText}`); - } - - if (payload && payload.success === false) { - const errorText = payload?.message || payload?.msg || payload?.error || '未知错误'; - throw new Error(`Hotmail API 返回失败:${errorText}`); - } - - return { - mailbox, - payload, - messages: normalizeHotmailMailApiMessages(payload?.data), - nextRefreshToken: String(payload?.new_refresh_token || payload?.newRefreshToken || '').trim(), - }; } function applyHotmailApiResultToAccount(account, apiResult) { @@ -1282,7 +1297,10 @@ async function fetchHotmailMailboxMessagesFromRemoteService(account, mailboxes = mailboxResults.push({ mailbox, count: result.messages.length, - messages: result.messages.map((message) => ({ ...message, mailbox })), + messages: result.messages.map((message) => ({ + ...message, + mailbox: message?.mailbox || mailbox, + })), }); } } catch (err) { @@ -1592,7 +1610,7 @@ async function pollHotmailVerificationCode(step, state, pollPayload = {}) { for (let attempt = 1; attempt <= maxAttempts; attempt++) { throwIfStopped(); try { - await addLog(`步骤 ${step}:正在轮询 Hotmail 邮件(${attempt}/${maxAttempts})...`, 'info'); + await addLog(`步骤 ${step}:正在通过 API对接 轮询 Hotmail 邮件(${attempt}/${maxAttempts})...`, 'info'); const fetchResult = await fetchHotmailMailboxMessages(account, HOTMAIL_MAILBOXES); account = fetchResult.account; const matchResult = pickVerificationMessageWithTimeFallback(fetchResult.messages, { @@ -1609,7 +1627,7 @@ async function pollHotmailVerificationCode(step, state, pollPayload = {}) { const fallbackLabel = matchResult.usedTimeFallback ? '宽松匹配 + 时间回退' : '宽松匹配'; await addLog(`步骤 ${step}:严格规则未命中,已改用 ${fallbackLabel} 并命中 Hotmail ${mailboxLabel} 验证码。`, 'warn'); } - await addLog(`步骤 ${step}:已在 Hotmail ${mailboxLabel} 中找到验证码:${match.code}`, 'ok'); + await addLog(`步骤 ${step}:已通过 API对接 在 Hotmail ${mailboxLabel} 中找到验证码:${match.code}`, 'ok'); return { ok: true, code: match.code, @@ -1626,7 +1644,7 @@ async function pollHotmailVerificationCode(step, state, pollPayload = {}) { } } catch (err) { lastError = err; - await addLog(`步骤 ${step}:Hotmail 收件箱轮询失败:${err.message}`, 'warn'); + await addLog(`步骤 ${step}:Hotmail API 对接轮询失败:${err.message}`, 'warn'); } if (attempt < maxAttempts) { @@ -3720,7 +3738,7 @@ function getSourceLabel(source) { 'mail-2925': '2925 邮箱', 'inbucket-mail': 'Inbucket 邮箱', 'duck-mail': 'Duck 邮箱', - 'hotmail-api': 'Hotmail(远程/本地)', + 'hotmail-api': 'Hotmail(API对接/本地助手)', 'luckmail-api': 'LuckMail(API 购邮)', 'cloudflare-temp-email': 'Cloudflare Temp Email', }; @@ -6514,7 +6532,7 @@ function getMailConfig(state) { return { provider: 'custom', label: '自定义邮箱' }; } if (provider === HOTMAIL_PROVIDER) { - return { provider: HOTMAIL_PROVIDER, label: 'Hotmail(远程/本地)' }; + return { provider: HOTMAIL_PROVIDER, label: 'Hotmail(API对接/本地助手)' }; } if (provider === LUCKMAIL_PROVIDER) { return { provider: LUCKMAIL_PROVIDER, label: 'LuckMail(API 购邮)' }; diff --git a/hotmail-utils.js b/hotmail-utils.js index 97e07bf..5d736ca 100644 --- a/hotmail-utils.js +++ b/hotmail-utils.js @@ -7,6 +7,8 @@ root.HotmailUtils = factory(); })(typeof self !== 'undefined' ? self : globalThis, function createHotmailUtils() { const HOTMAIL_MAIL_API_URL = 'https://apple.882263.xyz/api/mail-new'; + const HOTMAIL_SERVICE_MODE_REMOTE = 'remote'; + const HOTMAIL_SERVICE_MODE_LOCAL = 'local'; function normalizeText(value) { return String(value || '') @@ -25,6 +27,12 @@ return Number.isFinite(timestamp) ? timestamp : 0; } + function normalizeHotmailServiceMode(rawValue = '') { + return String(rawValue || '').trim().toLowerCase() === HOTMAIL_SERVICE_MODE_REMOTE + ? HOTMAIL_SERVICE_MODE_REMOTE + : HOTMAIL_SERVICE_MODE_LOCAL; + } + function extractVerificationCode(text) { const source = String(text || ''); const matchCn = source.match(/(?:代码为|验证码[^0-9]*?)[\s::]*(\d{6})/i); @@ -368,6 +376,7 @@ getHotmailVerificationPollConfig, getHotmailVerificationRequestTimestamp, isAuthorizedHotmailAccount, + normalizeHotmailServiceMode, normalizeHotmailMailApiMessages, normalizeTimestamp, parseHotmailImportText, diff --git a/microsoft-email.js b/microsoft-email.js new file mode 100644 index 0000000..131af23 --- /dev/null +++ b/microsoft-email.js @@ -0,0 +1,451 @@ +(function attachMicrosoftEmailHelpers(globalScope) { + const OPENAI_SENDER_PATTERNS = [ + /openai\.com/i, + /auth0\.openai\.com/i, + ]; + const CODE_PATTERN = /\b(\d{6})\b/; + const GRAPH_SCOPES = 'offline_access https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/User.Read'; + const GRAPH_DEFAULT_SCOPE = 'https://graph.microsoft.com/.default'; + const TOKEN_STRATEGIES = [ + { + name: 'entra-common-delegated', + url: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + extraData: { scope: GRAPH_SCOPES }, + }, + { + name: 'entra-consumers-delegated', + url: 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token', + extraData: { scope: GRAPH_SCOPES }, + }, + { + name: 'entra-common-default', + url: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + extraData: { scope: GRAPH_DEFAULT_SCOPE }, + }, + { + name: 'entra-common-outlook', + url: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + extraData: {}, + }, + ]; + const TRANSPORT_PLANS = [ + { + transport: 'graph', + strategyNames: ['entra-common-delegated', 'entra-consumers-delegated', 'entra-common-default'], + }, + { + transport: 'outlook', + strategyNames: ['entra-common-outlook', 'entra-common-delegated', 'entra-consumers-delegated'], + }, + ]; + const GRAPH_API_BASE = 'https://graph.microsoft.com/v1.0/me/mailFolders'; + const OUTLOOK_API_BASE = 'https://outlook.office.com/api/v2.0/me/mailfolders'; + + function getFetchImpl(fetchImpl) { + const resolved = fetchImpl || globalScope.fetch; + if (typeof resolved !== 'function') { + throw new Error('Microsoft email helper requires a fetch implementation.'); + } + return resolved; + } + + function resolveTokenStrategy(name) { + return TOKEN_STRATEGIES.find((item) => item.name === name) || TOKEN_STRATEGIES[0]; + } + + function normalizeMailboxLabel(mailbox = 'INBOX') { + return /^junk(?:\s*e-?mail|\s*email)?$/i.test(String(mailbox || '').trim()) ? 'Junk' : 'INBOX'; + } + + function normalizeMailboxId(mailbox = 'INBOX') { + return normalizeMailboxLabel(mailbox) === 'Junk' ? 'junkemail' : 'inbox'; + } + + function normalizeMailboxList(mailboxes) { + const list = Array.isArray(mailboxes) && mailboxes.length ? mailboxes : ['INBOX']; + return [...new Set(list.map((mailbox) => normalizeMailboxLabel(mailbox)))]; + } + + async function getResponseErrorText(response) { + const text = await response.text().catch(() => ''); + if (!text) { + return response.statusText || `HTTP ${response.status}`; + } + try { + const parsed = JSON.parse(text); + return parsed.error_description || parsed.error?.message || parsed.error || parsed.message || text; + } catch { + return text; + } + } + + async function exchangeRefreshToken(clientId, refreshToken, options = {}) { + const fetchImpl = getFetchImpl(options.fetchImpl); + const strategy = resolveTokenStrategy(options.strategyName); + const body = new URLSearchParams({ + client_id: clientId, + grant_type: 'refresh_token', + refresh_token: refreshToken, + ...(strategy.extraData || {}), + }); + const response = await fetchImpl(strategy.url, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: body.toString(), + signal: options.signal, + }); + + if (!response.ok) { + throw new Error(`${strategy.name}: ${await getResponseErrorText(response)}`); + } + + const data = await response.json(); + if (!data.access_token) { + throw new Error(`${strategy.name}: token response missing access_token`); + } + + return { + ...data, + tokenStrategy: strategy.name, + }; + } + + async function fetchGraphMessages(accessToken, options = {}) { + const fetchImpl = getFetchImpl(options.fetchImpl); + const mailbox = normalizeMailboxLabel(options.mailbox); + const top = Math.max(1, Math.min(Number(options.top) || 5, 30)); + const url = `${GRAPH_API_BASE}/${normalizeMailboxId(mailbox)}/messages?$top=${encodeURIComponent(top)}&$select=id,internetMessageId,subject,from,bodyPreview,receivedDateTime&$orderby=receivedDateTime desc`; + const response = await fetchImpl(url, { + method: 'GET', + headers: { + Accept: 'application/json', + Authorization: `Bearer ${accessToken}`, + }, + signal: options.signal, + }); + + if (!response.ok) { + throw new Error(`graph: ${await getResponseErrorText(response)}`); + } + + const payload = await response.json(); + return Array.isArray(payload?.value) ? payload.value : []; + } + + async function fetchOutlookMessages(accessToken, options = {}) { + const fetchImpl = getFetchImpl(options.fetchImpl); + const mailbox = normalizeMailboxLabel(options.mailbox); + const top = Math.max(1, Math.min(Number(options.top) || 5, 30)); + const url = `${OUTLOOK_API_BASE}/${normalizeMailboxId(mailbox)}/messages?$top=${encodeURIComponent(top)}&$select=Id,Subject,From,BodyPreview,Body,ReceivedDateTime&$orderby=ReceivedDateTime desc`; + const response = await fetchImpl(url, { + method: 'GET', + headers: { + Accept: 'application/json', + Authorization: `Bearer ${accessToken}`, + }, + signal: options.signal, + }); + + if (!response.ok) { + throw new Error(`outlook: ${await getResponseErrorText(response)}`); + } + + const payload = await response.json(); + return Array.isArray(payload?.value) ? payload.value : []; + } + + function normalizeMessage(message, mailbox = 'INBOX') { + const sender = message?.From || message?.from || {}; + const emailAddress = sender?.EmailAddress || sender?.emailAddress || {}; + return { + mailbox: normalizeMailboxLabel(mailbox || message?.mailbox), + from: { + emailAddress: { + address: String(emailAddress?.Address || emailAddress?.address || '').trim(), + name: String(emailAddress?.Name || emailAddress?.name || '').trim(), + }, + }, + subject: String(message?.Subject || message?.subject || '').trim(), + receivedDateTime: String(message?.ReceivedDateTime || message?.receivedDateTime || '').trim(), + bodyPreview: String(message?.BodyPreview || message?.bodyPreview || '').trim(), + body: { + content: String(message?.Body?.Content || message?.body?.content || '').trim(), + }, + id: String(message?.Id || message?.id || message?.internetMessageId || '').trim(), + }; + } + + function normalizeFilterValue(value) { + return String(value || '').trim().toLowerCase(); + } + + function getMessageSender(message) { + return String( + message?.from?.emailAddress?.address + || message?.sender?.emailAddress?.address + || '' + ).trim(); + } + + function getMessageTimestamp(message) { + const value = Date.parse(message?.receivedDateTime || message?.createdDateTime || ''); + return Number.isFinite(value) ? value : 0; + } + + function getMessageSearchText(message) { + return [ + message?.subject, + message?.bodyPreview, + message?.body?.content, + getMessageSender(message), + ] + .map((value) => String(value || '')) + .join('\n'); + } + + function isOpenAiMessage(message) { + const sender = getMessageSender(message); + if (OPENAI_SENDER_PATTERNS.some((pattern) => pattern.test(sender))) { + return true; + } + + const searchText = getMessageSearchText(message); + return OPENAI_SENDER_PATTERNS.some((pattern) => pattern.test(searchText)); + } + + function extractVerificationCodeFromMessages(messages, options = {}) { + const filterAfterTimestamp = Number(options.filterAfterTimestamp || 0) || 0; + const senderFilters = (options.senderFilters || []).map(normalizeFilterValue).filter(Boolean); + const subjectFilters = (options.subjectFilters || []).map(normalizeFilterValue).filter(Boolean); + const excludedCodes = new Set((options.excludeCodes || []).map((value) => String(value || '').trim()).filter(Boolean)); + const hasExplicitFilters = senderFilters.length > 0 || subjectFilters.length > 0; + + const sortedMessages = (Array.isArray(messages) ? messages : []) + .map((raw) => normalizeMessage(raw, raw?.mailbox)) + .sort((left, right) => getMessageTimestamp(right) - getMessageTimestamp(left)); + + for (const message of sortedMessages) { + const receivedAt = getMessageTimestamp(message); + if (receivedAt && receivedAt < filterAfterTimestamp) { + continue; + } + + const sender = normalizeFilterValue(getMessageSender(message)); + const subject = normalizeFilterValue(message?.subject); + const preview = normalizeFilterValue(message?.bodyPreview); + const searchText = normalizeFilterValue(getMessageSearchText(message)); + const codeMatch = getMessageSearchText(message).match(CODE_PATTERN); + const code = codeMatch?.[1] || ''; + if (!code || excludedCodes.has(code)) { + continue; + } + + if (!hasExplicitFilters && !isOpenAiMessage(message)) { + continue; + } + + const senderMatched = senderFilters.length === 0 + ? true + : senderFilters.some((filter) => sender.includes(filter) || preview.includes(filter) || searchText.includes(filter)); + const subjectMatched = subjectFilters.length === 0 + ? true + : subjectFilters.some((filter) => subject.includes(filter) || preview.includes(filter) || searchText.includes(filter)); + if (!senderMatched && !subjectMatched) { + continue; + } + + return { + code, + emailTimestamp: receivedAt || Date.now(), + messageId: message?.id || null, + sender: getMessageSender(message), + subject: String(message?.subject || ''), + mailbox: message?.mailbox || 'INBOX', + message, + }; + } + + return null; + } + + async function fetchMicrosoftMailboxMessages(options = {}) { + const { + clientId, + refreshToken, + mailbox = 'INBOX', + top = 5, + fetchImpl, + signal, + log = null, + } = options; + + if (!refreshToken) { + throw new Error('Microsoft refresh token is empty.'); + } + if (!clientId) { + throw new Error('Microsoft client_id is empty.'); + } + + const errors = []; + for (const plan of TRANSPORT_PLANS) { + for (const strategyName of plan.strategyNames) { + try { + const tokenData = await exchangeRefreshToken(clientId, refreshToken, { + fetchImpl, + signal, + strategyName, + }); + const rawMessages = plan.transport === 'graph' + ? await fetchGraphMessages(tokenData.access_token, { mailbox, top, fetchImpl, signal }) + : await fetchOutlookMessages(tokenData.access_token, { mailbox, top, fetchImpl, signal }); + + return { + tokenData, + nextRefreshToken: String(tokenData?.refresh_token || '').trim(), + tokenStrategy: strategyName, + transport: plan.transport, + mailbox: normalizeMailboxLabel(mailbox), + messages: rawMessages.map((message) => normalizeMessage(message, mailbox)), + }; + } catch (error) { + const message = error?.message || String(error); + errors.push(`${plan.transport}/${strategyName}: ${message}`); + if (typeof log === 'function') { + log(`mailbox=${normalizeMailboxLabel(mailbox)} ${plan.transport}/${strategyName} failed: ${message}`); + } + } + } + } + + throw new Error(`Microsoft mailbox request failed: ${errors.join(' | ')}`); + } + + function delay(timeoutMs, signal) { + if (timeoutMs <= 0) { + return Promise.resolve(); + } + + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + cleanup(); + resolve(); + }, timeoutMs); + const onAbort = () => { + cleanup(); + reject(signal.reason || new Error('Aborted')); + }; + const cleanup = () => { + clearTimeout(timer); + signal?.removeEventListener('abort', onAbort); + }; + + if (signal?.aborted) { + cleanup(); + reject(signal.reason || new Error('Aborted')); + return; + } + + signal?.addEventListener('abort', onAbort, { once: true }); + }); + } + + async function fetchMicrosoftVerificationCode(options = {}) { + const { + token, + refreshToken, + clientId, + maxRetries = 3, + retryDelayMs = 10000, + top = 5, + log = null, + filterAfterTimestamp = 0, + senderFilters = [], + subjectFilters = [], + excludeCodes = [], + mailboxes = ['INBOX'], + fetchImpl, + signal, + } = options; + + let workingRefreshToken = String(refreshToken || token || '').trim(); + if (!workingRefreshToken) { + throw new Error('Microsoft refresh token is empty.'); + } + if (!clientId) { + throw new Error('Microsoft client_id is empty.'); + } + + const normalizedMailboxes = normalizeMailboxList(mailboxes); + let lastError = null; + + for (let attempt = 1; attempt <= maxRetries; attempt += 1) { + try { + const collectedMessages = []; + for (const mailbox of normalizedMailboxes) { + const result = await fetchMicrosoftMailboxMessages({ + clientId, + refreshToken: workingRefreshToken, + mailbox, + top, + fetchImpl, + signal, + log, + }); + if (result.nextRefreshToken) { + workingRefreshToken = result.nextRefreshToken; + } + collectedMessages.push(...result.messages); + } + + const match = extractVerificationCodeFromMessages(collectedMessages, { + filterAfterTimestamp, + senderFilters, + subjectFilters, + excludeCodes, + }); + if (match) { + return { + ...match, + nextRefreshToken: workingRefreshToken, + messages: collectedMessages, + }; + } + + lastError = new Error('No matching Microsoft verification email found.'); + } catch (error) { + lastError = error; + } + + if (attempt < maxRetries) { + if (typeof log === 'function') { + log(`attempt ${attempt}/${maxRetries} found no matching Microsoft mail, retrying...`); + } + await delay(retryDelayMs, signal); + } + } + + throw lastError || new Error('No matching Microsoft verification email found.'); + } + + const api = { + CODE_PATTERN, + exchangeRefreshToken, + extractVerificationCodeFromMessages, + fetchGraphMessages, + fetchMicrosoftMailboxMessages, + fetchMicrosoftVerificationCode, + fetchOutlookMessages, + getMessageSender, + getMessageTimestamp, + isOpenAiMessage, + normalizeMailboxId, + normalizeMailboxLabel, + normalizeMessage, + }; + + globalScope.MultiPageMicrosoftEmail = api; + + if (typeof module !== 'undefined' && module.exports) { + module.exports = api; + } +})(typeof globalThis !== 'undefined' ? globalThis : this); diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 7bdb2e4..133a64a 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -153,7 +153,7 @@
+ API对接 +