|
@@ -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);
|