/** * Microsoft Graph API provider — replaces IMAP for Outlook.com/Office 365 accounts. * Uses Graph API for reliable, fast mail sync with native write-back. * * SOURCE OF TRUTH: this file is the canonical Outlook provider for mailx. * Same Provider abstraction as Gmail and IMAP, lives alongside them so future * Android Outlook support uses the same code with no re-implementation. * * Modeled on gmail.ts (the gold-standard provider): shared module-level token * bucket, robust 429/5xx retry with Retry-After + shared cooldown + terminal * cooldown, and provider_id-first identity for every per-message op. Graph's * string IDs ARE identity; the integer uid is a sort/display convenience only. * See the imap_uid_not_identity lesson — any per-message op keyed solely by the * hashed uid must list-and-hash, which is capped and breaks on large folders, * so we always prefer the caller-supplied Graph id. * * Platform requirements: globalThis.fetch (Node 18+ and all browsers/WebViews), * atob/btoa, Uint8Array, TextDecoder, TextEncoder. No Node-specific imports * (no node:*, no Buffer) — this runs in the Android WebView too. */ import type { MailProvider, ProviderFolder, ProviderMessage, FetchOptions } from "./types.js"; export declare class OutlookApiProvider implements MailProvider { private tokenProvider; constructor(tokenProvider: () => Promise); /** Hierarchical display path → Graph folder id, plus id → id (self) so a raw * Graph id passed back in still resolves. Populated by listFolders. Instance- * scoped (not module-shared like rateState) because folder ids are per- * mailbox: two accounts share throttle state but never folder identity. */ private folderIds; /** Turn whatever the dispatcher hands us (a hierarchical display path like * "Projects/2026/Invoices", a Graph well-known name, or a raw Graph id) * into a Graph folder id usable in a /mailFolders/{id} URL or as a move * destinationId. Disambiguation order: * (a) Graph well-known name (inbox/drafts/…) → pass through; it IS an id. * (b) Known hierarchical path or already-an-id in the map → mapped id. * (c) Map empty (fresh instance — listFolders hasn't run this session, * as happens for move/rename which never list first) → list once to * populate, then re-check. * (d) Still unknown → return as-is (back-compat: assume it's a raw Graph * id we simply haven't catalogued, e.g. a destination outside the * listed set). */ private resolveFolder; /** Block until (a) cooldown has elapsed and (b) a token is available. * Token-bucket refill happens lazily on each call. Copy of gmail.ts's * acquireToken so all providers share one throttling discipline. */ private acquireToken; /** Compute a retry delay from the attempt number + an optional Retry-After * header (seconds OR HTTP-date). Full jitter, capped. Shared by the JSON * fetch and the raw fetch so both back off identically. Graph signals * throttling via 429 + Retry-After — it has no 403-quota equivalent, so * (unlike gmail.ts) there's no quota-403 branch. */ private retryDelay; private fetch; /** Fetch raw bytes (for RFC 2822 $value endpoint). Goes through the same * acquireToken + 429/5xx retry discipline as the JSON fetch — a body * prefetch must not side-step the throttle and re-trigger a cooldown. */ private fetchRaw; listFolders(): Promise; /** Recurse into child folders. `parentPath` is the HIERARCHICAL display path * of the parent (e.g. "Projects/2026"), so a child's path nests fully * ("Projects/2026/Invoices") rather than flattening to one level. */ private fetchChildFolders; /** Convert Graph message to ProviderMessage */ private parseMessage; /** Stable integer UID from Graph string ID. NOT identity — only a * sort/display convenience. Every per-message op prefers the Graph id. */ private idToUid; /** List messages in a folder with optional $filter. Tracks whether * pagination was capped so getUids can flag truncation for reconcile. */ private listMessages; fetchSince(folder: string, sinceUid: number, options?: FetchOptions): Promise; fetchByDate(folder: string, since: Date, before: Date, options?: FetchOptions, onChunk?: (msgs: ProviderMessage[]) => void): Promise; fetchByUids(folder: string, uids: number[], options?: FetchOptions): Promise; fetchOne(folder: string, uid: number, options?: FetchOptions): Promise; /** Bulk-fetch raw bodies for many UIDs in one folder. Lists the folder * once to build a uid→Graph-id map, then pulls each wanted message's MIME * via /$value with bounded concurrency (4 workers). We deliberately do NOT * use Graph $batch multipart here: bounded-concurrency individual GETs are * simpler and Graph's per-request reliability doesn't need the fragile * multipart parsing. Mirrors gmail.ts's fetchBodiesIndividually shape. */ fetchBodiesBatch(folder: string, uids: number[], onBody: (uid: number, source: string) => void): Promise; /** Apply the absolute flag state to a message. Graph model: isRead boolean * + flag.flagStatus. We send both so the end state matches regardless of * prior state (idempotent, safe to retry). */ setFlags(folder: string, uid: number, flags: string[], providerId?: string): Promise; /** Move a message to Deleted Items. Graph's well-known folder name * "deleteditems" is accepted as a move destination. */ trashMessage(folder: string, uid: number, providerId?: string): Promise; /** Move a message to another folder. `toFolder` is the client's `path` (a * hierarchical display path) — resolve it to the Graph destination id. The * source folder is only needed for the resolveId list-and-hash fallback, * which resolveFolder handles internally via listMessages. */ moveMessage(fromFolder: string, uid: number, toFolder: string, providerId?: string): Promise; /** POST /messages/{id}/move, treating 404 as already-done. * * CRITICAL (differs from Gmail): a Graph move returns a NEW message id in * the destination — the source id we hold becomes invalid. So if a move * succeeds server-side but the ACK is lost, the queued retry re-fires with * the now-stale id and Graph answers 404. Throwing there would exhaust the * sync-action retries and `clearTombstoneForUid` would RESURRECT a message * that was actually moved (the exact "deletions un-happening" class the * Gmail provider_id fix killed). A 404 on a move means "it's no longer * here" — which is the goal — so we swallow it as success. */ private moveById; /** Rename and/or reparent a mail folder. `folderPath` and `newParentPath` * are the client's hierarchical display paths — resolve each to its Graph * id before use. * * - Rename: PATCH /mailFolders/{id} { displayName }. * - Reparent: POST /mailFolders/{id}/move { destinationId }. * Both can apply in one call: move first (so the folder lands under the new * parent), then patch the display name. * * Well-known folders (inbox/sentitems/drafts/deleteditems/junkemail/archive) * must not be renamed — Graph rejects it, and it would corrupt the special- * use mapping. We guard on both the well-known name (the path may be the bare * name) and the folder's leaf display name, lower-cased. */ renameFolder(folderPath: string, newName: string, newParentPath?: string): Promise; /** Send a full RFC822 MIME message via Graph's /sendMail. Graph accepts a * base64-encoded MIME payload when the request Content-Type is text/plain * and the body is the base64 string. We don't route this through the JSON * fetch() helper (different content type) but still acquire a token and * apply a simple inline retry. */ sendRaw(mime: string): Promise; /** Resolve a Graph message id: prefer the caller-supplied provider_id; * otherwise list-and-hash (capped) and throw if not found. Centralizes the * rationale shared by setFlags/trash/move — the cap means a write to a * message past the most-recent ~1000 fails loudly rather than silently * hitting the wrong message. */ private resolveId; getUids(folder: string): Promise; close(): Promise; /** Add RFC 2822 source to messages by pulling each /$value. Sequential — * bodies-with-source paths are already date- or uid-bounded so the count * is small; bulk prefetch uses fetchBodiesBatch's worker pool instead. */ private addSources; } //# sourceMappingURL=outlook.d.ts.map