@dawnlit/outlook-bridge 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -9
- package/dist/backend.d.ts +2 -2
- package/dist/backend.js.map +1 -1
- package/dist/bridge.js +3 -3
- package/dist/bridge.js.map +1 -1
- package/dist/errors.d.ts +17 -8
- package/dist/errors.js +17 -13
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/mac/accounts.d.ts +4 -0
- package/dist/mac/accounts.js +10 -0
- package/dist/mac/accounts.js.map +1 -1
- package/dist/mac/attachments.js +1 -1
- package/dist/mac/attachments.js.map +1 -1
- package/dist/mac/bounces.js +18 -31
- package/dist/mac/bounces.js.map +1 -1
- package/dist/mac/cleanup.d.ts +11 -3
- package/dist/mac/cleanup.js +30 -58
- package/dist/mac/cleanup.js.map +1 -1
- package/dist/mac/drafts.d.ts +3 -1
- package/dist/mac/drafts.js +37 -49
- package/dist/mac/drafts.js.map +1 -1
- package/dist/mac/folders.d.ts +10 -3
- package/dist/mac/folders.js +31 -23
- package/dist/mac/folders.js.map +1 -1
- package/dist/mac/profile.d.ts +12 -0
- package/dist/mac/profile.js +75 -1
- package/dist/mac/profile.js.map +1 -1
- package/dist/mac/read.d.ts +4 -0
- package/dist/mac/read.js +68 -42
- package/dist/mac/read.js.map +1 -1
- package/dist/mac/run.d.ts +19 -5
- package/dist/mac/run.js +49 -9
- package/dist/mac/run.js.map +1 -1
- package/dist/mac/scripts.d.ts +67 -14
- package/dist/mac/scripts.js +150 -31
- package/dist/mac/scripts.js.map +1 -1
- package/dist/mac/send.d.ts +5 -4
- package/dist/mac/send.js +52 -14
- package/dist/mac/send.js.map +1 -1
- package/dist/mac/templates.js +16 -22
- package/dist/mac/templates.js.map +1 -1
- package/dist/mail.d.ts +11 -3
- package/dist/mail.js +25 -6
- package/dist/mail.js.map +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/shared/args.js.map +1 -1
- package/dist/shared/attachmentMatch.d.ts +0 -6
- package/dist/shared/attachmentMatch.js +0 -1
- package/dist/shared/attachmentMatch.js.map +1 -1
- package/dist/shared/exec.js.map +1 -1
- package/dist/shared/locate.d.ts +0 -8
- package/dist/shared/locate.js +0 -1
- package/dist/shared/locate.js.map +1 -1
- package/dist/shared/replyBody.d.ts +0 -15
- package/dist/shared/replyBody.js +0 -2
- package/dist/shared/replyBody.js.map +1 -1
- package/dist/shared/templateLimits.d.ts +14 -0
- package/dist/shared/templateLimits.js +21 -0
- package/dist/shared/templateLimits.js.map +1 -0
- package/dist/templateBody.d.ts +6 -0
- package/dist/templateBody.js +8 -1
- package/dist/templateBody.js.map +1 -1
- package/dist/types.d.ts +43 -7
- package/dist/types.js.map +1 -1
- package/dist/windows/accounts.js +1 -1
- package/dist/windows/accounts.js.map +1 -1
- package/dist/windows/attachments.js +2 -2
- package/dist/windows/attachments.js.map +1 -1
- package/dist/windows/bounces.js +7 -7
- package/dist/windows/bounces.js.map +1 -1
- package/dist/windows/cleanup.d.ts +5 -1
- package/dist/windows/cleanup.js +18 -15
- package/dist/windows/cleanup.js.map +1 -1
- package/dist/windows/drafts.d.ts +4 -4
- package/dist/windows/drafts.js +22 -33
- package/dist/windows/drafts.js.map +1 -1
- package/dist/windows/folders.js +2 -2
- package/dist/windows/folders.js.map +1 -1
- package/dist/{shared → windows}/json.d.ts +11 -6
- package/dist/{shared → windows}/json.js +12 -7
- package/dist/windows/json.js.map +1 -0
- package/dist/windows/read.js +23 -16
- package/dist/windows/read.js.map +1 -1
- package/dist/windows/run.d.ts +0 -2
- package/dist/windows/run.js +1 -2
- package/dist/windows/run.js.map +1 -1
- package/dist/windows/scripts.d.ts +10 -4
- package/dist/windows/scripts.js +21 -12
- package/dist/windows/scripts.js.map +1 -1
- package/dist/windows/send.d.ts +13 -4
- package/dist/windows/send.js +24 -6
- package/dist/windows/send.js.map +1 -1
- package/dist/windows/templates.js +11 -17
- package/dist/windows/templates.js.map +1 -1
- package/package.json +2 -2
- package/dist/shared/json.js.map +0 -1
package/dist/mail.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mail.js","sourceRoot":"","sources":["../src/mail.ts"],"names":[],"mappings":";;;AAyDA,wCAEC;AAmDD,wCAEC;AAQD,sCAKC;AAOD,8CAQC;AAGD,wCAEC;AAkBD,sCAOC;AAOD,oCAMC;AAeD,kDAsBC;AA5ND,iFAAiF;AACjF,+EAA+E;AAC/E,0EAA0E;AAC1E,qCAA+C;AAG/C;;;;GAIG;AACU,QAAA,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC;IAClC,YAAY,EAAE,CAAC;IACf,MAAM,EAAE,CAAC;IACT,QAAQ,EAAE,CAAC;IACX,KAAK,EAAE,CAAC;IACR,MAAM,EAAE,EAAE;IACV,IAAI,EAAE,EAAE;CACX,CAAC,CAAC;AAEH;;;;GAIG;AACU,QAAA,kBAAkB,GAAqC,MAAM,CAAC,MAAM,CAAC;IAC9E,OAAO,EAAE,gBAAQ,CAAC,KAAK;IACvB,YAAY,EAAE,gBAAQ,CAAC,QAAQ;IAC/B,MAAM,EAAE,gBAAQ,CAAC,QAAQ;IACzB,WAAW,EAAE,gBAAQ,CAAC,QAAQ;IAC9B,QAAQ,EAAE,gBAAQ,CAAC,MAAM;IACzB,eAAe,EAAE,gBAAQ,CAAC,YAAY;IACtC,SAAS,EAAE,gBAAQ,CAAC,YAAY;IAChC,OAAO,EAAE,gBAAQ,CAAC,YAAY;IAC9B,YAAY,EAAE,gBAAQ,CAAC,IAAI;IAC3B,MAAM,EAAE,gBAAQ,CAAC,IAAI;IACrB,QAAQ,EAAE,gBAAQ,CAAC,MAAM;CAC5B,CAAC,CAAC;AAEH,6CAA6C;AAChC,QAAA,SAAS,GAA4B,MAAM,CAAC,MAAM,CAAC;IAC5D,MAAM,EAAE,gBAAQ,CAAC,KAAK;IACtB,SAAS,EAAE,OAAO;IAClB,QAAQ,EAAE,EAAE;CACf,CAAC,CAAC;AAEH;;;GAGG;AACU,QAAA,qBAAqB,GAC9B,2FAA2F,CAAC;AAEhG;;;GAGG;AACH,SAAgB,cAAc,CAAC,MAAc;IACzC,OAAO,MAAM,KAAK,gBAAQ,CAAC,QAAQ,IAAI,MAAM,KAAK,gBAAQ,CAAC,MAAM,IAAI,MAAM,KAAK,gBAAQ,CAAC,MAAM,CAAC;AACpG,CAAC;AAED;;;;;;;;GAQG;AACU,QAAA,kBAAkB,GAAsB,MAAM,CAAC,MAAM,CAAC;IAC/D,gBAAQ,CAAC,YAAY;IACrB,gBAAQ,CAAC,MAAM;IACf,gBAAQ,CAAC,QAAQ;IACjB,gBAAQ,CAAC,MAAM;IACf,gBAAQ,CAAC,IAAI;CAChB,CAAC,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,MAAM,cAAc,GAAsB;IACtC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK;IAC3E,gBAAgB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,gBAAgB;IACtE,gBAAgB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,gBAAgB;IACtE,gBAAgB,EAAE,gBAAgB;CACrC,CAAC;AAEF,yEAAyE;AAC5D,QAAA,mBAAmB,GAC5B,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,oCAAoC,CAAC;AACpE,QAAA,YAAY,GAAG,IAAI,MAAM,CAAC,2BAAmB,EAAE,GAAG,CAAC,CAAC;AAEjE;;;;;GAKG;AACH,SAAgB,cAAc,CAAC,OAAe;IAC1C,OAAO,oBAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACtC,CAAC;AAED;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,OAAe;IACzC,IAAI,IAAI,GAAG,OAAO,CAAC;IACnB,sEAAsE;IACtE,OAAO,oBAAY,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAY,EAAE,EAAE,CAAC,CAAC;IACtE,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,IAAY;IAC1C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;QACpB,IAAI,EAAE,KAAK,GAAG;YAAE,MAAM,IAAI,IAAI,CAAC;aAC1B,IAAI,EAAE,KAAK,GAAG;YAAE,MAAM,IAAI,GAAG,CAAC;;YAC9B,MAAM,IAAI,EAAE,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,IAAI,MAAM,GAAG,CAAC;AACzB,CAAC;AAED,mDAAmD;AACnD,SAAgB,cAAc,CAAC,KAAa;IACxC,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;AACnE,CAAC;AAED,qFAAqF;AACrF,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAE3C,SAAS,cAAc,CAAC,MAAc;IAClC,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACxG,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,aAAa,CAAC,MAAc;IACxC,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;IACxC,IAAI,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,0BAAkB,EAAE,IAAI,CAAC,EAAE,CAAC;QACvF,OAAO,EAAC,MAAM,EAAE,0BAAkB,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAC,CAAC;IACnG,CAAC;IACD,OAAO,EAAC,MAAM,EAAE,gBAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAC,CAAC;AAClE,CAAC;AAED;;;;GAIG;AACH,SAAgB,YAAY,CAAC,MAA0B;IACnD,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,EAAC,GAAG,EAAE,iBAAS,EAAE,KAAK,EAAE,OAAO,EAAC,CAAC;IAC1F,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,4BAAmB,CAAC,WAAW,MAAM,2BAA2B,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,EAAC,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,EAAC,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,mBAAmB,CAAC,IAAY;IAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,EAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAC,CAAC;IACtD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,SAAS,GAAG,EAAE,CAAC;QACnB,IAAI,qCAAqC,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS,GAAG,4BAA4B,CAAC;QAC/F,oEAAoE;aAC/D,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS,GAAG,qBAAqB,CAAC;QAClE,mEAAmE;aAC9D,IAAI,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS,GAAG,aAAa,CAAC;aACpE,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAAE,SAAS,GAAG,gBAAgB,CAAC;QAC3D,kEAAkE;aAC7D,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,8BAA8B,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YACrG,SAAS,GAAG,0BAA0B,CAAC;QAC3C,CAAC;QACD,IAAI,CAAC,SAAS;YAAE,SAAS;QACzB,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QACpD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,OAAO,KAAK,CAAC;QAC/B,OAAO,EAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,SAAS,EAAC,CAAC;IACvE,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["// Platform-neutral mail helpers: folder strings, reply prefixes, and splitting a\n// reply from the thread it quotes. Pure functions, usable without Outlook, and\n// shared by both platforms so neither can come to read these differently.\nimport { InvalidRequestError } from './errors';\nimport type { MailFolderRef } from './types';\n\n/**\n * Outlook's olDefaultFolders ids for the folders this package addresses by\n * role rather than by name. The ids are Outlook's own, and a folder's NAME is\n * localized, so a role is always resolved through its id.\n */\nexport const FolderId = Object.freeze({\n DeletedItems: 3,\n Outbox: 4,\n SentMail: 5,\n Inbox: 6,\n Drafts: 16,\n Junk: 23,\n});\n\n/**\n * The folders addressable by name rather than by walking from the Inbox, with\n * their olDefaultFolders id. A well-known name wins over a user folder of the\n * same name; reach the latter by qualifying it (`Inbox\\Drafts`).\n */\nexport const WELL_KNOWN_FOLDERS: Readonly<Record<string, number>> = Object.freeze({\n 'inbox': FolderId.Inbox,\n 'sent items': FolderId.SentMail,\n 'sent': FolderId.SentMail,\n 'sent mail': FolderId.SentMail,\n 'drafts': FolderId.Drafts,\n 'deleted items': FolderId.DeletedItems,\n 'deleted': FolderId.DeletedItems,\n 'trash': FolderId.DeletedItems,\n 'junk email': FolderId.Junk,\n 'junk': FolderId.Junk,\n 'outbox': FolderId.Outbox,\n});\n\n/** The Inbox root, as a folder reference. */\nexport const INBOX_REF: Readonly<MailFolderRef> = Object.freeze({\n rootId: FolderId.Inbox,\n rootLabel: 'Inbox',\n segments: [],\n});\n\n/**\n * Why `deleteOutlookEmails` refuses an item, worded once for both platforms —\n * it reaches the caller verbatim as a per-item `reason`.\n */\nexport const PROTECTED_MAIL_REASON =\n 'received or sent mail (Inbox/Sent Items or a subfolder) - pass allowProtected to override';\n\n/**\n * Roots whose items are outgoing mail, which carries a sent time where incoming\n * mail carries a received one — so a date filter has to know which it's on.\n */\nexport function isOutgoingRoot(rootId: number): boolean {\n return rootId === FolderId.SentMail || rootId === FolderId.Outbox || rootId === FolderId.Drafts;\n}\n\n/**\n * Roots an Inbox walk must never descend into.\n *\n * On an Exchange profile these sit beside the Inbox and a walk from it can't\n * reach them anyway. On an IMAP profile Outlook nests them UNDER the Inbox, so\n * the same walk would hand back mail already sent, drafted or thrown away as if\n * it had just arrived. They are matched by id, never by name: the names are\n * localized.\n */\nexport const NON_INCOMING_ROOTS: readonly number[] = Object.freeze([\n FolderId.DeletedItems,\n FolderId.Outbox,\n FolderId.SentMail,\n FolderId.Drafts,\n FolderId.Junk,\n]);\n\n/**\n * Subject prefixes that mark a reply or a forward.\n *\n * Kept as a pattern SOURCE because it runs in two engines: macOS tests it as a\n * JavaScript RegExp, Windows inside PowerShell's .NET one, and both read every\n * construct used here the same way. Non-ASCII is written as \\uXXXX so the\n * generated PowerShell stays plain ASCII.\n *\n * Not only English: mail threaded in Chinese, Japanese or Korean replies under\n * 回复:/返信:/답장: and forwards under 转发:/転送:/전달:, and the European\n * clients use AW:, WG:, SV:, TR:, RV:, ENC: and friends. The colon may be the\n * full-width one a CJK input method types, and Outlook's reply counter (RE[2]:)\n * can sit before it.\n */\nconst REPLY_PREFIXES: readonly string[] = [\n 're', 'fwd?', 'aw', 'wg', 'sv', 'vs', 'vb', 'tr', 'res', 'rv', 'enc', 'odp',\n '\\\\u56DE\\\\u590D', '\\\\u56DE\\\\u8986', '\\\\u7B54\\\\u590D', '\\\\u7B54\\\\u8986',\n '\\\\u8F6C\\\\u53D1', '\\\\u8F49\\\\u767C', '\\\\u8FD4\\\\u4FE1', '\\\\u8EE2\\\\u9001',\n '\\\\uB2F5\\\\uC7A5', '\\\\uC804\\\\uB2EC',\n];\n\n/** The reply/forward prefixes as one case-insensitive pattern source. */\nexport const REPLY_PREFIX_SOURCE =\n '^\\\\s*(?:' + REPLY_PREFIXES.join('|') + ')\\\\s*(?:\\\\[\\\\d+\\\\])?\\\\s*[:\\\\uFF1A]';\nexport const REPLY_PREFIX = new RegExp(REPLY_PREFIX_SOURCE, 'i');\n\n/**\n * Whether a subject opens on a reply or forward prefix.\n *\n * This reads what the subject claims, not how the email was made: a forward\n * carries a prefix too, and a client can leave one off a real reply.\n */\nexport function hasReplyPrefix(subject: string): boolean {\n return REPLY_PREFIX.test(subject);\n}\n\n/**\n * The subject every message in a thread shares: every reply and forward prefix\n * stripped, however many clients stacked them (`RE: FW: RE[2]: Rate request`\n * is `Rate request`), and whitespace collapsed. Case is kept, so lower-case\n * both sides to compare two threads.\n */\nexport function threadSubject(subject: string): string {\n let rest = subject;\n // Each pass removes at least the prefix's colon, so this always ends.\n while (REPLY_PREFIX.test(rest)) rest = rest.replace(REPLY_PREFIX, '');\n return rest.replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * A subject glob as an anchored regex source: `*` matches any run, `?` one\n * character, everything else itself. Written with constructs JavaScript and\n * .NET read identically, so both platforms apply exactly the same test.\n */\nexport function subjectGlobSource(glob: string): string {\n let source = '';\n for (const ch of glob) {\n if (ch === '*') source += '.*';\n else if (ch === '?') source += '.';\n else source += ch.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n }\n return `^${source}$`;\n}\n\n/** The leaf name of a folder path or bare name. */\nexport function folderLeafName(entry: string): string {\n return entry.trim().split(/[\\\\/]/).filter(Boolean).pop() ?? '';\n}\n\n/** A full Outlook folder path starts with the mailbox: `\\\\mailbox@example.com\\…`. */\nconst MAILBOX_PREFIX = /^\\\\\\\\[^\\\\/]+[\\\\/]/;\n\nfunction folderSegments(folder: string): string[] {\n return folder.trim().replace(MAILBOX_PREFIX, '').split(/[\\\\/]+/).map(s => s.trim()).filter(Boolean);\n}\n\n/**\n * Resolve a folder string to a well-known root plus the segments below it.\n *\n * Accepts every shape a person has to hand: a bare name ('Invoices'), a\n * relative path ('Invoices\\Paid'), a well-known folder ('Sent Items'), a path\n * under one ('Deleted Items\\2026'), and the full path listInboxFolders prints\n * ('\\\\team@example.com\\Inbox\\Invoices'). Anything not starting at a well-known\n * folder is taken as relative to the Inbox.\n */\nexport function mailFolderRef(folder: string): MailFolderRef {\n const segments = folderSegments(folder);\n const head = segments[0]?.toLowerCase();\n if (head !== undefined && Object.prototype.hasOwnProperty.call(WELL_KNOWN_FOLDERS, head)) {\n return {rootId: WELL_KNOWN_FOLDERS[head], rootLabel: segments[0], segments: segments.slice(1)};\n }\n return {rootId: FolderId.Inbox, rootLabel: 'Inbox', segments};\n}\n\n/**\n * A caller's folder option as a reference: none given means the Inbox root,\n * and a string that names no folder at all (only separators) is refused rather\n * than silently read as the Inbox root.\n */\nexport function folderOption(folder: string | undefined): { ref: MailFolderRef; label: string } {\n if (folder === undefined || folder.trim() === '') return {ref: INBOX_REF, label: 'Inbox'};\n if (folderSegments(folder).length === 0) {\n throw new InvalidRequestError(`Folder '${folder}' does not name a folder.`);\n }\n return {ref: mailFolderRef(folder), label: folder.trim()};\n}\n\n/**\n * Split a plain-text reply into the sender's own text and the quoted thread\n * below it.\n *\n * This matters more than it looks: the quoted original carries the text of the\n * message being answered, and handing a reader one blob invites a figure from\n * that message to be read back as the sender's own.\n *\n * Splits at the earliest separator Outlook or the common webmail clients emit.\n * If that would leave no new text at all — a bottom-posted reply, or a body\n * that opens on a quote — the split is abandoned and everything is `body`,\n * since losing the sender's actual words is the one outcome worth avoiding.\n */\nexport function splitQuotedOriginal(text: string): { body: string; quoted: string; separator: string } {\n const lines = text.split(/\\r?\\n/);\n const whole = {body: text, quoted: '', separator: ''};\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n let separator = '';\n if (/^-{2,}\\s*original message\\s*-{2,}$/i.test(line)) separator = '-----Original Message-----';\n // Outlook's HTML thread rule, directly above the From:/Sent: block.\n else if (/^_{10,}$/.test(line)) separator = 'Outlook thread rule';\n // \"On Tue, Jul 21, 2026 at 9:14 AM Jo Doe <jo@example.com> wrote:\"\n else if (/^on\\b.{5,300}\\bwrote:$/i.test(line)) separator = 'On … wrote:';\n else if (/^>/.test(lines[i])) separator = '> quoted lines';\n // A bare header block: \"From: …\" followed by another header line.\n else if (/^from:\\s*\\S/i.test(line) && /^(sent|date|to|subject|cc):/i.test((lines[i + 1] || '').trim())) {\n separator = 'From:/Sent: header block';\n }\n if (!separator) continue;\n const body = lines.slice(0, i).join('\\n').trimEnd();\n if (!body.trim()) return whole;\n return {body, quoted: lines.slice(i).join('\\n').trim(), separator};\n }\n return whole;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"mail.js","sourceRoot":"","sources":["../src/mail.ts"],"names":[],"mappings":"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","sourcesContent":["// Platform-neutral mail helpers: folder strings, reply prefixes, splitting a\n// reply from the thread it quotes, and what deleting mail reports. Pure\n// functions, usable without Outlook, and shared by both platforms so neither can\n// come to read these differently.\nimport { InvalidRequestError } from './errors';\nimport type { DeleteMailOutcome, DeleteMailResult, MailFolderRef } from './types';\n\n/**\n * Outlook's olDefaultFolders ids for the folders this package addresses by\n * role rather than by name. The ids are Outlook's own, and a folder's NAME is\n * localized, so a role is always resolved through its id.\n */\nexport const FolderId = Object.freeze({\n DeletedItems: 3,\n Outbox: 4,\n SentMail: 5,\n Inbox: 6,\n Drafts: 16,\n Junk: 23,\n});\n\n/**\n * The folders addressable by name rather than by walking from the Inbox, with\n * their olDefaultFolders id. A well-known name wins over a user folder of the\n * same name; reach the latter by qualifying it (`Inbox\\Drafts`).\n */\nexport const WELL_KNOWN_FOLDERS: Readonly<Record<string, number>> = Object.freeze({\n 'inbox': FolderId.Inbox,\n 'sent items': FolderId.SentMail,\n 'sent': FolderId.SentMail,\n 'sent mail': FolderId.SentMail,\n 'drafts': FolderId.Drafts,\n 'deleted items': FolderId.DeletedItems,\n 'deleted': FolderId.DeletedItems,\n 'trash': FolderId.DeletedItems,\n 'junk email': FolderId.Junk,\n 'junk': FolderId.Junk,\n 'outbox': FolderId.Outbox,\n});\n\n/** The Inbox root, as a folder reference. */\nconst INBOX_REF: Readonly<MailFolderRef> = Object.freeze({\n rootId: FolderId.Inbox,\n rootLabel: 'Inbox',\n segments: [],\n});\n\n/**\n * Roots whose contents are received or sent mail rather than working state —\n * what `deleteOutlookEmails` refuses to touch, subfolders included, unless\n * allowed. Matched by id on both platforms, as every role is.\n */\nexport const PROTECTED_ROOTS: readonly number[] = Object.freeze([FolderId.Inbox, FolderId.SentMail]);\n\n/**\n * Why `deleteOutlookEmails` refuses an item, worded once for both platforms —\n * it reaches the caller verbatim as a per-item `reason`.\n */\nexport const PROTECTED_MAIL_REASON =\n 'received or sent mail (Inbox/Sent Items or a subfolder) - pass allowProtected to override';\n\nconst DELETE_STATUSES: readonly DeleteMailOutcome['status'][] = ['deleted', 'would-delete', 'refused', 'failed'];\n\n/** The status a delete script reported for one item; anything it shouldn't have said is a failure. */\nexport function deleteStatus(reported: string): DeleteMailOutcome['status'] {\n return DELETE_STATUSES.find(status => status === reported) ?? 'failed';\n}\n\n/** A deleteOutlookEmails result: every outcome, and how many ended each way. */\nexport function deleteMailResult(dryRun: boolean, items: DeleteMailOutcome[]): DeleteMailResult {\n const tally = (status: DeleteMailOutcome['status']) => items.filter(item => item.status === status).length;\n return { dryRun, deleted: tally('deleted'), refused: tally('refused'), failed: tally('failed'), items };\n}\n\n/**\n * Roots whose items are outgoing mail, which carries a sent time where incoming\n * mail carries a received one — so a date filter has to know which it's on.\n */\nexport function isOutgoingRoot(rootId: number): boolean {\n return rootId === FolderId.SentMail || rootId === FolderId.Outbox || rootId === FolderId.Drafts;\n}\n\n/**\n * Roots an Inbox walk must never descend into.\n *\n * On an Exchange profile these sit beside the Inbox and a walk from it can't\n * reach them anyway. On an IMAP profile Outlook nests them UNDER the Inbox, so\n * the same walk would hand back mail already sent, drafted or thrown away as if\n * it had just arrived. They are matched by id, never by name: the names are\n * localized.\n */\nexport const NON_INCOMING_ROOTS: readonly number[] = Object.freeze([\n FolderId.DeletedItems,\n FolderId.Outbox,\n FolderId.SentMail,\n FolderId.Drafts,\n FolderId.Junk,\n]);\n\n/**\n * Subject prefixes that mark a reply or a forward.\n *\n * Kept as a pattern SOURCE because it runs in two engines: macOS tests it as a\n * JavaScript RegExp, Windows inside PowerShell's .NET one, and both read every\n * construct used here the same way. Non-ASCII is written as \\uXXXX so the\n * generated PowerShell stays plain ASCII.\n *\n * Not only English: mail threaded in Chinese, Japanese or Korean replies under\n * 回复:/返信:/답장: and forwards under 转发:/転送:/전달:, and the European\n * clients use AW:, WG:, SV:, TR:, RV:, ENC: and friends. The colon may be the\n * full-width one a CJK input method types, and Outlook's reply counter (RE[2]:)\n * can sit before it.\n */\nconst REPLY_PREFIXES: readonly string[] = [\n 're', 'fwd?', 'aw', 'wg', 'sv', 'vs', 'vb', 'tr', 'res', 'rv', 'enc', 'odp',\n '\\\\u56DE\\\\u590D', '\\\\u56DE\\\\u8986', '\\\\u7B54\\\\u590D', '\\\\u7B54\\\\u8986',\n '\\\\u8F6C\\\\u53D1', '\\\\u8F49\\\\u767C', '\\\\u8FD4\\\\u4FE1', '\\\\u8EE2\\\\u9001',\n '\\\\uB2F5\\\\uC7A5', '\\\\uC804\\\\uB2EC',\n];\n\n/** The reply/forward prefixes as one case-insensitive pattern source. */\nexport const REPLY_PREFIX_SOURCE =\n '^\\\\s*(?:' + REPLY_PREFIXES.join('|') + ')\\\\s*(?:\\\\[\\\\d+\\\\])?\\\\s*[:\\\\uFF1A]';\nexport const REPLY_PREFIX = new RegExp(REPLY_PREFIX_SOURCE, 'i');\n\n/**\n * Whether a subject opens on a reply or forward prefix.\n *\n * This reads what the subject claims, not how the email was made: a forward\n * carries a prefix too, and a client can leave one off a real reply.\n */\nexport function hasReplyPrefix(subject: string): boolean {\n return REPLY_PREFIX.test(subject);\n}\n\n/**\n * The subject every message in a thread shares: every reply and forward prefix\n * stripped, however many clients stacked them (`RE: FW: RE[2]: Rate request`\n * is `Rate request`), and whitespace collapsed. Case is kept, so lower-case\n * both sides to compare two threads.\n */\nexport function threadSubject(subject: string): string {\n let rest = subject;\n // Each pass removes at least the prefix's colon, so this always ends.\n while (REPLY_PREFIX.test(rest)) rest = rest.replace(REPLY_PREFIX, '');\n return rest.replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * A subject glob as an anchored regex source: `*` matches any run, `?` one\n * character, everything else itself. Written with constructs JavaScript and\n * .NET read identically, so both platforms apply exactly the same test.\n */\nexport function subjectGlobSource(glob: string): string {\n let source = '';\n for (const ch of glob) {\n if (ch === '*') source += '.*';\n else if (ch === '?') source += '.';\n else source += ch.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n }\n return `^${source}$`;\n}\n\n/** The leaf name of a folder path or bare name. */\nexport function folderLeafName(entry: string): string {\n return entry.trim().split(/[\\\\/]/).filter(Boolean).pop() ?? '';\n}\n\n/** A full Outlook folder path starts with the mailbox: `\\\\mailbox@example.com\\…`. */\nconst MAILBOX_PREFIX = /^\\\\\\\\[^\\\\/]+[\\\\/]/;\n\nfunction folderSegments(folder: string): string[] {\n return folder.trim().replace(MAILBOX_PREFIX, '').split(/[\\\\/]+/).map(s => s.trim()).filter(Boolean);\n}\n\n/**\n * Resolve a folder string to a well-known root plus the segments below it.\n *\n * Accepts every shape a person has to hand: a bare name ('Invoices'), a\n * relative path ('Invoices\\Paid'), a well-known folder ('Sent Items'), a path\n * under one ('Deleted Items\\2026'), and the full path listInboxFolders prints\n * ('\\\\team@example.com\\Inbox\\Invoices'). Anything not starting at a well-known\n * folder is taken as relative to the Inbox.\n */\nexport function mailFolderRef(folder: string): MailFolderRef {\n const segments = folderSegments(folder);\n const head = segments[0]?.toLowerCase();\n if (head !== undefined && Object.prototype.hasOwnProperty.call(WELL_KNOWN_FOLDERS, head)) {\n return { rootId: WELL_KNOWN_FOLDERS[head], rootLabel: segments[0], segments: segments.slice(1) };\n }\n return { rootId: FolderId.Inbox, rootLabel: 'Inbox', segments };\n}\n\n/**\n * A caller's folder option as a reference: none given means the Inbox root,\n * and a string that names no folder at all (only separators) is refused rather\n * than silently read as the Inbox root.\n */\nexport function folderOption(folder: string | undefined): { ref: MailFolderRef; label: string } {\n if (folder === undefined || folder.trim() === '') return { ref: INBOX_REF, label: 'Inbox' };\n if (folderSegments(folder).length === 0) {\n throw new InvalidRequestError(`Folder '${folder}' does not name a folder.`);\n }\n return { ref: mailFolderRef(folder), label: folder.trim() };\n}\n\n/**\n * Split a plain-text reply into the sender's own text and the quoted thread\n * below it.\n *\n * This matters more than it looks: the quoted original carries the text of the\n * message being answered, and handing a reader one blob invites a figure from\n * that message to be read back as the sender's own.\n *\n * Splits at the earliest separator Outlook or the common webmail clients emit.\n * If that would leave no new text at all — a bottom-posted reply, or a body\n * that opens on a quote — the split is abandoned and everything is `body`,\n * since losing the sender's actual words is the one outcome worth avoiding.\n */\nexport function splitQuotedOriginal(text: string): { body: string; quoted: string; separator: string } {\n const lines = text.split(/\\r?\\n/);\n const whole = { body: text, quoted: '', separator: '' };\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i].trim();\n let separator = '';\n if (/^-{2,}\\s*original message\\s*-{2,}$/i.test(line)) separator = '-----Original Message-----';\n // Outlook's HTML thread rule, directly above the From:/Sent: block.\n else if (/^_{10,}$/.test(line)) separator = 'Outlook thread rule';\n // \"On Tue, Jul 21, 2026 at 9:14 AM Jo Doe <jo@example.com> wrote:\"\n else if (/^on\\b.{5,300}\\bwrote:$/i.test(line)) separator = 'On … wrote:';\n else if (/^>/.test(lines[i])) separator = '> quoted lines';\n // A bare header block: \"From: …\" followed by another header line.\n else if (/^from:\\s*\\S/i.test(line) && /^(sent|date|to|subject|cc):/i.test((lines[i + 1] || '').trim())) {\n separator = 'From:/Sent: header block';\n }\n if (!separator) continue;\n const body = lines.slice(0, i).join('\\n').trimEnd();\n if (!body.trim()) return whole;\n return { body, quoted: lines.slice(i).join('\\n').trim(), separator };\n }\n return whole;\n}\n"]}
|
package/dist/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["// Settings, and the scratch files every run needs.\n//\n// Each operation generates a script and runs it under an interpreter, which\n// leaves a caller four things they cannot otherwise reach: how long a run may\n// take, how much it may print, how to call it off, and what the script actually\n// said. Those are the settings here — process-wide through `configure()`, or per\n// bridge through `createOutlookBridge()`.\nimport { AsyncLocalStorage } from 'async_hooks';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport { InvalidRequestError, type ScriptRunner } from './errors';\n\n/** One completed script run, handed to the `debug` hook. */\nexport interface BridgeDebugEvent {\n runner: ScriptRunner;\n /** The script exactly as it ran. */\n script: string;\n durationMs: number;\n /** Present only when the run failed. */\n error?: string;\n}\n\nexport interface BridgeOptions {\n /**\n * Ceiling on every script run, in milliseconds, replacing each operation's\n * own budget; 0 disables timeouts. Leave unset (or pass null to go back) to\n * let each operation use a budget sized for its work — a minute for a\n * lookup, up to ten for purging Deleted Items. `editEmailTemplate` waits on\n * a person and is never timed; cancel it with `signal`.\n */\n timeoutMs?: number | null;\n /**\n * Max bytes a script may print before the run is killed with\n * OUTPUT_TOO_LARGE. Searches that return whole bodies push against this.\n * Default 8 MiB.\n */\n maxBufferBytes?: number;\n /** Directory for generated scripts, and for saved attachments when a call\n * names no destination. Default: the OS temp directory. */\n tempDir?: string;\n /**\n * Cancels in-flight runs: the interpreter is killed and the call rejects\n * with ABORTED. Most useful scoped to the calls it should cancel:\n * `bridge.withOptions({ signal }).searchInboxByFilter(...)`.\n */\n signal?: AbortSignal;\n /** `true` logs every script to stderr; a function receives each run\n * instead. Default: on when OUTLOOK_BRIDGE_DEBUG is set to anything but '' or '0'. */\n debug?: boolean | ((event: BridgeDebugEvent) => void);\n}\n\n/** The settings a call runs with, every default applied. */\nexport interface ResolvedConfig {\n /** undefined = each operation's own budget. */\n readonly timeoutMs: number | undefined;\n readonly maxBufferBytes: number;\n readonly tempDir: string;\n readonly signal: AbortSignal | undefined;\n readonly debug: boolean | ((event: BridgeDebugEvent) => void);\n}\n\nconst envDebug = process.env.OUTLOOK_BRIDGE_DEBUG;\n\n/** The process-wide settings, as `configure()` leaves them. */\nlet globalConfig: ResolvedConfig = {\n timeoutMs: undefined,\n maxBufferBytes: 8 * 1024 * 1024,\n tempDir: os.tmpdir(),\n signal: undefined,\n debug: !!envDebug && envDebug !== '0',\n};\n\n/**\n * Per-bridge settings, scoped to an async call tree.\n *\n * The platform code reads its settings through `getConfig()` wherever it needs\n * them rather than having a config threaded through every function. A bridge\n * instance runs each call inside `withConfig`, and the store follows every\n * `await` beneath it — so two bridges with different timeouts can run at once\n * in one process without seeing each other's.\n */\nconst scoped = new AsyncLocalStorage<ResolvedConfig>();\n\n/** Caller options merged over a base; keys left out keep the base's value. */\nexport function mergeOptions(base: ResolvedConfig, options: BridgeOptions = {}): ResolvedConfig {\n const next = {...base};\n if (options.timeoutMs === null) next.timeoutMs = undefined;\n else if (options.timeoutMs !== undefined) next.timeoutMs = nonNegative(options.timeoutMs, 'timeoutMs');\n if (options.maxBufferBytes !== undefined) next.maxBufferBytes = Math.max(1, nonNegative(options.maxBufferBytes, 'maxBufferBytes'));\n if (options.tempDir !== undefined) next.tempDir = options.tempDir;\n if (options.signal !== undefined) next.signal = options.signal;\n if (options.debug !== undefined) next.debug = options.debug;\n return next;\n}\n\nfunction nonNegative(value: number, name: string): number {\n if (typeof value !== 'number' || Number.isNaN(value)) {\n throw new InvalidRequestError(`${name} must be a number (got ${String(value)}).`);\n }\n return Math.max(0, Math.floor(value));\n}\n\n/**\n * Change the process-wide settings. Merges, so naming one option leaves the\n * rest alone.\n *\n * Affects every caller in the process that uses the exported functions. Prefer\n * `createOutlookBridge(options)` in a process shared with code you don't own.\n */\nexport function configure(options: BridgeOptions): void {\n globalConfig = mergeOptions(globalConfig, options);\n}\n\n/** The settings in force for the current call: the enclosing bridge's, else the process-wide ones. */\nexport function getConfig(): ResolvedConfig {\n return scoped.getStore() ?? globalConfig;\n}\n\n/** The process-wide settings, ignoring any bridge scope. */\nexport function getGlobalConfig(): ResolvedConfig {\n return globalConfig;\n}\n\n/** Run `fn` with `config` in force for it and everything it awaits. */\nexport function withConfig<T>(config: ResolvedConfig, fn: () => T): T {\n return scoped.run(config, fn);\n}\n\n/**\n * How long each kind of run may take when no `timeoutMs` is set.\n *\n * Generous on purpose: a budget is a guard against a hung interpreter, not a\n * performance target, and the first call against an Outlook that isn't running\n * yet spends much of its budget starting it.\n */\nexport const RUN_BUDGETS = {\n /** One lookup: accounts, signatures, opening or reading a single email. */\n quick: 60_000,\n /** Composing, filing, saving attachments, or reading one folder. */\n standard: 120_000,\n /** Walking a mailbox tree, or acting on a batch of items. */\n scan: 300_000,\n /** Emptying Deleted Items. */\n purge: 600_000,\n} as const;\n\n/** A budget name, or 'interactive' for a run that waits on a person and is never timed. */\nexport type RunBudget = keyof typeof RUN_BUDGETS | 'interactive';\n\n/** The timeout one run gets: the configured ceiling if there is one, else its budget. */\nexport function timeoutFor(budget: RunBudget): number {\n if (budget === 'interactive') return 0;\n return getConfig().timeoutMs ?? RUN_BUDGETS[budget];\n}\n\n/** Report one finished run to the configured debug hook. */\nexport function reportRun(event: BridgeDebugEvent): void {\n const {debug} = getConfig();\n if (!debug) return;\n if (typeof debug === 'function') {\n debug(event);\n return;\n }\n const outcome = event.error ? `FAILED: ${event.error}` : 'ok';\n process.stderr.write(`[outlook-bridge] ${event.runner} ${event.durationMs}ms ${outcome}\\n${event.script}\\n`);\n}\n\n/**\n * A scratch-file path in the configured temp directory. The random part\n * matters: two concurrent calls in the same millisecond would otherwise pick the\n * same name and overwrite each other's script mid-run.\n */\nexport function tempFile(kind: string, extension: string): string {\n const unique = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n return path.join(getConfig().tempDir, `outlook-bridge-${kind}-${unique}.${extension}`);\n}\n\n/** Delete a scratch file, ignoring one that is already gone. */\nexport function removeTempFile(file: string): void {\n try {\n fs.unlinkSync(file);\n } catch {\n // The run is over either way; a leftover scratch file isn't worth failing it.\n }\n}\n\n/** A destination for saved attachments, and how to undo creating it. */\nexport interface AttachmentDestination {\n dir: string;\n\n /** Remove the directory if this call created it and left it empty. */\n discardIfUnused(): void;\n}\n\n/**\n * Where one call saves its attachments: the caller's directory (created if\n * absent), or a fresh private directory of its own.\n *\n * Private by default because attachments keep the names senders gave them, so\n * one shared folder means two emails carrying 'invoice.pdf' overwrite each\n * other — and so do two programs using this package on one machine.\n */\nexport function attachmentDestination(destDir?: string): AttachmentDestination {\n if (destDir) {\n const existed = fs.existsSync(destDir);\n fs.mkdirSync(destDir, {recursive: true});\n return {\n dir: destDir,\n discardIfUnused() {\n if (!existed) removeIfEmpty(destDir);\n },\n };\n }\n const dir = fs.mkdtempSync(path.join(getConfig().tempDir, 'outlook-bridge-attachments-'));\n return {dir, discardIfUnused: () => removeIfEmpty(dir)};\n}\n\nfunction removeIfEmpty(dir: string): void {\n try {\n if (fs.readdirSync(dir).length === 0) fs.rmdirSync(dir);\n } catch {\n // Nothing to undo if it can't be read.\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["// Settings, and the scratch files every run needs.\n//\n// Each operation generates a script and runs it under an interpreter, which\n// leaves a caller four things they cannot otherwise reach: how long a run may\n// take, how much it may print, how to call it off, and what the script actually\n// said. Those are the settings here — process-wide through `configure()`, or per\n// bridge through `createOutlookBridge()`.\nimport { AsyncLocalStorage } from 'async_hooks';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport { InvalidRequestError, type ScriptRunner } from './errors';\n\n/** One completed script run, handed to the `debug` hook. */\nexport interface BridgeDebugEvent {\n runner: ScriptRunner;\n /** The script exactly as it ran. */\n script: string;\n durationMs: number;\n /** Present only when the run failed. */\n error?: string;\n}\n\nexport interface BridgeOptions {\n /**\n * Ceiling on every script run, in milliseconds, replacing each operation's\n * own budget; 0 disables timeouts. Leave unset (or pass null to go back) to\n * let each operation use a budget sized for its work — a minute for a\n * lookup, up to ten for purging Deleted Items. `editEmailTemplate` waits on\n * a person and is never timed; cancel it with `signal`.\n */\n timeoutMs?: number | null;\n /**\n * Max bytes a script may print before the run is killed with\n * OUTPUT_TOO_LARGE. Searches that return whole bodies push against this.\n * Default 8 MiB.\n */\n maxBufferBytes?: number;\n /** Directory for generated scripts, and for saved attachments when a call\n * names no destination. Default: the OS temp directory. */\n tempDir?: string;\n /**\n * Cancels in-flight runs: the interpreter is killed and the call rejects\n * with ABORTED. Most useful scoped to the calls it should cancel:\n * `bridge.withOptions({ signal }).searchInboxByFilter(...)`.\n */\n signal?: AbortSignal;\n /** `true` logs every script to stderr; a function receives each run\n * instead. Default: on when OUTLOOK_BRIDGE_DEBUG is set to anything but '' or '0'. */\n debug?: boolean | ((event: BridgeDebugEvent) => void);\n}\n\n/** The settings a call runs with, every default applied. */\nexport interface ResolvedConfig {\n /** undefined = each operation's own budget. */\n readonly timeoutMs: number | undefined;\n readonly maxBufferBytes: number;\n readonly tempDir: string;\n readonly signal: AbortSignal | undefined;\n readonly debug: boolean | ((event: BridgeDebugEvent) => void);\n}\n\nconst envDebug = process.env.OUTLOOK_BRIDGE_DEBUG;\n\n/** The process-wide settings, as `configure()` leaves them. */\nlet globalConfig: ResolvedConfig = {\n timeoutMs: undefined,\n maxBufferBytes: 8 * 1024 * 1024,\n tempDir: os.tmpdir(),\n signal: undefined,\n debug: !!envDebug && envDebug !== '0',\n};\n\n/**\n * Per-bridge settings, scoped to an async call tree.\n *\n * The platform code reads its settings through `getConfig()` wherever it needs\n * them rather than having a config threaded through every function. A bridge\n * instance runs each call inside `withConfig`, and the store follows every\n * `await` beneath it — so two bridges with different timeouts can run at once\n * in one process without seeing each other's.\n */\nconst scoped = new AsyncLocalStorage<ResolvedConfig>();\n\n/** Caller options merged over a base; keys left out keep the base's value. */\nexport function mergeOptions(base: ResolvedConfig, options: BridgeOptions = {}): ResolvedConfig {\n const next = { ...base };\n if (options.timeoutMs === null) next.timeoutMs = undefined;\n else if (options.timeoutMs !== undefined) next.timeoutMs = nonNegative(options.timeoutMs, 'timeoutMs');\n if (options.maxBufferBytes !== undefined) next.maxBufferBytes = Math.max(1, nonNegative(options.maxBufferBytes, 'maxBufferBytes'));\n if (options.tempDir !== undefined) next.tempDir = options.tempDir;\n if (options.signal !== undefined) next.signal = options.signal;\n if (options.debug !== undefined) next.debug = options.debug;\n return next;\n}\n\nfunction nonNegative(value: number, name: string): number {\n if (typeof value !== 'number' || Number.isNaN(value)) {\n throw new InvalidRequestError(`${name} must be a number (got ${String(value)}).`);\n }\n return Math.max(0, Math.floor(value));\n}\n\n/**\n * Change the process-wide settings. Merges, so naming one option leaves the\n * rest alone.\n *\n * Affects every caller in the process that uses the exported functions. Prefer\n * `createOutlookBridge(options)` in a process shared with code you don't own.\n */\nexport function configure(options: BridgeOptions): void {\n globalConfig = mergeOptions(globalConfig, options);\n}\n\n/** The settings in force for the current call: the enclosing bridge's, else the process-wide ones. */\nexport function getConfig(): ResolvedConfig {\n return scoped.getStore() ?? globalConfig;\n}\n\n/** The process-wide settings, ignoring any bridge scope. */\nexport function getGlobalConfig(): ResolvedConfig {\n return globalConfig;\n}\n\n/** Run `fn` with `config` in force for it and everything it awaits. */\nexport function withConfig<T>(config: ResolvedConfig, fn: () => T): T {\n return scoped.run(config, fn);\n}\n\n/**\n * How long each kind of run may take when no `timeoutMs` is set.\n *\n * Generous on purpose: a budget is a guard against a hung interpreter, not a\n * performance target, and the first call against an Outlook that isn't running\n * yet spends much of its budget starting it.\n */\nexport const RUN_BUDGETS = {\n /** One lookup: accounts, signatures, opening or reading a single email. */\n quick: 60_000,\n /** Composing, filing, saving attachments, or reading one folder. */\n standard: 120_000,\n /** Walking a mailbox tree, or acting on a batch of items. */\n scan: 300_000,\n /** Emptying Deleted Items. */\n purge: 600_000,\n} as const;\n\n/** A budget name, or 'interactive' for a run that waits on a person and is never timed. */\nexport type RunBudget = keyof typeof RUN_BUDGETS | 'interactive';\n\n/** The timeout one run gets: the configured ceiling if there is one, else its budget. */\nexport function timeoutFor(budget: RunBudget): number {\n if (budget === 'interactive') return 0;\n return getConfig().timeoutMs ?? RUN_BUDGETS[budget];\n}\n\n/** Report one finished run to the configured debug hook. */\nexport function reportRun(event: BridgeDebugEvent): void {\n const { debug } = getConfig();\n if (!debug) return;\n if (typeof debug === 'function') {\n debug(event);\n return;\n }\n const outcome = event.error ? `FAILED: ${event.error}` : 'ok';\n process.stderr.write(`[outlook-bridge] ${event.runner} ${event.durationMs}ms ${outcome}\\n${event.script}\\n`);\n}\n\n/**\n * A scratch-file path in the configured temp directory. The random part\n * matters: two concurrent calls in the same millisecond would otherwise pick the\n * same name and overwrite each other's script mid-run.\n */\nexport function tempFile(kind: string, extension: string): string {\n const unique = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n return path.join(getConfig().tempDir, `outlook-bridge-${kind}-${unique}.${extension}`);\n}\n\n/** Delete a scratch file, ignoring one that is already gone. */\nexport function removeTempFile(file: string): void {\n try {\n fs.unlinkSync(file);\n } catch {\n // The run is over either way; a leftover scratch file isn't worth failing it.\n }\n}\n\n/** A destination for saved attachments, and how to undo creating it. */\nexport interface AttachmentDestination {\n dir: string;\n\n /** Remove the directory if this call created it and left it empty. */\n discardIfUnused(): void;\n}\n\n/**\n * Where one call saves its attachments: the caller's directory (created if\n * absent), or a fresh private directory of its own.\n *\n * Private by default because attachments keep the names senders gave them, so\n * one shared folder means two emails carrying 'invoice.pdf' overwrite each\n * other — and so do two programs using this package on one machine.\n */\nexport function attachmentDestination(destDir?: string): AttachmentDestination {\n if (destDir) {\n const existed = fs.existsSync(destDir);\n fs.mkdirSync(destDir, { recursive: true });\n return {\n dir: destDir,\n discardIfUnused() {\n if (!existed) removeIfEmpty(destDir);\n },\n };\n }\n const dir = fs.mkdtempSync(path.join(getConfig().tempDir, 'outlook-bridge-attachments-'));\n return { dir, discardIfUnused: () => removeIfEmpty(dir) };\n}\n\nfunction removeIfEmpty(dir: string): void {\n try {\n if (fs.readdirSync(dir).length === 0) fs.rmdirSync(dir);\n } catch {\n // Nothing to undo if it can't be read.\n }\n}\n"]}
|
package/dist/shared/args.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/shared/args.ts"],"names":[],"mappings":"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","sourcesContent":["// Checking and normalizing what a caller passes, once, before either platform\n// sees it.\n//\n// This is a security boundary as much as a convenience. Arguments end up\n// inside generated scripts, and TypeScript's types vanish at run time: a\n// JavaScript caller, or a tool layer passing JSON through, can hand a string\n// where a count belongs. So every value is checked for its type here, every\n// count is made a whole number within its documented range, and the platform\n// code only ever receives values of the shape its script templates assume.\nimport { InvalidRequestError } from '../errors';\nimport type { EmailLocator, EmailRef, Recipients } from '../types';\n\nfunction describe(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'an array';\n return typeof value === 'string' ? `'${value}'` : typeof value;\n}\n\n/** A required parameters object. */\nexport function requireObject<T extends object>(value: T | undefined | null, name: string): Partial<T> {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an object (got ${describe(value)}).`);\n }\n return value;\n}\n\n/** An optional options object; undefined and null are an empty one. */\nexport function optionsObject<T extends object>(value: T | undefined | null, name = 'options'): Partial<T> {\n if (value === undefined || value === null) return {};\n return requireObject(value, name);\n}\n\n/** A required string, trimmed. Blank is an error. */\nexport function requiredText(value: unknown, name: string): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new InvalidRequestError(`${name} is required and must be a non-empty string (got ${describe(value)}).`);\n }\n return value.trim();\n}\n\n/** A required string kept exactly as given — content, not an identifier. May be empty. */\nexport function requiredContent(value: unknown, name: string): string {\n if (typeof value !== 'string') {\n throw new InvalidRequestError(`${name} must be a string (got ${describe(value)}).`);\n }\n return value;\n}\n\n/** Optional content kept exactly as given; undefined and null mean \"not given\". */\nexport function optionalContent(value: unknown, name: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n return requiredContent(value, name);\n}\n\n/** An optional regular expression. */\nexport function optionalRegExp(value: unknown, name: string): RegExp | undefined {\n if (value === undefined || value === null) return undefined;\n // Checked by tag rather than instanceof, so a RegExp from another realm passes.\n if (Object.prototype.toString.call(value) !== '[object RegExp]') {\n throw new InvalidRequestError(`${name} must be a RegExp (got ${describe(value)}).`);\n }\n return value as RegExp;\n}\n\n/** An optional string, trimmed; undefined, null and blank all mean \"not given\". */\nexport function optionalText(value: unknown, name: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== 'string') {\n throw new InvalidRequestError(`${name} must be a string (got ${describe(value)}).`);\n }\n return value.trim() || undefined;\n}\n\n/** An optional flag; undefined and null take the fallback. */\nexport function flag(value: unknown, name: string, fallback: boolean): boolean {\n if (value === undefined || value === null) return fallback;\n if (typeof value !== 'boolean') {\n throw new InvalidRequestError(`${name} must be true or false (got ${describe(value)}).`);\n }\n return value;\n}\n\n/**\n * An optional count: undefined and null take the fallback, a fraction is\n * rounded down, and the result is clamped into [min, max] — the documented\n * range is a guard, not a trap. Anything that isn't a finite number is an error.\n */\nexport function count(value: unknown, name: string, fallback: number, min: number, max: number): number {\n if (value === undefined || value === null) return fallback;\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new InvalidRequestError(`${name} must be a finite number (got ${describe(value)}).`);\n }\n return Math.min(max, Math.max(min, Math.floor(value)));\n}\n\n/** A list of strings, each non-blank and trimmed. Omitted is an empty list. */\nexport function textList(value: unknown, name: string): string[] {\n if (value === undefined || value === null) return [];\n if (!Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an array of strings (got ${describe(value)}).`);\n }\n return value.map((item, index) => requiredText(item, `${name}[${index}]`));\n}\n\n/** A list of names matched exactly as given — attachment file names, say. */\nexport function nameList(value: unknown, name: string): string[] {\n if (!Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an array of strings (got ${describe(value)}).`);\n }\n return value.map((item, index) => {\n if (typeof item !== 'string' || item.trim() === '') {\n throw new InvalidRequestError(`${name}[${index}] must be a non-empty string (got ${describe(item)}).`);\n }\n return item;\n });\n}\n\n/** The entry ids a batch operation acts on. Duplicates are dropped; order is kept. */\nexport function entryIdList(value: unknown, name = 'entryIds'): string[] {\n if (!Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an array of entry ids (got ${describe(value)}).`);\n }\n return [...new Set(value.map((id, index) => requiredText(id, `${name}[${index}]`)))];\n}\n\n/** Where an email lives, from an id or anything carrying a locator. */\nexport function emailLocator(email: EmailRef | undefined | null, name = 'email'): EmailLocator {\n if (typeof email === 'string') return {entryId: requiredText(email, name)};\n if (email && typeof email === 'object') {\n const entryId = requiredText(email.entryId, `${name}.entryId`);\n const storeId = optionalText(email.storeId, `${name}.storeId`);\n return storeId ? {entryId, storeId} : {entryId};\n }\n throw new InvalidRequestError(`${name} must be an entry id or an object with an entryId (got ${describe(email)}).`);\n}\n\n/** A string→string map, e.g. template placeholders. */\nexport function stringRecord(value: unknown, name: string): Record<string, string> | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== 'object' || Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an object of strings (got ${describe(value)}).`);\n }\n const out: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry !== 'string') {\n throw new InvalidRequestError(`${name}.${key} must be a string (got ${describe(entry)}).`);\n }\n out[key] = entry;\n }\n return out;\n}\n\n/**\n * Split a recipient list into its entries.\n *\n * Separators are commas and semicolons — but not inside a quoted display name\n * or between angle brackets, where `\"Doe, Jo\" <jo@example.com>` has a comma\n * that belongs to the name.\n */\nexport function recipientList(value: Recipients | undefined | null, name: string): string[] {\n if (value === undefined || value === null) return [];\n const parts = Array.isArray(value) ? value : [value];\n const out: string[] = [];\n parts.forEach((part, index) => {\n if (typeof part !== 'string') {\n throw new InvalidRequestError(`${name}[${index}] must be a string (got ${describe(part)}).`);\n }\n out.push(...splitAddressList(part));\n });\n return out;\n}\n\nfunction splitAddressList(text: string): string[] {\n const entries: string[] = [];\n let current = '';\n let quoted = false;\n let angled = false;\n for (const ch of text) {\n if (ch === '\"' && !angled) quoted = !quoted;\n else if (ch === '<' && !quoted) angled = true;\n else if (ch === '>' && !quoted) angled = false;\n if ((ch === ',' || ch === ';') && !quoted && !angled) {\n entries.push(current);\n current = '';\n } else {\n current += ch;\n }\n }\n entries.push(current);\n return entries.map(entry => entry.trim()).filter(Boolean);\n}\n\n/** One recipient entry as a display name and an address. */\nexport interface ParsedRecipient {\n name: string;\n address: string;\n}\n\n/** `\"Doe, Jo\" <jo@example.com>` → name + address; a bare address has no name. */\nexport function parseRecipient(entry: string): ParsedRecipient {\n const angled = /^(.*)<([^<>]+)>\\s*$/.exec(entry);\n if (!angled) return {name: '', address: entry.trim()};\n const name = angled[1].trim().replace(/^\"(.*)\"$/, '$1').trim();\n return {name, address: angled[2].trim()};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/shared/args.ts"],"names":[],"mappings":"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","sourcesContent":["// Checking and normalizing what a caller passes, once, before either platform\n// sees it.\n//\n// This is a security boundary as much as a convenience. Arguments end up\n// inside generated scripts, and TypeScript's types vanish at run time: a\n// JavaScript caller, or a tool layer passing JSON through, can hand a string\n// where a count belongs. So every value is checked for its type here, every\n// count is made a whole number within its documented range, and the platform\n// code only ever receives values of the shape its script templates assume.\nimport { InvalidRequestError } from '../errors';\nimport type { EmailLocator, EmailRef, Recipients } from '../types';\n\nfunction describe(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'an array';\n return typeof value === 'string' ? `'${value}'` : typeof value;\n}\n\n/** A required parameters object. */\nexport function requireObject<T extends object>(value: T | undefined | null, name: string): Partial<T> {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an object (got ${describe(value)}).`);\n }\n return value;\n}\n\n/** An optional options object; undefined and null are an empty one. */\nexport function optionsObject<T extends object>(value: T | undefined | null, name = 'options'): Partial<T> {\n if (value === undefined || value === null) return {};\n return requireObject(value, name);\n}\n\n/** A required string, trimmed. Blank is an error. */\nexport function requiredText(value: unknown, name: string): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new InvalidRequestError(`${name} is required and must be a non-empty string (got ${describe(value)}).`);\n }\n return value.trim();\n}\n\n/** A required string kept exactly as given — content, not an identifier. May be empty. */\nexport function requiredContent(value: unknown, name: string): string {\n if (typeof value !== 'string') {\n throw new InvalidRequestError(`${name} must be a string (got ${describe(value)}).`);\n }\n return value;\n}\n\n/** Optional content kept exactly as given; undefined and null mean \"not given\". */\nexport function optionalContent(value: unknown, name: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n return requiredContent(value, name);\n}\n\n/** An optional regular expression. */\nexport function optionalRegExp(value: unknown, name: string): RegExp | undefined {\n if (value === undefined || value === null) return undefined;\n // Checked by tag rather than instanceof, so a RegExp from another realm passes.\n if (Object.prototype.toString.call(value) !== '[object RegExp]') {\n throw new InvalidRequestError(`${name} must be a RegExp (got ${describe(value)}).`);\n }\n return value as RegExp;\n}\n\n/** An optional string, trimmed; undefined, null and blank all mean \"not given\". */\nexport function optionalText(value: unknown, name: string): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== 'string') {\n throw new InvalidRequestError(`${name} must be a string (got ${describe(value)}).`);\n }\n return value.trim() || undefined;\n}\n\n/** An optional flag; undefined and null take the fallback. */\nexport function flag(value: unknown, name: string, fallback: boolean): boolean {\n if (value === undefined || value === null) return fallback;\n if (typeof value !== 'boolean') {\n throw new InvalidRequestError(`${name} must be true or false (got ${describe(value)}).`);\n }\n return value;\n}\n\n/**\n * An optional count: undefined and null take the fallback, a fraction is\n * rounded down, and the result is clamped into [min, max] — the documented\n * range is a guard, not a trap. Anything that isn't a finite number is an error.\n */\nexport function count(value: unknown, name: string, fallback: number, min: number, max: number): number {\n if (value === undefined || value === null) return fallback;\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new InvalidRequestError(`${name} must be a finite number (got ${describe(value)}).`);\n }\n return Math.min(max, Math.max(min, Math.floor(value)));\n}\n\n/** A list of strings, each non-blank and trimmed. Omitted is an empty list. */\nexport function textList(value: unknown, name: string): string[] {\n if (value === undefined || value === null) return [];\n if (!Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an array of strings (got ${describe(value)}).`);\n }\n return value.map((item, index) => requiredText(item, `${name}[${index}]`));\n}\n\n/** A list of names matched exactly as given — attachment file names, say. */\nexport function nameList(value: unknown, name: string): string[] {\n if (!Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an array of strings (got ${describe(value)}).`);\n }\n return value.map((item, index) => {\n if (typeof item !== 'string' || item.trim() === '') {\n throw new InvalidRequestError(`${name}[${index}] must be a non-empty string (got ${describe(item)}).`);\n }\n return item;\n });\n}\n\n/** The entry ids a batch operation acts on. Duplicates are dropped; order is kept. */\nexport function entryIdList(value: unknown, name = 'entryIds'): string[] {\n if (!Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an array of entry ids (got ${describe(value)}).`);\n }\n return [...new Set(value.map((id, index) => requiredText(id, `${name}[${index}]`)))];\n}\n\n/** Where an email lives, from an id or anything carrying a locator. */\nexport function emailLocator(email: EmailRef | undefined | null, name = 'email'): EmailLocator {\n if (typeof email === 'string') return { entryId: requiredText(email, name) };\n if (email && typeof email === 'object') {\n const entryId = requiredText(email.entryId, `${name}.entryId`);\n const storeId = optionalText(email.storeId, `${name}.storeId`);\n return storeId ? { entryId, storeId } : { entryId };\n }\n throw new InvalidRequestError(`${name} must be an entry id or an object with an entryId (got ${describe(email)}).`);\n}\n\n/** A string→string map, e.g. template placeholders. */\nexport function stringRecord(value: unknown, name: string): Record<string, string> | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value !== 'object' || Array.isArray(value)) {\n throw new InvalidRequestError(`${name} must be an object of strings (got ${describe(value)}).`);\n }\n const out: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry !== 'string') {\n throw new InvalidRequestError(`${name}.${key} must be a string (got ${describe(entry)}).`);\n }\n out[key] = entry;\n }\n return out;\n}\n\n/**\n * Split a recipient list into its entries.\n *\n * Separators are commas and semicolons — but not inside a quoted display name\n * or between angle brackets, where `\"Doe, Jo\" <jo@example.com>` has a comma\n * that belongs to the name.\n */\nexport function recipientList(value: Recipients | undefined | null, name: string): string[] {\n if (value === undefined || value === null) return [];\n const parts = Array.isArray(value) ? value : [value];\n const out: string[] = [];\n parts.forEach((part, index) => {\n if (typeof part !== 'string') {\n throw new InvalidRequestError(`${name}[${index}] must be a string (got ${describe(part)}).`);\n }\n out.push(...splitAddressList(part));\n });\n return out;\n}\n\nfunction splitAddressList(text: string): string[] {\n const entries: string[] = [];\n let current = '';\n let quoted = false;\n let angled = false;\n for (const ch of text) {\n if (ch === '\"' && !angled) quoted = !quoted;\n else if (ch === '<' && !quoted) angled = true;\n else if (ch === '>' && !quoted) angled = false;\n if ((ch === ',' || ch === ';') && !quoted && !angled) {\n entries.push(current);\n current = '';\n } else {\n current += ch;\n }\n }\n entries.push(current);\n return entries.map(entry => entry.trim()).filter(Boolean);\n}\n\n/** One recipient entry as a display name and an address. */\nexport interface ParsedRecipient {\n name: string;\n address: string;\n}\n\n/** `\"Doe, Jo\" <jo@example.com>` → name + address; a bare address has no name. */\nexport function parseRecipient(entry: string): ParsedRecipient {\n const angled = /^(.*)<([^<>]+)>\\s*$/.exec(entry);\n if (!angled) return { name: '', address: entry.trim() };\n const name = angled[1].trim().replace(/^\"(.*)\"$/, '$1').trim();\n return { name, address: angled[2].trim() };\n}\n"]}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Collapse whitespace runs (non-breaking spaces included) and trim, so a
|
|
3
|
-
* trivially reformatted file name still resolves. Both platforms' regex engines
|
|
4
|
-
* count U+00A0 as whitespace.
|
|
5
|
-
*/
|
|
6
|
-
export declare function normalizeAttachmentName(name: string): string;
|
|
7
1
|
/**
|
|
8
2
|
* Index of the attachment matching `wanted`, or -1: exact first, then
|
|
9
3
|
* whitespace-normalized and case-insensitive — in that order, so two names that
|
|
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.MAX_FILE_STEM = exports.RESERVED_FILE_NAME = exports.UNSAFE_FILE_NAME_CHARS = void 0;
|
|
7
|
-
exports.normalizeAttachmentName = normalizeAttachmentName;
|
|
8
7
|
exports.findAttachmentIndex = findAttachmentIndex;
|
|
9
8
|
exports.attachmentNotFoundMessage = attachmentNotFoundMessage;
|
|
10
9
|
exports.safeFileName = safeFileName;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachmentMatch.js","sourceRoot":"","sources":["../../src/shared/attachmentMatch.ts"],"names":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"attachmentMatch.js","sourceRoot":"","sources":["../../src/shared/attachmentMatch.ts"],"names":[],"mappings":";;;;;;AAwBA,kDAKC;AAOD,8DAYC;AAkBD,oCAOC;AAMD,wCAQC;AAvFD,0EAA0E;AAC1E,+BAA+B;AAC/B,EAAE;AACF,sEAAsE;AACtE,6EAA6E;AAC7E,wEAAwE;AACxE,gFAAgF;AAChF,4CAA4C;AAC5C,gDAAwB;AAExB;;;;GAIG;AACH,SAAS,uBAAuB,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,SAAgB,mBAAmB,CAAC,KAAwB,EAAE,MAAc;IACxE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/B,MAAM,UAAU,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IACjE,OAAO,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,KAAK,UAAU,CAAC,CAAC;AAC/F,CAAC;AAED;;;;GAIG;AACH,SAAgB,yBAAyB,CAAC,IAOzC;IACG,OAAO,eAAe,IAAI,CAAC,QAAQ,eAAe;UAC5C,wBAAwB,IAAI,CAAC,WAAW,aAAa,IAAI,CAAC,OAAO,cAAc,IAAI,CAAC,YAAY,IAAI;UACpG,wBAAwB,IAAI,CAAC,OAAO,IAAI;UACxC,iHAAiH,CAAC;AAC5H,CAAC;AAED,4EAA4E;AAC/D,QAAA,sBAAsB,GAAG,2BAA2B,CAAC;AAElE,wEAAwE;AAC3D,QAAA,kBAAkB,GAAG,+CAA+C,CAAC;AAElF,6DAA6D;AAChD,QAAA,aAAa,GAAG,GAAG,CAAC;AAEjC;;;;;;GAMG;AACH,SAAgB,YAAY,CAAC,IAAY;IACrC,IAAI,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,8BAAsB,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC5F,IAAI,IAAI,MAAM,CAAC,0BAAkB,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;IACtE,MAAM,GAAG,GAAG,cAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;IACrD,IAAI,IAAI,CAAC,MAAM,GAAG,qBAAa;QAAE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,qBAAa,CAAC,GAAG,GAAG,CAAC;IAC3E,OAAO,IAAI,IAAI,YAAY,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,SAAgB,cAAc,CAAC,GAAW,EAAE,IAAY,EAAE,MAAiC;IACvF,MAAM,GAAG,GAAG,cAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;IACrD,IAAI,SAAS,GAAG,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACrC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,SAAS,GAAG,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,SAAS,CAAC;AACrB,CAAC","sourcesContent":["// Matching an attachment by name, the error when nothing matches, and the\n// file name it is saved under.\n//\n// The error is the useful half. Resolving an email by id can return a\n// DIFFERENT message when the id is stale or wrong — common exactly when many\n// replies share one subject — so a bare \"not found\" points at the wrong\n// problem. Naming the email that actually resolved, and the attachments it does\n// carry, is what makes the mistake visible.\nimport path from 'path';\n\n/**\n * Collapse whitespace runs (non-breaking spaces included) and trim, so a\n * trivially reformatted file name still resolves. Both platforms' regex engines\n * count U+00A0 as whitespace.\n */\nfunction normalizeAttachmentName(name: string): string {\n return name.replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Index of the attachment matching `wanted`, or -1: exact first, then\n * whitespace-normalized and case-insensitive — in that order, so two names that\n * differ only in case still resolve the one asked for.\n */\nexport function findAttachmentIndex(names: readonly string[], wanted: string): number {\n const exact = names.indexOf(wanted);\n if (exact !== -1) return exact;\n const normalized = normalizeAttachmentName(wanted).toLowerCase();\n return names.findIndex(name => normalizeAttachmentName(name).toLowerCase() === normalized);\n}\n\n/**\n * The \"attachment not found\" message, worded once for both platforms. Fields\n * arrive as strings so the Windows script can build the same sentence from\n * PowerShell variables where macOS passes the values it read.\n */\nexport function attachmentNotFoundMessage(init: {\n fileName: string;\n senderEmail: string;\n subject: string;\n receivedTime: string;\n /** The attachments the email does carry, joined for display. */\n present: string;\n}): string {\n return `Attachment '${init.fileName}' not found. `\n + `Resolved email: from=${init.senderEmail}; subject=${init.subject}; received=${init.receivedTime}. `\n + `Attachments present: ${init.present}. `\n + 'If this is not the email you expected, its id is likely wrong or stale - list the mail again for a current one.';\n}\n\n/** Characters no file name may carry on Windows (a superset of macOS's). */\nexport const UNSAFE_FILE_NAME_CHARS = '[<>:\"/\\\\\\\\|?*\\\\x00-\\\\x1F]';\n\n/** Names Windows reserves for devices, with or without an extension. */\nexport const RESERVED_FILE_NAME = '^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\\\\..*)?$';\n\n/** How long a saved name's stem may run before it is cut. */\nexport const MAX_FILE_STEM = 150;\n\n/**\n * A sender-chosen attachment name made safe to write: no path separators or\n * characters a file system rejects, no trailing dots or spaces, no reserved\n * device name, and a stem short enough to stay clear of path-length limits. A\n * name like `..\\..\\x.exe` therefore lands inside the destination as\n * `.._.._x.exe`, never outside it.\n */\nexport function safeFileName(name: string): string {\n let safe = name.replace(new RegExp(UNSAFE_FILE_NAME_CHARS, 'g'), '_').replace(/[. ]+$/, '');\n if (new RegExp(RESERVED_FILE_NAME, 'i').test(safe)) safe = `_${safe}`;\n const ext = path.extname(safe);\n const stem = safe.slice(0, safe.length - ext.length);\n if (stem.length > MAX_FILE_STEM) safe = stem.slice(0, MAX_FILE_STEM) + ext;\n return safe || 'attachment';\n}\n\n/**\n * A path in `dir` for `name` that `exists` reports free: the name itself, else\n * `name (1).ext`, `name (2).ext`, … Existing files are never overwritten.\n */\nexport function uniqueSavePath(dir: string, name: string, exists: (file: string) => boolean): string {\n const ext = path.extname(name);\n const stem = name.slice(0, name.length - ext.length);\n let candidate = path.join(dir, name);\n for (let n = 1; exists(candidate); n++) {\n candidate = path.join(dir, `${stem} (${n})${ext}`);\n }\n return candidate;\n}\n"]}
|
package/dist/shared/exec.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/shared/exec.ts"],"names":[],"mappings":";;AA4BA,4CAoCC;AAhED,2EAA2E;AAC3E,EAAE;AACF,yEAAyE;AACzE,+EAA+E;AAC/E,6EAA6E;AAC7E,kDAAkD;AAClD,iDAAyC;AACzC,sCAAkE;AAClE,wCAA8E;AAmB9E,8FAA8F;AAC9F,SAAgB,gBAAgB,CAAC,GAAkB;IAC/C,MAAM,
|
|
1
|
+
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/shared/exec.ts"],"names":[],"mappings":";;AA4BA,4CAoCC;AAhED,2EAA2E;AAC3E,EAAE;AACF,yEAAyE;AACzE,+EAA+E;AAC/E,6EAA6E;AAC7E,kDAAkD;AAClD,iDAAyC;AACzC,sCAAkE;AAClE,wCAA8E;AAmB9E,8FAA8F;AAC9F,SAAgB,gBAAgB,CAAC,GAAkB;IAC/C,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,IAAA,mBAAS,GAAE,CAAC;IAC/C,MAAM,OAAO,GAAG,IAAA,oBAAU,EAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,IAAA,wBAAQ,EACJ,GAAG,CAAC,OAAO,EACX,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC;QACb,sEAAsE;QACtE,+BAA+B;QAC/B,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EACnF,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACtB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAC1C,IAAI,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,YAAY,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;gBACzD,MAAM,OAAO,GAAG,IAAA,2BAAkB,EAAC;oBAC/B,MAAM,EAAE,GAAG,CAAC,MAAM;oBAClB,MAAM,EAAE,GAAG,CAAC,MAAM;oBAClB,MAAM,EAAE,MAAM,IAAI,EAAE;oBACpB,OAAO;oBACP,IAAI;oBACJ,UAAU;oBACV,SAAS,EAAE,KAAK;oBAChB,SAAS,EAAE,OAAO;oBAClB,cAAc;oBACd,MAAM;iBACT,CAAC,CAAC;gBACH,IAAA,mBAAS,EAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC1F,MAAM,CAAC,OAAO,CAAC,CAAC;gBAChB,OAAO;YACX,CAAC;YACD,IAAA,mBAAS,EAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;YAClE,OAAO,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;QACpC,CAAC,CACJ,CAAC;IACN,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["// Running one interpreter process: the part both platforms' runners share.\n//\n// Each platform decides how its script reaches the interpreter and how a\n// failure reads in that interpreter's stderr. Everything else — the settings a\n// run takes, telling an abort from a timeout from an overflow, and reporting\n// the run to the debug hook — happens here, once.\nimport { execFile } from 'child_process';\nimport { classifyRunFailure, type ScriptRunner } from '../errors';\nimport { getConfig, reportRun, type RunBudget, timeoutFor } from '../runtime';\n\nexport interface ScriptProcess {\n runner: ScriptRunner;\n command: string;\n args: readonly string[];\n /** The script exactly as it runs, for errors and the debug hook. */\n script: string;\n budget: RunBudget;\n\n /** The script's own failure message, and the line it came from, out of stderr. */\n parseFailure(stderr: string): { message: string; line?: number };\n}\n\nexport interface ProcessOutput {\n stdout: string;\n durationMs: number;\n}\n\n/** Run one script to completion, rejecting with the most specific OutlookError on failure. */\nexport function runScriptProcess(run: ScriptProcess): Promise<ProcessOutput> {\n const { maxBufferBytes, signal } = getConfig();\n const timeout = timeoutFor(run.budget);\n const startedAt = Date.now();\n return new Promise((resolve, reject) => {\n execFile(\n run.command,\n [...run.args],\n // windowsHide keeps a GUI host (an Electron app, say) from flashing a\n // console window on every run.\n { maxBuffer: maxBufferBytes, timeout, signal, windowsHide: true, encoding: 'utf8' },\n (error, stdout, stderr) => {\n const durationMs = Date.now() - startedAt;\n if (error) {\n const { message, line } = run.parseFailure(stderr ?? '');\n const failure = classifyRunFailure({\n runner: run.runner,\n script: run.script,\n stderr: stderr ?? '',\n message,\n line,\n durationMs,\n nodeError: error,\n timeoutMs: timeout,\n maxBufferBytes,\n signal,\n });\n reportRun({ runner: run.runner, script: run.script, durationMs, error: failure.message });\n reject(failure);\n return;\n }\n reportRun({ runner: run.runner, script: run.script, durationMs });\n resolve({ stdout, durationMs });\n },\n );\n });\n}\n"]}
|
package/dist/shared/locate.d.ts
CHANGED
|
@@ -22,14 +22,6 @@ export declare function subjectPrefilter(subject: string | undefined): string |
|
|
|
22
22
|
* the stamp was written in, and a search started either side of midnight.
|
|
23
23
|
*/
|
|
24
24
|
export declare function daysBackFor(receivedTime: string | undefined, fallback: number, now?: number): number;
|
|
25
|
-
/**
|
|
26
|
-
* How well a found email answers the description, or 0 for "not this one".
|
|
27
|
-
*
|
|
28
|
-
* Disagreement on any given field disqualifies outright. Agreement is then
|
|
29
|
-
* ranked, so an exact minute beats the same day: a sender who wrote twice in
|
|
30
|
-
* one afternoon still resolves to the email described.
|
|
31
|
-
*/
|
|
32
|
-
export declare function matchScore(wanted: WantedEmail, match: InboxSearchMatch): number;
|
|
33
25
|
/** The best match, or null when nothing agrees with the description. */
|
|
34
26
|
export declare function bestMatch(wanted: WantedEmail, matches: readonly InboxSearchMatch[]): InboxSearchMatch | null;
|
|
35
27
|
/**
|
package/dist/shared/locate.js
CHANGED
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.requireDescribed = requireDescribed;
|
|
4
4
|
exports.subjectPrefilter = subjectPrefilter;
|
|
5
5
|
exports.daysBackFor = daysBackFor;
|
|
6
|
-
exports.matchScore = matchScore;
|
|
7
6
|
exports.bestMatch = bestMatch;
|
|
8
7
|
exports.accountSearchOrder = accountSearchOrder;
|
|
9
8
|
// Finding an email again from what was kept about it: the rules, as pure
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locate.js","sourceRoot":"","sources":["../../src/shared/locate.ts"],"names":[],"mappings":"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","sourcesContent":["// Finding an email again from what was kept about it: the rules, as pure\n// functions. The search itself is an ordinary searchInboxByFilter per mailbox;\n// what lives here is how narrow that search can be made, and which of its\n// results is the email.\n//\n// The rules never guess. Every field the description gives has to agree, and at\n// least two have to be given, because a subject alone names every message in a\n// thread. A miss stays a miss rather than returning somebody else's email.\nimport { threadSubject } from '../mail';\nimport { InvalidRequestError } from '../errors';\nimport type { InboxSearchMatch } from '../types';\n\n/** The fields a match is judged on, trimmed; blank means not given. */\nexport interface WantedEmail {\n readonly subject?: string;\n readonly sender?: string;\n readonly receivedTime?: string;\n}\n\n/** `yyyy-MM-dd`, optionally followed by ` HH:mm` — how both platforms stamp a received time. */\nconst STAMP = /^\\d{4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2})?/;\n\n/** An address inside a sender string, which may be a name, an address, or both. */\nconst ADDRESS = /[^\\s<>,;\"']+@[^\\s<>,;\"']+/;\n\nconst DAY_MS = 86_400_000;\n\nconst subjectKey = (subject: string): string => threadSubject(subject).toLowerCase();\n\n/**\n * The stamp in the listings' own form (`yyyy-MM-dd HH:mm`), or '' when it isn't\n * a stamp at all — which is then treated as not given, since it can't disagree\n * with anything.\n */\nfunction stampOf(receivedTime: string | undefined): string {\n const match = STAMP.exec((receivedTime ?? '').trim());\n return match ? match[0].replace('T', ' ') : '';\n}\n\n/** How many of the three fields the description actually gives. */\nfunction recordedCount(wanted: WantedEmail): number {\n return (wanted.subject ? 1 : 0) + (wanted.sender ? 1 : 0) + (stampOf(wanted.receivedTime) ? 1 : 0);\n}\n\n/** INVALID_REQUEST unless the description gives enough to tell one email from its thread. */\nexport function requireDescribed(wanted: WantedEmail): void {\n if (recordedCount(wanted) < 2) {\n throw new InvalidRequestError(\n 'Describe the email by at least two of subject, sender and receivedTime (as yyyy-MM-dd HH:mm): '\n + 'a subject alone names every message in its thread.',\n );\n }\n}\n\n/**\n * The subject glob to prefilter a search with: the longest run of plain\n * characters in the subject, or undefined when there's no run of four.\n *\n * A subject can hold an apostrophe or a `*`, and the prefilter becomes a query\n * on some stores; it only has to be selective, since every result is judged on\n * the whole subject afterwards.\n */\nexport function subjectPrefilter(subject: string | undefined): string | undefined {\n const longest = (threadSubject(subject ?? '').match(/[A-Za-z0-9 ._-]+/g) ?? [])\n .map(run => run.trim())\n .sort((a, b) => b.length - a.length)[0] ?? '';\n return longest.length >= 4 ? `*${longest}*` : undefined;\n}\n\n/**\n * How far back a search has to reach to cover the day the email arrived, or\n * `fallback` when no day is known. Two days of slack cover whichever timezone\n * the stamp was written in, and a search started either side of midnight.\n */\nexport function daysBackFor(receivedTime: string | undefined, fallback: number, now = Date.now()): number {\n const stamp = stampOf(receivedTime);\n if (!stamp) return fallback;\n const day = Date.parse(`${stamp.slice(0, 10)}T00:00:00Z`);\n if (Number.isNaN(day)) return fallback;\n return Math.max(1, Math.floor((now - day) / DAY_MS) + 2);\n}\n\n/** Whether a found email came from the sender described. */\nfunction senderMatches(wanted: string, match: InboxSearchMatch): boolean {\n const want = wanted.toLowerCase();\n const address = ADDRESS.exec(want)?.[0];\n if (address) return address === match.senderEmail.trim().toLowerCase();\n const name = match.senderName.trim().toLowerCase();\n return name !== '' && (want.includes(name) || name.includes(want));\n}\n\n/**\n * How well a found email answers the description, or 0 for \"not this one\".\n *\n * Disagreement on any given field disqualifies outright. Agreement is then\n * ranked, so an exact minute beats the same day: a sender who wrote twice in\n * one afternoon still resolves to the email described.\n */\nexport function matchScore(wanted: WantedEmail, match: InboxSearchMatch): number {\n if (recordedCount(wanted) < 2) return 0;\n\n const wantSubject = wanted.subject ? subjectKey(wanted.subject) : '';\n if (wantSubject && subjectKey(match.subject) !== wantSubject) return 0;\n if (wanted.sender && !senderMatches(wanted.sender, match)) return 0;\n\n const stamp = stampOf(wanted.receivedTime);\n const found = match.receivedTime.trim();\n const sameDay = stamp !== '' && stamp.slice(0, 10) === found.slice(0, 10);\n const sameMinute = sameDay && stamp.length >= 16 && stamp.slice(0, 16) === found.slice(0, 16);\n if (stamp && !sameDay) return 0;\n\n return (wantSubject ? 1 : 0) + (wanted.sender ? 2 : 0) + (sameMinute ? 3 : sameDay ? 1 : 0);\n}\n\n/** The best match, or null when nothing agrees with the description. */\nexport function bestMatch(wanted: WantedEmail, matches: readonly InboxSearchMatch[]): InboxSearchMatch | null {\n let best: InboxSearchMatch | null = null;\n let bestScore = 0;\n for (const match of matches) {\n const score = matchScore(wanted, match);\n if (score > bestScore) {\n best = match;\n bestScore = score;\n }\n }\n return best;\n}\n\n/**\n * Accounts in the order to search them: the one a listing's folderPath names\n * first (`\\\\team@example.com\\Inbox\\…`), then the rest as given.\n */\nexport function accountSearchOrder(accounts: readonly string[], folderPath: string | undefined): string[] {\n const path = (folderPath ?? '').toLowerCase();\n if (!path) return [...accounts];\n const named = accounts.filter(account => path.includes(account.toLowerCase()));\n return [...named, ...accounts.filter(account => !named.includes(account))];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"locate.js","sourceRoot":"","sources":["../../src/shared/locate.ts"],"names":[],"mappings":"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","sourcesContent":["// Finding an email again from what was kept about it: the rules, as pure\n// functions. The search itself is an ordinary searchInboxByFilter per mailbox;\n// what lives here is how narrow that search can be made, and which of its\n// results is the email.\n//\n// The rules never guess. Every field the description gives has to agree, and at\n// least two have to be given, because a subject alone names every message in a\n// thread. A miss stays a miss rather than returning somebody else's email.\nimport { threadSubject } from '../mail';\nimport { InvalidRequestError } from '../errors';\nimport type { InboxSearchMatch } from '../types';\n\n/** The fields a match is judged on, trimmed; blank means not given. */\nexport interface WantedEmail {\n readonly subject?: string;\n readonly sender?: string;\n readonly receivedTime?: string;\n}\n\n/** `yyyy-MM-dd`, optionally followed by ` HH:mm` — how both platforms stamp a received time. */\nconst STAMP = /^\\d{4}-\\d{2}-\\d{2}([ T]\\d{2}:\\d{2})?/;\n\n/** An address inside a sender string, which may be a name, an address, or both. */\nconst ADDRESS = /[^\\s<>,;\"']+@[^\\s<>,;\"']+/;\n\nconst DAY_MS = 86_400_000;\n\nconst subjectKey = (subject: string): string => threadSubject(subject).toLowerCase();\n\n/**\n * The stamp in the listings' own form (`yyyy-MM-dd HH:mm`), or '' when it isn't\n * a stamp at all — which is then treated as not given, since it can't disagree\n * with anything.\n */\nfunction stampOf(receivedTime: string | undefined): string {\n const match = STAMP.exec((receivedTime ?? '').trim());\n return match ? match[0].replace('T', ' ') : '';\n}\n\n/** How many of the three fields the description actually gives. */\nfunction recordedCount(wanted: WantedEmail): number {\n return (wanted.subject ? 1 : 0) + (wanted.sender ? 1 : 0) + (stampOf(wanted.receivedTime) ? 1 : 0);\n}\n\n/** INVALID_REQUEST unless the description gives enough to tell one email from its thread. */\nexport function requireDescribed(wanted: WantedEmail): void {\n if (recordedCount(wanted) < 2) {\n throw new InvalidRequestError(\n 'Describe the email by at least two of subject, sender and receivedTime (as yyyy-MM-dd HH:mm): '\n + 'a subject alone names every message in its thread.',\n );\n }\n}\n\n/**\n * The subject glob to prefilter a search with: the longest run of plain\n * characters in the subject, or undefined when there's no run of four.\n *\n * A subject can hold an apostrophe or a `*`, and the prefilter becomes a query\n * on some stores; it only has to be selective, since every result is judged on\n * the whole subject afterwards.\n */\nexport function subjectPrefilter(subject: string | undefined): string | undefined {\n const longest = (threadSubject(subject ?? '').match(/[A-Za-z0-9 ._-]+/g) ?? [])\n .map(run => run.trim())\n .sort((a, b) => b.length - a.length)[0] ?? '';\n return longest.length >= 4 ? `*${longest}*` : undefined;\n}\n\n/**\n * How far back a search has to reach to cover the day the email arrived, or\n * `fallback` when no day is known. Two days of slack cover whichever timezone\n * the stamp was written in, and a search started either side of midnight.\n */\nexport function daysBackFor(receivedTime: string | undefined, fallback: number, now = Date.now()): number {\n const stamp = stampOf(receivedTime);\n if (!stamp) return fallback;\n const day = Date.parse(`${stamp.slice(0, 10)}T00:00:00Z`);\n if (Number.isNaN(day)) return fallback;\n return Math.max(1, Math.floor((now - day) / DAY_MS) + 2);\n}\n\n/** Whether a found email came from the sender described. */\nfunction senderMatches(wanted: string, match: InboxSearchMatch): boolean {\n const want = wanted.toLowerCase();\n const address = ADDRESS.exec(want)?.[0];\n if (address) return address === match.senderEmail.trim().toLowerCase();\n const name = match.senderName.trim().toLowerCase();\n return name !== '' && (want.includes(name) || name.includes(want));\n}\n\n/**\n * How well a found email answers the description, or 0 for \"not this one\".\n *\n * Disagreement on any given field disqualifies outright. Agreement is then\n * ranked, so an exact minute beats the same day: a sender who wrote twice in\n * one afternoon still resolves to the email described.\n */\nfunction matchScore(wanted: WantedEmail, match: InboxSearchMatch): number {\n if (recordedCount(wanted) < 2) return 0;\n\n const wantSubject = wanted.subject ? subjectKey(wanted.subject) : '';\n if (wantSubject && subjectKey(match.subject) !== wantSubject) return 0;\n if (wanted.sender && !senderMatches(wanted.sender, match)) return 0;\n\n const stamp = stampOf(wanted.receivedTime);\n const found = match.receivedTime.trim();\n const sameDay = stamp !== '' && stamp.slice(0, 10) === found.slice(0, 10);\n const sameMinute = sameDay && stamp.length >= 16 && stamp.slice(0, 16) === found.slice(0, 16);\n if (stamp && !sameDay) return 0;\n\n return (wantSubject ? 1 : 0) + (wanted.sender ? 2 : 0) + (sameMinute ? 3 : sameDay ? 1 : 0);\n}\n\n/** The best match, or null when nothing agrees with the description. */\nexport function bestMatch(wanted: WantedEmail, matches: readonly InboxSearchMatch[]): InboxSearchMatch | null {\n let best: InboxSearchMatch | null = null;\n let bestScore = 0;\n for (const match of matches) {\n const score = matchScore(wanted, match);\n if (score > bestScore) {\n best = match;\n bestScore = score;\n }\n }\n return best;\n}\n\n/**\n * Accounts in the order to search them: the one a listing's folderPath names\n * first (`\\\\team@example.com\\Inbox\\…`), then the rest as given.\n */\nexport function accountSearchOrder(accounts: readonly string[], folderPath: string | undefined): string[] {\n const path = (folderPath ?? '').toLowerCase();\n if (!path) return [...accounts];\n const named = accounts.filter(account => path.includes(account.toLowerCase()));\n return [...named, ...accounts.filter(account => !named.includes(account))];\n}\n"]}
|
|
@@ -16,21 +16,6 @@ export interface ReplyContent {
|
|
|
16
16
|
readonly templatePlaceholders?: Readonly<Record<string, string>>;
|
|
17
17
|
readonly signatureName?: string;
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
20
|
-
* The content of an HTML document's `<body>`, or the input when it isn't a
|
|
21
|
-
* whole document. Signature files and saved templates are both whole documents,
|
|
22
|
-
* and neither may nest its `<html>`/`<body>` inside another email's.
|
|
23
|
-
*/
|
|
24
|
-
export declare function innerBodyHtml(html: string): string;
|
|
25
|
-
/**
|
|
26
|
-
* A saved template's HTML, found by subject.
|
|
27
|
-
*
|
|
28
|
-
* The read is filtered by subject in the platform script: unfiltered, EVERY
|
|
29
|
-
* template's full body would cross the interpreter's stdout on every reply, and
|
|
30
|
-
* a folder of Word-sized templates overruns the output limit on a call that
|
|
31
|
-
* only ever needed one of them.
|
|
32
|
-
*/
|
|
33
|
-
export declare function resolveTemplateHtml(deps: ReplyBodyDeps, account: string, folder: string, subject: string): Promise<string>;
|
|
34
19
|
/** A named signature's HTML, ready to insert into a body; NOT_FOUND naming the ones there are. */
|
|
35
20
|
export declare function resolveSignatureHtml(deps: Pick<ReplyBodyDeps, 'readOutlookSignatureHtml' | 'listOutlookSignatures'>, name: string): Promise<string>;
|
|
36
21
|
/**
|
package/dist/shared/replyBody.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WORD_SECTION_ANCHOR = void 0;
|
|
4
|
-
exports.innerBodyHtml = innerBodyHtml;
|
|
5
|
-
exports.resolveTemplateHtml = resolveTemplateHtml;
|
|
6
4
|
exports.resolveSignatureHtml = resolveSignatureHtml;
|
|
7
5
|
exports.placeSignature = placeSignature;
|
|
8
6
|
exports.composeReplyHtml = composeReplyHtml;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replyBody.js","sourceRoot":"","sources":["../../src/shared/replyBody.ts"],"names":[],"mappings":";;;AAmCA,sCAGC;AAUD,kDAwBC;AAMD,oDAUC;AAaD,wCAKC;AAUD,4CA6BC;AAjJD,wEAAwE;AACxE,6EAA6E;AAC7E,8EAA8E;AAC9E,4EAA4E;AAC5E,0EAA0E;AAC1E,kDAAgF;AAChF,sCAA+D;AAwB/D;;;;GAIG;AACH,SAAgB,aAAa,CAAC,IAAY;IACtC,MAAM,KAAK,GAAG,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5C,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,mBAAmB,CACrC,IAAmB,EACnB,OAAe,EACf,MAAc,EACd,OAAe;IAEf,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,EAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAC,CAAC,CAAC;IACvG,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QACtB,MAAM,OAAO,GAAG,MAAM,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC;QAC/D,MAAM,IAAI,sBAAa,CAAC,QAAQ,EAAE,oBAAoB,MAAM,kBAAkB,OAAO,wBAAwB,OAAO,GAAG,CAAC,CAAC;IAC7H,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,CAAC;IACpF,IAAI,CAAC,KAAK,EAAE,CAAC;QACT,oEAAoE;QACpE,2CAA2C;QAC3C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,EAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,EAAC,CAAC,CAAC;QAC7F,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC;QACvF,MAAM,IAAI,sBAAa,CAAC,UAAU,EAAE,aAAa,OAAO,mBAAmB,MAAM,uBAAuB,KAAK,GAAG,CAAC,CAAC;IACtH,CAAC;IACD,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC/B,MAAM,IAAI,sBAAa,CAAC,UAAU,EAAE,aAAa,OAAO,2BAA2B,CAAC,CAAC;IACzF,CAAC;IACD,OAAO,KAAK,CAAC,QAAQ,CAAC;AAC1B,CAAC;AAED,+CAA+C;AAC/C,MAAM,eAAe,GAAG,eAAe,CAAC;AAExC,kGAAkG;AAC3F,KAAK,UAAU,oBAAoB,CACtC,IAA+E,EAC/E,IAAY;IAEZ,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAChE,IAAI,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC9B,MAAM,SAAS,GAAG,CAAC,MAAM,IAAI,CAAC,qBAAqB,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC;QAC9E,MAAM,IAAI,sBAAa,CAAC,WAAW,EAAE,sBAAsB,IAAI,4CAA4C,SAAS,GAAG,CAAC,CAAC;IAC7H,CAAC;IACD,OAAO,aAAa,CAAC,aAAa,CAAC,CAAC;AACxC,CAAC;AAED,oDAAoD;AACpD,SAAS,gBAAgB,CAAC,IAAY;IAClC,OAAO,IAAA,yBAAU,EAAC,IAAI,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;GAKG;AACH,SAAgB,cAAc,CAAC,IAAY,EAAE,SAAiB;IAC1D,IAAI,gBAAgB,CAAC,IAAI,CAAC;QAAE,OAAO,IAAA,2BAAY,EAAC,IAAI,EAAE,eAAe,EAAE,SAAS,CAAC,CAAC;IAClF,MAAM,KAAK,GAAG,OAAO,SAAS,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IACvD,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAC1F,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,gBAAgB,CAAC,OAAqB,EAAE,IAAmB;IAC7E,IAAI,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC;IAC5B,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;QAC3D,IAAI,GAAG,MAAM,mBAAmB,CAAC,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,cAAc,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;QACzG,YAAY,GAAG,IAAI,CAAC;IACxB,CAAC;IACD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC9B,MAAM,IAAI,4BAAmB,CAAC,mDAAmD,CAAC,CAAC;IACvF,CAAC;IAED,wEAAwE;IACxE,0EAA0E;IAC1E,uDAAuD;IACvD,IAAI,YAAY,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAChD,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,oBAAoB,CAAC,IAAI,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACvG,MAAM,gBAAgB,GAAG,SAAS,KAAK,EAAE,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACpE,IAAI,gBAAgB;QAAE,YAAY,GAAG,EAAC,GAAG,YAAY,EAAE,SAAS,EAAE,SAAS,EAAC,CAAC;IAE7E,MAAM,eAAe,GAAG,CAAC,CAAC,YAAY,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/E,IAAI,YAAY,IAAI,OAAO,CAAC,eAAe,IAAI,eAAe,EAAE,CAAC;QAC7D,IAAI,GAAG,IAAA,kCAAmB,EAAC,IAAI,EAAE;YAC7B,OAAO,EAAE,OAAO,CAAC,eAAe;YAChC,YAAY;YACZ,KAAK,EAAE,OAAO,CAAC,eAAe;SACjC,CAAC,CAAC;IACP,CAAC;IACD,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACjC,OAAO,SAAS,KAAK,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1F,CAAC;AAED;;;;;;GAMG;AACU,QAAA,mBAAmB,GAAG,eAAe,CAAC","sourcesContent":["// Everything a reply needs before it touches Outlook: resolving a named\n// template, keeping its section, filling placeholders and a named signature,\n// and reducing the result to HTML that can be inserted into another document.\n// Signing a new email uses the signature half of it. String work plus three\n// reads each platform already provides, so it is done once here for both.\nimport { composeTemplateBody, findTokens, replaceToken } from '../templateBody';\nimport { InvalidRequestError, NotFoundError } from '../errors';\nimport type { ReadTemplatesRequest } from '../backend';\nimport type { TemplateFolderResult } from '../types';\n\n/** The reads a template- or signature-backed reply needs. */\nexport interface ReplyBodyDeps {\n readTemplateEmails(request: ReadTemplatesRequest): Promise<TemplateFolderResult>;\n\n readOutlookSignatureHtml(name: string): Promise<string>;\n\n listOutlookSignatures(): Promise<string[]>;\n}\n\n/** A reply's content, as the caller described it — already validated. */\nexport interface ReplyContent {\n readonly account: string;\n readonly htmlBody?: string;\n readonly templateSubject?: string;\n readonly templateFolder: string;\n readonly templateSection?: string;\n readonly templatePlaceholders?: Readonly<Record<string, string>>;\n readonly signatureName?: string;\n}\n\n/**\n * The content of an HTML document's `<body>`, or the input when it isn't a\n * whole document. Signature files and saved templates are both whole documents,\n * and neither may nest its `<html>`/`<body>` inside another email's.\n */\nexport function innerBodyHtml(html: string): string {\n const match = /<body[^>]*>([\\s\\S]*)<\\/body>/i.exec(html);\n return (match ? match[1] : html).trim();\n}\n\n/**\n * A saved template's HTML, found by subject.\n *\n * The read is filtered by subject in the platform script: unfiltered, EVERY\n * template's full body would cross the interpreter's stdout on every reply, and\n * a folder of Word-sized templates overruns the output limit on a call that\n * only ever needed one of them.\n */\nexport async function resolveTemplateHtml(\n deps: ReplyBodyDeps,\n account: string,\n folder: string,\n subject: string,\n): Promise<string> {\n const result = await deps.readTemplateEmails({account, folder, limit: 50, includeBody: true, subject});\n if (!result.folderFound) {\n const folders = result.availableFolders.join(', ') || '(none)';\n throw new NotFoundError('folder', `Template folder '${folder}' not found in ${account}. Top-level folders: ${folders}.`);\n }\n const wanted = subject.trim().toLowerCase();\n const match = result.templates.find(t => t.subject.trim().toLowerCase() === wanted);\n if (!match) {\n // The read above was filtered, so it can't name the alternatives. A\n // second read without bodies can, cheaply.\n const all = await deps.readTemplateEmails({account, folder, limit: 200, includeBody: false});\n const names = all.templates.map(t => t.subject).filter(Boolean).join(', ') || '(none)';\n throw new NotFoundError('template', `Template '${subject}' not found in '${folder}'. Templates there: ${names}.`);\n }\n if (match.htmlBody.trim() === '') {\n throw new NotFoundError('template', `Template '${subject}' has an empty HTML body.`);\n }\n return match.htmlBody;\n}\n\n/** Where a template asks for the signature. */\nconst SIGNATURE_TOKEN = '{{SIGNATURE}}';\n\n/** A named signature's HTML, ready to insert into a body; NOT_FOUND naming the ones there are. */\nexport async function resolveSignatureHtml(\n deps: Pick<ReplyBodyDeps, 'readOutlookSignatureHtml' | 'listOutlookSignatures'>,\n name: string,\n): Promise<string> {\n const signatureHtml = await deps.readOutlookSignatureHtml(name);\n if (signatureHtml.trim() === '') {\n const available = (await deps.listOutlookSignatures()).join(', ') || '(none)';\n throw new NotFoundError('signature', `Outlook signature '${name}' not found. Signatures on this machine: ${available}.`);\n }\n return innerBodyHtml(signatureHtml);\n}\n\n/** Whether a body says where its signature goes. */\nfunction asksForSignature(html: string): boolean {\n return findTokens(html, [SIGNATURE_TOKEN]).length > 0;\n}\n\n/**\n * Sign a body: in its `{{SIGNATURE}}` placeholder when it has one, otherwise at\n * the end, where Outlook itself would put a signature. In a whole document the\n * end is just inside `</body>`, since anything after `</html>` is outside the\n * email.\n */\nexport function placeSignature(html: string, signature: string): string {\n if (asksForSignature(html)) return replaceToken(html, SIGNATURE_TOKEN, signature);\n const block = `<br>${signature}`;\n const close = html.toLowerCase().lastIndexOf('</body');\n return close === -1 ? html + block : html.slice(0, close) + block + html.slice(close);\n}\n\n/**\n * The HTML to insert above the quoted original: template resolved, section\n * kept, placeholders and signature filled, document wrapper removed.\n *\n * A body taken from a template is always put through composition, even with\n * nothing to fill, so a stray [[SECTION]] marker is caught here rather than\n * mailed out.\n */\nexport async function composeReplyHtml(content: ReplyContent, deps: ReplyBodyDeps): Promise<string> {\n let html = content.htmlBody;\n let fromTemplate = false;\n if ((!html || html.trim() === '') && content.templateSubject) {\n html = await resolveTemplateHtml(deps, content.account, content.templateFolder, content.templateSubject);\n fromTemplate = true;\n }\n if (!html || html.trim() === '') {\n throw new InvalidRequestError('A reply needs either htmlBody or templateSubject.');\n }\n\n // A signature with a placeholder to go in is filled with the others, so\n // one that sat in a dropped section goes with it. Without one it is added\n // below the new text once everything else is composed.\n let placeholders = content.templatePlaceholders;\n const signature = content.signatureName ? await resolveSignatureHtml(deps, content.signatureName) : '';\n const signatureInPlace = signature !== '' && asksForSignature(html);\n if (signatureInPlace) placeholders = {...placeholders, SIGNATURE: signature};\n\n const hasPlaceholders = !!placeholders && Object.keys(placeholders).length > 0;\n if (fromTemplate || content.templateSection || hasPlaceholders) {\n html = composeTemplateBody(html, {\n section: content.templateSection,\n placeholders,\n label: content.templateSubject,\n });\n }\n const body = innerBodyHtml(html);\n return signature !== '' && !signatureInPlace ? placeSignature(body, signature) : body;\n}\n\n/**\n * Where the new text goes in an Outlook-built reply: straight after the opening\n * of `WordSection1`, the div holding the empty paragraph where a person's cursor\n * would be. A reply Outlook didn't build that way falls back to just inside\n * `<body>`. The insertion itself happens in each platform's own language, since\n * the reply HTML never leaves Outlook; this anchor is the part both must agree on.\n */\nexport const WORD_SECTION_ANCHOR = 'WordSection1>';\n"]}
|
|
1
|
+
{"version":3,"file":"replyBody.js","sourceRoot":"","sources":["../../src/shared/replyBody.ts"],"names":[],"mappings":"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","sourcesContent":["// Everything a reply needs before it touches Outlook: resolving a named\n// template, keeping its section, filling placeholders and a named signature,\n// and reducing the result to HTML that can be inserted into another document.\n// Signing a new email uses the signature half of it. String work plus three\n// reads each platform already provides, so it is done once here for both.\nimport { composeTemplateBody, findTokens, replaceToken } from '../templateBody';\nimport { InvalidRequestError, NotFoundError } from '../errors';\nimport type { ReadTemplatesRequest } from '../backend';\nimport type { TemplateFolderResult } from '../types';\n\n/** The reads a template- or signature-backed reply needs. */\nexport interface ReplyBodyDeps {\n readTemplateEmails(request: ReadTemplatesRequest): Promise<TemplateFolderResult>;\n\n readOutlookSignatureHtml(name: string): Promise<string>;\n\n listOutlookSignatures(): Promise<string[]>;\n}\n\n/** A reply's content, as the caller described it — already validated. */\nexport interface ReplyContent {\n readonly account: string;\n readonly htmlBody?: string;\n readonly templateSubject?: string;\n readonly templateFolder: string;\n readonly templateSection?: string;\n readonly templatePlaceholders?: Readonly<Record<string, string>>;\n readonly signatureName?: string;\n}\n\n/**\n * The content of an HTML document's `<body>`, or the input when it isn't a\n * whole document. Signature files and saved templates are both whole documents,\n * and neither may nest its `<html>`/`<body>` inside another email's.\n */\nfunction innerBodyHtml(html: string): string {\n const match = /<body[^>]*>([\\s\\S]*)<\\/body>/i.exec(html);\n return (match ? match[1] : html).trim();\n}\n\n/**\n * A saved template's HTML, found by subject.\n *\n * The read is filtered by subject in the platform script: unfiltered, EVERY\n * template's full body would cross the interpreter's stdout on every reply, and\n * a folder of Word-sized templates overruns the output limit on a call that\n * only ever needed one of them.\n */\nasync function resolveTemplateHtml(\n deps: ReplyBodyDeps,\n account: string,\n folder: string,\n subject: string,\n): Promise<string> {\n const result = await deps.readTemplateEmails({ account, folder, limit: 50, includeBody: true, subject });\n if (!result.folderFound) {\n const folders = result.availableFolders.join(', ') || '(none)';\n throw new NotFoundError('folder', `Template folder '${folder}' not found in ${account}. Top-level folders: ${folders}.`);\n }\n const wanted = subject.trim().toLowerCase();\n const match = result.templates.find(t => t.subject.trim().toLowerCase() === wanted);\n if (!match) {\n // The read above was filtered, so it can't name the alternatives. A\n // second read without bodies can, cheaply.\n const all = await deps.readTemplateEmails({ account, folder, limit: 200, includeBody: false });\n const names = all.templates.map(t => t.subject).filter(Boolean).join(', ') || '(none)';\n throw new NotFoundError('template', `Template '${subject}' not found in '${folder}'. Templates there: ${names}.`);\n }\n if (match.htmlBody.trim() === '') {\n throw new NotFoundError('template', `Template '${subject}' has an empty HTML body.`);\n }\n return match.htmlBody;\n}\n\n/** Where a template asks for the signature. */\nconst SIGNATURE_TOKEN = '{{SIGNATURE}}';\n\n/** A named signature's HTML, ready to insert into a body; NOT_FOUND naming the ones there are. */\nexport async function resolveSignatureHtml(\n deps: Pick<ReplyBodyDeps, 'readOutlookSignatureHtml' | 'listOutlookSignatures'>,\n name: string,\n): Promise<string> {\n const signatureHtml = await deps.readOutlookSignatureHtml(name);\n if (signatureHtml.trim() === '') {\n const available = (await deps.listOutlookSignatures()).join(', ') || '(none)';\n throw new NotFoundError('signature', `Outlook signature '${name}' not found. Signatures on this machine: ${available}.`);\n }\n return innerBodyHtml(signatureHtml);\n}\n\n/** Whether a body says where its signature goes. */\nfunction asksForSignature(html: string): boolean {\n return findTokens(html, [SIGNATURE_TOKEN]).length > 0;\n}\n\n/**\n * Sign a body: in its `{{SIGNATURE}}` placeholder when it has one, otherwise at\n * the end, where Outlook itself would put a signature. In a whole document the\n * end is just inside `</body>`, since anything after `</html>` is outside the\n * email.\n */\nexport function placeSignature(html: string, signature: string): string {\n if (asksForSignature(html)) return replaceToken(html, SIGNATURE_TOKEN, signature);\n const block = `<br>${signature}`;\n const close = html.toLowerCase().lastIndexOf('</body');\n return close === -1 ? html + block : html.slice(0, close) + block + html.slice(close);\n}\n\n/**\n * The HTML to insert above the quoted original: template resolved, section\n * kept, placeholders and signature filled, document wrapper removed.\n *\n * A body taken from a template is always put through composition, even with\n * nothing to fill, so a stray [[SECTION]] marker is caught here rather than\n * mailed out.\n */\nexport async function composeReplyHtml(content: ReplyContent, deps: ReplyBodyDeps): Promise<string> {\n let html = content.htmlBody;\n let fromTemplate = false;\n if ((!html || html.trim() === '') && content.templateSubject) {\n html = await resolveTemplateHtml(deps, content.account, content.templateFolder, content.templateSubject);\n fromTemplate = true;\n }\n if (!html || html.trim() === '') {\n throw new InvalidRequestError('A reply needs either htmlBody or templateSubject.');\n }\n\n // A signature with a placeholder to go in is filled with the others, so\n // one that sat in a dropped section goes with it. Without one it is added\n // below the new text once everything else is composed.\n let placeholders = content.templatePlaceholders;\n const signature = content.signatureName ? await resolveSignatureHtml(deps, content.signatureName) : '';\n const signatureInPlace = signature !== '' && asksForSignature(html);\n if (signatureInPlace) placeholders = { ...placeholders, SIGNATURE: signature };\n\n const hasPlaceholders = !!placeholders && Object.keys(placeholders).length > 0;\n if (fromTemplate || content.templateSection || hasPlaceholders) {\n html = composeTemplateBody(html, {\n section: content.templateSection,\n placeholders,\n label: content.templateSubject,\n });\n }\n const body = innerBodyHtml(html);\n return signature !== '' && !signatureInPlace ? placeSignature(body, signature) : body;\n}\n\n/**\n * Where the new text goes in an Outlook-built reply: straight after the opening\n * of `WordSection1`, the div holding the empty paragraph where a person's cursor\n * would be. A reply Outlook didn't build that way falls back to just inside\n * `<body>`. The insertion itself happens in each platform's own language, since\n * the reply HTML never leaves Outlook; this anchor is the part both must agree on.\n */\nexport const WORD_SECTION_ANCHOR = 'WordSection1>';\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** How deep under the mailbox root a template folder is searched for. */
|
|
2
|
+
export declare const TEMPLATE_SEARCH_DEPTH = 3;
|
|
3
|
+
/** Cap on one template body; a Word-generated one is large, and a runaway one shouldn't sink the call. */
|
|
4
|
+
export declare const MAX_TEMPLATE_HTML = 100000;
|
|
5
|
+
/** Characters of plain text standing in for a body that wasn't asked for. */
|
|
6
|
+
export declare const TEMPLATE_PREVIEW_CHARS = 200;
|
|
7
|
+
/**
|
|
8
|
+
* An embedded (cid:) image tag, removed from a template body as it is read.
|
|
9
|
+
* Those images live as attachments on the TEMPLATE item, so its HTML reused
|
|
10
|
+
* elsewhere would show broken-image placeholders; template emails are text and
|
|
11
|
+
* markup only. Matched ignoring case on both platforms, since editors write
|
|
12
|
+
* both `<img src=` and `<IMG SRC=`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const EMBEDDED_IMAGE_SOURCE = "<img[^>]*src=\"cid:[^\"]*\"[^>]*>";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// How far both platforms go when reading a folder of template emails, and what
|
|
3
|
+
// they strip from a body, stated once so a template that reads one way on
|
|
4
|
+
// Windows can't read another on macOS.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.EMBEDDED_IMAGE_SOURCE = exports.TEMPLATE_PREVIEW_CHARS = exports.MAX_TEMPLATE_HTML = exports.TEMPLATE_SEARCH_DEPTH = void 0;
|
|
7
|
+
/** How deep under the mailbox root a template folder is searched for. */
|
|
8
|
+
exports.TEMPLATE_SEARCH_DEPTH = 3;
|
|
9
|
+
/** Cap on one template body; a Word-generated one is large, and a runaway one shouldn't sink the call. */
|
|
10
|
+
exports.MAX_TEMPLATE_HTML = 100_000;
|
|
11
|
+
/** Characters of plain text standing in for a body that wasn't asked for. */
|
|
12
|
+
exports.TEMPLATE_PREVIEW_CHARS = 200;
|
|
13
|
+
/**
|
|
14
|
+
* An embedded (cid:) image tag, removed from a template body as it is read.
|
|
15
|
+
* Those images live as attachments on the TEMPLATE item, so its HTML reused
|
|
16
|
+
* elsewhere would show broken-image placeholders; template emails are text and
|
|
17
|
+
* markup only. Matched ignoring case on both platforms, since editors write
|
|
18
|
+
* both `<img src=` and `<IMG SRC=`.
|
|
19
|
+
*/
|
|
20
|
+
exports.EMBEDDED_IMAGE_SOURCE = '<img[^>]*src="cid:[^"]*"[^>]*>';
|
|
21
|
+
//# sourceMappingURL=templateLimits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templateLimits.js","sourceRoot":"","sources":["../../src/shared/templateLimits.ts"],"names":[],"mappings":";AAAA,+EAA+E;AAC/E,0EAA0E;AAC1E,uCAAuC;;;AAEvC,yEAAyE;AAC5D,QAAA,qBAAqB,GAAG,CAAC,CAAC;AAEvC,0GAA0G;AAC7F,QAAA,iBAAiB,GAAG,OAAO,CAAC;AAEzC,6EAA6E;AAChE,QAAA,sBAAsB,GAAG,GAAG,CAAC;AAE1C;;;;;;GAMG;AACU,QAAA,qBAAqB,GAAG,gCAAgC,CAAC","sourcesContent":["// How far both platforms go when reading a folder of template emails, and what\n// they strip from a body, stated once so a template that reads one way on\n// Windows can't read another on macOS.\n\n/** How deep under the mailbox root a template folder is searched for. */\nexport const TEMPLATE_SEARCH_DEPTH = 3;\n\n/** Cap on one template body; a Word-generated one is large, and a runaway one shouldn't sink the call. */\nexport const MAX_TEMPLATE_HTML = 100_000;\n\n/** Characters of plain text standing in for a body that wasn't asked for. */\nexport const TEMPLATE_PREVIEW_CHARS = 200;\n\n/**\n * An embedded (cid:) image tag, removed from a template body as it is read.\n * Those images live as attachments on the TEMPLATE item, so its HTML reused\n * elsewhere would show broken-image placeholders; template emails are text and\n * markup only. Matched ignoring case on both platforms, since editors write\n * both `<img src=` and `<IMG SRC=`.\n */\nexport const EMBEDDED_IMAGE_SOURCE = '<img[^>]*src=\"cid:[^\"]*\"[^>]*>';\n"]}
|
package/dist/templateBody.d.ts
CHANGED
|
@@ -28,6 +28,12 @@ export interface TemplateMarkers {
|
|
|
28
28
|
}
|
|
29
29
|
/** Find the markers in a template body (HTML, or the plain text Outlook derives from it). */
|
|
30
30
|
export declare function findTemplateMarkers(body: string): TemplateMarkers;
|
|
31
|
+
/**
|
|
32
|
+
* Any `[[SECTION]]`, `[[/SECTION]]` or `{{PLACEHOLDER}}` token, paired or not, as
|
|
33
|
+
* a pattern source JavaScript and .NET read the same way: Windows scans a
|
|
34
|
+
* template's plain text with it inside PowerShell.
|
|
35
|
+
*/
|
|
36
|
+
export declare const MARKER_TOKEN_SOURCE = "\\[\\[\\s*/?\\s*[A-Za-z0-9_-]{1,40}\\s*\\]\\]|\\{\\{\\s*[A-Za-z0-9_-]{1,40}\\s*\\}\\}";
|
|
31
37
|
/** Replace every occurrence of `token` with `value`, which is never rescanned. */
|
|
32
38
|
export declare function replaceToken(html: string, token: string, value: string): string;
|
|
33
39
|
/**
|
package/dist/templateBody.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* written.
|
|
23
23
|
*/
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.MARKER_TOKEN_SOURCE = void 0;
|
|
25
26
|
exports.findTemplateMarkers = findTemplateMarkers;
|
|
26
27
|
exports.replaceToken = replaceToken;
|
|
27
28
|
exports.removeTokenLine = removeTokenLine;
|
|
@@ -105,6 +106,12 @@ function findTemplateMarkers(body) {
|
|
|
105
106
|
}
|
|
106
107
|
return { sections: opened.filter(n => closed.has(n)), placeholders };
|
|
107
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Any `[[SECTION]]`, `[[/SECTION]]` or `{{PLACEHOLDER}}` token, paired or not, as
|
|
111
|
+
* a pattern source JavaScript and .NET read the same way: Windows scans a
|
|
112
|
+
* template's plain text with it inside PowerShell.
|
|
113
|
+
*/
|
|
114
|
+
exports.MARKER_TOKEN_SOURCE = `\\[\\[\\s*/?\\s*${NAME}\\s*\\]\\]|\\{\\{\\s*${NAME}\\s*\\}\\}`;
|
|
108
115
|
/**
|
|
109
116
|
* Every `[[...]]` / `{{...}}` token still in a body, paired or not — the last
|
|
110
117
|
* check before a composed body goes out, so a half-deleted marker stops the
|
|
@@ -112,7 +119,7 @@ function findTemplateMarkers(body) {
|
|
|
112
119
|
*/
|
|
113
120
|
function markerTokens(body) {
|
|
114
121
|
const text = stripToText(body || '');
|
|
115
|
-
const re = new RegExp(
|
|
122
|
+
const re = new RegExp(exports.MARKER_TOKEN_SOURCE, 'g');
|
|
116
123
|
const found = [];
|
|
117
124
|
let m;
|
|
118
125
|
while ((m = re.exec(text)) !== null) {
|