| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- (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);
|