@dawnlit/outlook-bridge 1.0.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.
Files changed (117) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +253 -0
  3. package/dist/OutlookService.d.ts +43 -0
  4. package/dist/OutlookService.js +137 -0
  5. package/dist/OutlookService.js.map +1 -0
  6. package/dist/errors.d.ts +112 -0
  7. package/dist/errors.js +140 -0
  8. package/dist/errors.js.map +1 -0
  9. package/dist/index.d.ts +11 -0
  10. package/dist/index.js +71 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/mac/accounts.d.ts +5 -0
  13. package/dist/mac/accounts.js +17 -0
  14. package/dist/mac/accounts.js.map +1 -0
  15. package/dist/mac/attachments.d.ts +28 -0
  16. package/dist/mac/attachments.js +120 -0
  17. package/dist/mac/attachments.js.map +1 -0
  18. package/dist/mac/bounces.d.ts +32 -0
  19. package/dist/mac/bounces.js +285 -0
  20. package/dist/mac/bounces.js.map +1 -0
  21. package/dist/mac/cleanup.d.ts +29 -0
  22. package/dist/mac/cleanup.js +219 -0
  23. package/dist/mac/cleanup.js.map +1 -0
  24. package/dist/mac/drafts.d.ts +27 -0
  25. package/dist/mac/drafts.js +214 -0
  26. package/dist/mac/drafts.js.map +1 -0
  27. package/dist/mac/folders.d.ts +28 -0
  28. package/dist/mac/folders.js +127 -0
  29. package/dist/mac/folders.js.map +1 -0
  30. package/dist/mac/index.d.ts +22 -0
  31. package/dist/mac/index.js +119 -0
  32. package/dist/mac/index.js.map +1 -0
  33. package/dist/mac/read.d.ts +46 -0
  34. package/dist/mac/read.js +406 -0
  35. package/dist/mac/read.js.map +1 -0
  36. package/dist/mac/run.d.ts +64 -0
  37. package/dist/mac/run.js +250 -0
  38. package/dist/mac/run.js.map +1 -0
  39. package/dist/mac/scripts.d.ts +137 -0
  40. package/dist/mac/scripts.js +337 -0
  41. package/dist/mac/scripts.js.map +1 -0
  42. package/dist/mac/send.d.ts +23 -0
  43. package/dist/mac/send.js +135 -0
  44. package/dist/mac/send.js.map +1 -0
  45. package/dist/mac/signatures.d.ts +4 -0
  46. package/dist/mac/signatures.js +36 -0
  47. package/dist/mac/signatures.js.map +1 -0
  48. package/dist/mac/templates.d.ts +34 -0
  49. package/dist/mac/templates.js +266 -0
  50. package/dist/mac/templates.js.map +1 -0
  51. package/dist/mail.d.ts +72 -0
  52. package/dist/mail.js +131 -0
  53. package/dist/mail.js.map +1 -0
  54. package/dist/outlookTemplateSections.d.ts +62 -0
  55. package/dist/outlookTemplateSections.js +313 -0
  56. package/dist/outlookTemplateSections.js.map +1 -0
  57. package/dist/runtime.d.ts +76 -0
  58. package/dist/runtime.js +123 -0
  59. package/dist/runtime.js.map +1 -0
  60. package/dist/shared/attachmentMatch.d.ts +32 -0
  61. package/dist/shared/attachmentMatch.js +50 -0
  62. package/dist/shared/attachmentMatch.js.map +1 -0
  63. package/dist/shared/bounceRules.d.ts +33 -0
  64. package/dist/shared/bounceRules.js +104 -0
  65. package/dist/shared/bounceRules.js.map +1 -0
  66. package/dist/shared/capabilities.d.ts +3 -0
  67. package/dist/shared/capabilities.js +9 -0
  68. package/dist/shared/capabilities.js.map +1 -0
  69. package/dist/shared/json.d.ts +11 -0
  70. package/dist/shared/json.js +45 -0
  71. package/dist/shared/json.js.map +1 -0
  72. package/dist/shared/replyBody.d.ts +49 -0
  73. package/dist/shared/replyBody.js +113 -0
  74. package/dist/shared/replyBody.js.map +1 -0
  75. package/dist/types.d.ts +365 -0
  76. package/dist/types.js +13 -0
  77. package/dist/types.js.map +1 -0
  78. package/dist/windows/accounts.d.ts +2 -0
  79. package/dist/windows/accounts.js +22 -0
  80. package/dist/windows/accounts.js.map +1 -0
  81. package/dist/windows/attachments.d.ts +28 -0
  82. package/dist/windows/attachments.js +141 -0
  83. package/dist/windows/attachments.js.map +1 -0
  84. package/dist/windows/bounces.d.ts +34 -0
  85. package/dist/windows/bounces.js +267 -0
  86. package/dist/windows/bounces.js.map +1 -0
  87. package/dist/windows/cleanup.d.ts +29 -0
  88. package/dist/windows/cleanup.js +165 -0
  89. package/dist/windows/cleanup.js.map +1 -0
  90. package/dist/windows/drafts.d.ts +29 -0
  91. package/dist/windows/drafts.js +253 -0
  92. package/dist/windows/drafts.js.map +1 -0
  93. package/dist/windows/folders.d.ts +16 -0
  94. package/dist/windows/folders.js +88 -0
  95. package/dist/windows/folders.js.map +1 -0
  96. package/dist/windows/index.d.ts +27 -0
  97. package/dist/windows/index.js +100 -0
  98. package/dist/windows/index.js.map +1 -0
  99. package/dist/windows/read.d.ts +54 -0
  100. package/dist/windows/read.js +372 -0
  101. package/dist/windows/read.js.map +1 -0
  102. package/dist/windows/run.d.ts +54 -0
  103. package/dist/windows/run.js +161 -0
  104. package/dist/windows/run.js.map +1 -0
  105. package/dist/windows/scripts.d.ts +56 -0
  106. package/dist/windows/scripts.js +163 -0
  107. package/dist/windows/scripts.js.map +1 -0
  108. package/dist/windows/send.d.ts +11 -0
  109. package/dist/windows/send.js +114 -0
  110. package/dist/windows/send.js.map +1 -0
  111. package/dist/windows/signatures.d.ts +14 -0
  112. package/dist/windows/signatures.js +69 -0
  113. package/dist/windows/signatures.js.map +1 -0
  114. package/dist/windows/templates.d.ts +25 -0
  115. package/dist/windows/templates.js +246 -0
  116. package/dist/windows/templates.js.map +1 -0
  117. package/package.json +50 -0
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FIND_FOLDER_PS = exports.SENDER_SMTP_PS = exports.DELIVERY_STORE_PS = void 0;
4
+ exports.accountScript = accountScript;
5
+ exports.namedStoreScript = namedStoreScript;
6
+ exports.getItemScript = getItemScript;
7
+ exports.mailScopeScript = mailScopeScript;
8
+ // The PowerShell fragments more than one Windows operation is built from.
9
+ //
10
+ // A fragment lives here once at least two modules emit it; one used by a single
11
+ // operation stays beside that operation, where its rules are readable next to
12
+ // the code that depends on them.
13
+ const run_1 = require("./run");
14
+ /**
15
+ * Open a MAPI session. `$outlook` and `$ns` are what every later fragment reads.
16
+ *
17
+ * `Logon()` is a no-op against an Outlook that is already running and starts one
18
+ * that isn't, so it costs nothing and removes a class of "works only when
19
+ * Outlook happens to be open" failure.
20
+ */
21
+ const PS_PRELUDE = `
22
+ $ErrorActionPreference = 'Stop'
23
+ $outlook = New-Object -ComObject Outlook.Application
24
+ $ns = $outlook.GetNamespace('mapi')
25
+ $ns.Logon()
26
+ `;
27
+ /**
28
+ * Session plus the account whose SMTP address matches; binds `$target` and
29
+ * `$account`.
30
+ *
31
+ * Fails rather than falling back to the default account. A bad address must
32
+ * never silently send from — or read — the wrong mailbox, and the thrown
33
+ * sentence is the one `classifyRunFailure` recognises to raise
34
+ * `AccountNotFoundError`, so the wording is load-bearing.
35
+ */
36
+ function accountScript(emailAccount) {
37
+ return `${PS_PRELUDE}
38
+ $target = '${(0, run_1.psEscape)(emailAccount)}'
39
+ $account = $null
40
+ foreach ($a in $ns.Accounts) {
41
+ if ($a.SmtpAddress -ieq $target) { $account = $a; break }
42
+ }
43
+ if ($account -eq $null) { throw "Account '$target' not found" }
44
+ `;
45
+ }
46
+ /** The account's own delivery store, as `$store`. Requires `accountScript`. */
47
+ exports.DELIVERY_STORE_PS = `
48
+ $store = $account.DeliveryStore
49
+ $storeId = $store.StoreID
50
+ `;
51
+ /**
52
+ * The account's store reached through the namespace's top-level folders, as
53
+ * `$storeFolder` / `$store`.
54
+ *
55
+ * Kept alongside `DELIVERY_STORE_PS` rather than replaced by it: the reading
56
+ * operations resolve the store this way, and on a profile where a mailbox is
57
+ * open under a display name that differs from the delivery store's, the two do
58
+ * not select the same folders. Changing which one a reader uses changes what it
59
+ * returns, so each keeps the route it was verified against.
60
+ */
61
+ function namedStoreScript(emailAccount) {
62
+ return `
63
+ $storeFolder = $null
64
+ foreach ($f in $ns.Folders) {
65
+ if ($f.Name -ieq $account.DisplayName) { $storeFolder = $f; break }
66
+ }
67
+ if ($storeFolder -eq $null) { throw "Store folder not found for account '${(0, run_1.psEscape)(emailAccount)}'" }
68
+ $store = $storeFolder.Store
69
+ $storeId = $store.StoreID
70
+ `;
71
+ }
72
+ /**
73
+ * Resolve one item into `$item`. A StoreID disambiguates across mailboxes, so
74
+ * pass it whenever the listing that produced the id carried one.
75
+ */
76
+ function getItemScript(entryId, storeId) {
77
+ return storeId
78
+ ? `$item = $ns.GetItemFromID('${(0, run_1.psEscape)(entryId)}', '${(0, run_1.psEscape)(storeId)}')`
79
+ : `$item = $ns.GetItemFromID('${(0, run_1.psEscape)(entryId)}')`;
80
+ }
81
+ /**
82
+ * The sender's real SMTP address, as `$senderSmtp`. Requires `$item`.
83
+ *
84
+ * An Exchange sender's `SenderEmailAddress` is an X500 DN rather than an
85
+ * address; a caller that means to reply to it, or correlate it with anything,
86
+ * needs the address it resolves to.
87
+ */
88
+ exports.SENDER_SMTP_PS = `
89
+ $senderSmtp = ''
90
+ try {
91
+ if ($item.SenderEmailType -eq 'EX') { $senderSmtp = $item.Sender.GetExchangeUser().PrimarySmtpAddress }
92
+ } catch {}
93
+ if (-not $senderSmtp) { $senderSmtp = if ($item.SenderEmailAddress) { $item.SenderEmailAddress } else { '' } }
94
+ `;
95
+ /**
96
+ * Recursive folder-by-name search. Case-insensitive, depth-capped so a huge
97
+ * mailbox tree can't hang the scan.
98
+ */
99
+ exports.FIND_FOLDER_PS = `
100
+ function Find-FolderByName($root, $name, $depth) {
101
+ foreach ($f in $root.Folders) {
102
+ if ($f.Name -ieq $name) { return $f }
103
+ }
104
+ if ($depth -le 1) { return $null }
105
+ foreach ($f in $root.Folders) {
106
+ $hit = Find-FolderByName $f $name ($depth - 1)
107
+ if ($hit -ne $null) { return $hit }
108
+ }
109
+ return $null
110
+ }
111
+ `;
112
+ /**
113
+ * Emit the PowerShell that resolves `$scope` from `$store`, walking a well-known
114
+ * root down through any further segments. Also binds `$scopeCreated`.
115
+ *
116
+ * Split out so the emitted script can be parse-checked without an Outlook
117
+ * session — the folder walk is the only part that varies per call, and a syntax
118
+ * error in it would only ever surface as a failed live run.
119
+ *
120
+ * `folderLabel` is the caller's original string, used verbatim in the error so
121
+ * the message names what they typed rather than the parsed segments. Set
122
+ * `createMissing` to build absent segments instead of throwing — the whole
123
+ * chain, so a nested destination can be created in one call.
124
+ */
125
+ function mailScopeScript(ref, folderLabel = '', createMissing = false) {
126
+ const root = `$store.GetDefaultFolder(${ref.rootId})`;
127
+ if (ref.segments.length === 0)
128
+ return `$scope = ${root}\n$scopeCreated = $false`;
129
+ return `
130
+ ${exports.FIND_FOLDER_PS}
131
+ $segments = @(${ref.segments.map(s => `'${(0, run_1.psEscape)(s)}'`).join(',')})
132
+ $scopeRoot = ${root}
133
+ $scope = $scopeRoot
134
+ $deepest = $scopeRoot
135
+ $scopeCreated = $false
136
+ foreach ($seg in $segments) {
137
+ $next = $null
138
+ foreach ($f in $scope.Folders) { if ($f.Name -ieq $seg) { $next = $f; break } }
139
+ if ($next -eq $null) { $deepest = $scope; $scope = $null; break }
140
+ $scope = $next
141
+ }
142
+ # A bare name that isn't a direct child still resolves by recursive search, so one
143
+ # folder string keeps working across tools.
144
+ if ($scope -eq $null -and $segments.Count -eq 1) {
145
+ $scope = Find-FolderByName $scopeRoot $segments[0] 3
146
+ }
147
+ if ($scope -eq $null -and ${createMissing ? '$true' : '$false'}) {
148
+ # Rebuild the whole chain from the root, creating only what is genuinely absent.
149
+ $scope = $scopeRoot
150
+ foreach ($seg in $segments) {
151
+ $next = $null
152
+ foreach ($f in $scope.Folders) { if ($f.Name -ieq $seg) { $next = $f; break } }
153
+ if ($next -eq $null) { $next = $scope.Folders.Add($seg); $scopeCreated = $true }
154
+ $scope = $next
155
+ }
156
+ }
157
+ if ($scope -eq $null) {
158
+ $names = @()
159
+ foreach ($f in $deepest.Folders) { $names += $f.Name }
160
+ throw "Folder '${(0, run_1.psEscape)(folderLabel)}' not found. Folders under '$($deepest.Name)': $($names -join ', ')"
161
+ }`;
162
+ }
163
+ //# sourceMappingURL=scripts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scripts.js","sourceRoot":"","sources":["../../src/windows/scripts.ts"],"names":[],"mappings":";;;AA+BA,sCASC;AAkBD,4CAUC;AAMD,sCAIC;AAgDD,0CAoCC;AAlKD,0EAA0E;AAC1E,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAC9E,iCAAiC;AACjC,+BAAiC;AAGjC;;;;;;GAMG;AACH,MAAM,UAAU,GAAG;;;;;CAKlB,CAAC;AAEF;;;;;;;;GAQG;AACH,SAAgB,aAAa,CAAC,YAAoB;IAC9C,OAAO,GAAG,UAAU;aACX,IAAA,cAAQ,EAAC,YAAY,CAAC;;;;;;CAMlC,CAAC;AACF,CAAC;AAED,+EAA+E;AAClE,QAAA,iBAAiB,GAAG;;;CAGhC,CAAC;AAEF;;;;;;;;;GASG;AACH,SAAgB,gBAAgB,CAAC,YAAoB;IACjD,OAAO;;;;;2EAKgE,IAAA,cAAQ,EAAC,YAAY,CAAC;;;CAGhG,CAAC;AACF,CAAC;AAED;;;GAGG;AACH,SAAgB,aAAa,CAAC,OAAe,EAAE,OAAgB;IAC3D,OAAO,OAAO;QACV,CAAC,CAAC,8BAA8B,IAAA,cAAQ,EAAC,OAAO,CAAC,OAAO,IAAA,cAAQ,EAAC,OAAO,CAAC,IAAI;QAC7E,CAAC,CAAC,8BAA8B,IAAA,cAAQ,EAAC,OAAO,CAAC,IAAI,CAAC;AAC9D,CAAC;AAED;;;;;;GAMG;AACU,QAAA,cAAc,GAAG;;;;;;CAM7B,CAAC;AAEF;;;GAGG;AACU,QAAA,cAAc,GAAG;;;;;;;;;;;;CAY7B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,SAAgB,eAAe,CAAC,GAAkB,EAAE,WAAW,GAAG,EAAE,EAAE,aAAa,GAAG,KAAK;IACvF,MAAM,IAAI,GAAG,2BAA2B,GAAG,CAAC,MAAM,GAAG,CAAC;IACtD,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,YAAY,IAAI,0BAA0B,CAAC;IACjF,OAAO;EACT,sBAAc;gBACA,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,IAAA,cAAQ,EAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;eACpD,IAAI;;;;;;;;;;;;;;;4BAeS,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ;;;;;;;;;;;;;qBAazC,IAAA,cAAQ,EAAC,WAAW,CAAC;EACxC,CAAC;AACH,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { ReplyEmailParams, ReplyEmailResult, SendEmailParams } from '../types';
2
+ /** Send (or display for review) an email through Outlook COM. */
3
+ export declare function sendOutlookEmail(params: SendEmailParams): Promise<void>;
4
+ /**
5
+ * Reply to an email, inserting the caller's HTML above the quoted original.
6
+ *
7
+ * The body may be given directly or named as a saved template — see
8
+ * `composeReplyHtml`, which resolves the template, its section and its
9
+ * placeholders before anything reaches Outlook.
10
+ */
11
+ export declare function replyOutlookEmail(params: ReplyEmailParams): Promise<ReplyEmailResult>;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendOutlookEmail = sendOutlookEmail;
4
+ exports.replyOutlookEmail = replyOutlookEmail;
5
+ // Composing outgoing mail: a new email, and a reply to an existing one.
6
+ const run_1 = require("./run");
7
+ const scripts_1 = require("./scripts");
8
+ const replyBody_1 = require("../shared/replyBody");
9
+ const json_1 = require("../shared/json");
10
+ const signatures_1 = require("./signatures");
11
+ const templates_1 = require("./templates");
12
+ /**
13
+ * Set the sending account through IDispatch reflection.
14
+ *
15
+ * Direct assignment (`$mail.SendUsingAccount = $account`) is a silent no-op under
16
+ * PowerShell's COM binding — the mail then goes out from whichever account
17
+ * Outlook considers default, which is the failure this package exists to prevent.
18
+ */
19
+ function sendUsingAccount(variable) {
20
+ return `[void]$${variable}.GetType().InvokeMember('SendUsingAccount', [Reflection.BindingFlags]::SetProperty, $null, $${variable}, @($account))`;
21
+ }
22
+ /**
23
+ * What to do with a composed item: send it, show it, or file it silently.
24
+ *
25
+ * `Display()` only shows the compose window — it never files the item, so a
26
+ * windowless draft has to `Save()` into Drafts explicitly.
27
+ */
28
+ function composeAction(variable, sendImmediately, openDraftWindow) {
29
+ if (sendImmediately)
30
+ return `$${variable}.Send()`;
31
+ return openDraftWindow !== false ? `$${variable}.Display()` : `$${variable}.Save()`;
32
+ }
33
+ /** Send (or display for review) an email through Outlook COM. */
34
+ async function sendOutlookEmail(params) {
35
+ (0, run_1.requireWindows)();
36
+ const attachLine = params.attachmentPath
37
+ ? `$mail.Attachments.Add('${(0, run_1.psEscape)(params.attachmentPath)}') | Out-Null`
38
+ : '';
39
+ // The HTML body goes through a file: inline, a large email is what pushes the
40
+ // generated script past the command-line length limit.
41
+ const body = (0, run_1.scriptInput)('email-body', params.htmlBody);
42
+ const script = `${(0, scripts_1.accountScript)(params.emailAccount)}
43
+ $mail = $outlook.CreateItem(0)
44
+ ${sendUsingAccount('mail')}
45
+ $mail.To = '${(0, run_1.psEscape)(params.to)}'
46
+ $mail.CC = '${(0, run_1.psEscape)(params.cc || '')}'
47
+ $mail.Subject = '${(0, run_1.psEscape)(params.subject)}'
48
+ $mail.HTMLBody = [IO.File]::ReadAllText('${(0, run_1.psEscape)(body.path)}', [Text.Encoding]::UTF8)
49
+ ${attachLine}
50
+ ${composeAction('mail', params.sendImmediately, params.openDraftWindow)}
51
+ `;
52
+ try {
53
+ await (0, run_1.runPowerShellFile)(script, 120000);
54
+ }
55
+ finally {
56
+ body.cleanup();
57
+ }
58
+ }
59
+ /**
60
+ * Reply to an email, inserting the caller's HTML above the quoted original.
61
+ *
62
+ * The body may be given directly or named as a saved template — see
63
+ * `composeReplyHtml`, which resolves the template, its section and its
64
+ * placeholders before anything reaches Outlook.
65
+ */
66
+ async function replyOutlookEmail(params) {
67
+ (0, run_1.requireWindows)();
68
+ const insertHtml = await (0, replyBody_1.composeReplyHtml)(params, {
69
+ readTemplateEmails: templates_1.readTemplateEmails,
70
+ readOutlookSignatureHtml: signatures_1.readOutlookSignatureHtml,
71
+ listOutlookSignatures: signatures_1.listOutlookSignatures,
72
+ });
73
+ const body = (0, run_1.scriptInput)('reply-insert', insertHtml);
74
+ const replyMethod = params.replyAll ? 'ReplyAll' : 'Reply';
75
+ // Outlook builds the reply as a Word document whose own empty paragraph sits
76
+ // inside WordSection1; inserting straight after that div's opening tag puts
77
+ // the new text where the user's cursor would have been. A reply Outlook did
78
+ // not build that way falls back to just inside <body>.
79
+ const script = `${(0, scripts_1.accountScript)(params.emailAccount)}
80
+ ${(0, scripts_1.getItemScript)(params.entryId, params.storeId)}
81
+ $reply = $item.${replyMethod}()
82
+ ${sendUsingAccount('reply')}
83
+
84
+ $insertHtml = [IO.File]::ReadAllText('${(0, run_1.psEscape)(body.path)}', [Text.Encoding]::UTF8)
85
+ $keyWord = '${(0, run_1.psEscape)(replyBody_1.WORD_SECTION_ANCHOR)}'
86
+ # Bound to a local first: each $reply.HTMLBody read marshals the WHOLE generated
87
+ # reply across COM, and a Word reply on a long thread runs to hundreds of KB.
88
+ $html = $reply.HTMLBody
89
+ $idx = $html.IndexOf($keyWord)
90
+ if ($idx -ge 0) { $idx += $keyWord.Length } else {
91
+ $m = [regex]::Match($html, '<body[^>]*>')
92
+ $idx = if ($m.Success) { $m.Index + $m.Length } else { 0 }
93
+ }
94
+ $reply.HTMLBody = $html.Insert($idx, $insertHtml)
95
+ $to = [string]$reply.To
96
+ $subject = [string]$reply.Subject
97
+ $sender = ''
98
+ try { $sender = [string]$item.SenderEmailAddress } catch {}
99
+ ${composeAction('reply', params.sendImmediately, params.openDraftWindow)}
100
+ ConvertTo-Json @{ to = $to; subject = $subject; repliedToSender = $sender }
101
+ `;
102
+ try {
103
+ const parsed = (0, json_1.parseObject)(await (0, run_1.runPowerShell)(script, 60000));
104
+ return {
105
+ to: (0, json_1.str)(parsed.to),
106
+ subject: (0, json_1.str)(parsed.subject),
107
+ repliedToSender: (0, json_1.str)(parsed.repliedToSender),
108
+ };
109
+ }
110
+ finally {
111
+ body.cleanup();
112
+ }
113
+ }
114
+ //# sourceMappingURL=send.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/windows/send.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Names of the user's Outlook signatures (the ".htm" files), sorted. Empty off
3
+ * Windows. Reads from disk synchronously but is declared async to match macOS,
4
+ * which has to ask Outlook itself — one signature for both platforms.
5
+ */
6
+ export declare function listOutlookSignatures(): Promise<string[]>;
7
+ /**
8
+ * Read a named signature's HTML with its image references rewritten to absolute
9
+ * file: URIs. Outlook stores signature images relative to a "<name>_files"
10
+ * folder; once the refs are absolute, assigning the HTML to a mail body lets
11
+ * Outlook resolve and embed the images on Display/Send. Returns '' if the
12
+ * signature can't be found.
13
+ */
14
+ export declare function readOutlookSignatureHtml(name: string): Promise<string>;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.listOutlookSignatures = listOutlookSignatures;
7
+ exports.readOutlookSignatureHtml = readOutlookSignatureHtml;
8
+ // Outlook stores each signature as "<name>.htm" (plus .rtf/.txt and a
9
+ // "<name>_files" folder for images) under %APPDATA%\Microsoft\Signatures, so
10
+ // both of these read the disk rather than driving COM.
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const url_1 = require("url");
14
+ const SIGNATURES_DIR = process.env.APPDATA
15
+ ? path_1.default.join(process.env.APPDATA, 'Microsoft', 'Signatures')
16
+ : '';
17
+ /**
18
+ * Names of the user's Outlook signatures (the ".htm" files), sorted. Empty off
19
+ * Windows. Reads from disk synchronously but is declared async to match macOS,
20
+ * which has to ask Outlook itself — one signature for both platforms.
21
+ */
22
+ async function listOutlookSignatures() {
23
+ if (process.platform !== 'win32' || !SIGNATURES_DIR)
24
+ return [];
25
+ try {
26
+ return fs_1.default.readdirSync(SIGNATURES_DIR)
27
+ .filter(f => f.toLowerCase().endsWith('.htm'))
28
+ .map(f => f.slice(0, -4))
29
+ .sort((a, b) => a.localeCompare(b));
30
+ }
31
+ catch {
32
+ return [];
33
+ }
34
+ }
35
+ /**
36
+ * Read a named signature's HTML with its image references rewritten to absolute
37
+ * file: URIs. Outlook stores signature images relative to a "<name>_files"
38
+ * folder; once the refs are absolute, assigning the HTML to a mail body lets
39
+ * Outlook resolve and embed the images on Display/Send. Returns '' if the
40
+ * signature can't be found.
41
+ */
42
+ async function readOutlookSignatureHtml(name) {
43
+ if (process.platform !== 'win32' || !SIGNATURES_DIR)
44
+ return '';
45
+ // Only accept a bare signature name — never a path — so a crafted name can't
46
+ // escape the Signatures folder.
47
+ const safe = path_1.default.basename(name);
48
+ const file = path_1.default.join(SIGNATURES_DIR, `${safe}.htm`);
49
+ if (!fs_1.default.existsSync(file))
50
+ return '';
51
+ // Classic Outlook signatures are saved as windows-1252, not UTF-8; decode by
52
+ // the charset the file declares so accented text / smart quotes survive.
53
+ const buf = fs_1.default.readFileSync(file);
54
+ const head = buf.toString('latin1', 0, 2048);
55
+ const charset = head.match(/charset=["']?([\w-]+)/i)?.[1] || 'utf-8';
56
+ let html;
57
+ try {
58
+ html = new TextDecoder(charset).decode(buf);
59
+ }
60
+ catch {
61
+ html = buf.toString('utf-8');
62
+ }
63
+ // pathToFileURL encodes spaces/specials the same way Outlook's relative refs
64
+ // are, so prefixing the (already relative, already-encoded) src keeps a valid URI.
65
+ const dirUri = (0, url_1.pathToFileURL)(SIGNATURES_DIR + path_1.default.sep).href;
66
+ html = html.replace(/(src|background)=(["'])(?!https?:|cid:|data:|file:|mailto:|#)/gi, `$1=$2${dirUri}`);
67
+ return html;
68
+ }
69
+ //# sourceMappingURL=signatures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signatures.js","sourceRoot":"","sources":["../../src/windows/signatures.ts"],"names":[],"mappings":";;;;;AAgBA,sDAUC;AASD,4DA0BC;AA7DD,sEAAsE;AACtE,6EAA6E;AAC7E,uDAAuD;AACvD,4CAAoB;AACpB,gDAAwB;AACxB,6BAAoC;AAEpC,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO;IACtC,CAAC,CAAC,cAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,YAAY,CAAC;IAC3D,CAAC,CAAC,EAAE,CAAC;AAET;;;;GAIG;AACI,KAAK,UAAU,qBAAqB;IACvC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,cAAc;QAAE,OAAO,EAAE,CAAC;IAC/D,IAAI,CAAC;QACD,OAAO,YAAE,CAAC,WAAW,CAAC,cAAc,CAAC;aAChC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;aAC7C,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;aACxB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,wBAAwB,CAAC,IAAY;IACvD,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,cAAc;QAAE,OAAO,EAAE,CAAC;IAC/D,6EAA6E;IAC7E,gCAAgC;IAChC,MAAM,IAAI,GAAG,cAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,cAAI,CAAC,IAAI,CAAC,cAAc,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC;IACtD,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACpC,6EAA6E;IAC7E,yEAAyE;IACzE,MAAM,GAAG,GAAG,YAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC;IACrE,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACD,IAAI,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACL,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IACD,6EAA6E;IAC7E,mFAAmF;IACnF,MAAM,MAAM,GAAG,IAAA,mBAAa,EAAC,cAAc,GAAG,cAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;IAC7D,IAAI,GAAG,IAAI,CAAC,OAAO,CACf,iEAAiE,EACjE,QAAQ,MAAM,EAAE,CACnB,CAAC;IACF,OAAO,IAAI,CAAC;AAChB,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { SaveTemplateResult, TemplateFolderResult } from '../types';
2
+ /**
3
+ * Read the template emails saved in a mailbox folder (default "Templates"),
4
+ * returning each item's full HTML body. When the folder doesn't exist, returns
5
+ * folderFound:false plus the mailbox's folder names instead of throwing, so the
6
+ * caller can ask the user whether to create it rather than fail.
7
+ */
8
+ export declare function readTemplateEmails(emailAccount: string, folderName?: string, limit?: number, includeBody?: boolean, subject?: string): Promise<TemplateFolderResult>;
9
+ /**
10
+ * Save a new template email into a mailbox folder (default "Templates"),
11
+ * creating the folder at the mailbox root if it doesn't exist. The item is a
12
+ * plain unsent mail (subject + HTML body) that can be edited in Outlook.
13
+ * Never overwrites an existing item — it always adds a new one, so check with
14
+ * readTemplateEmails first and only call after the user has agreed to create it.
15
+ */
16
+ export declare function saveTemplateEmail(emailAccount: string, subject: string, htmlBody: string, folderName?: string): Promise<SaveTemplateResult>;
17
+ /**
18
+ * Open an Outlook compose window pre-filled with `currentHtml`, wait for the
19
+ * user to close it, and return the HTML they saved.
20
+ *
21
+ * Deliberately unbounded: the run lasts as long as the person is editing. Give
22
+ * it an AbortSignal (`bridge.withOptions({ signal })`) if the caller needs a way
23
+ * to give up on them.
24
+ */
25
+ export declare function editEmailTemplate(label: string, currentHtml: string): Promise<string>;
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readTemplateEmails = readTemplateEmails;
4
+ exports.saveTemplateEmail = saveTemplateEmail;
5
+ exports.editEmailTemplate = editEmailTemplate;
6
+ // Template emails: ordinary mail items kept in a mailbox folder and reused as
7
+ // reply bodies, plus the compose-window editor for one.
8
+ const run_1 = require("./run");
9
+ const scripts_1 = require("./scripts");
10
+ const json_1 = require("../shared/json");
11
+ const outlookTemplateSections_1 = require("../outlookTemplateSections");
12
+ const mail_1 = require("../mail");
13
+ const errors_1 = require("../errors");
14
+ const runtime_1 = require("../runtime");
15
+ /**
16
+ * Read the template emails saved in a mailbox folder (default "Templates"),
17
+ * returning each item's full HTML body. When the folder doesn't exist, returns
18
+ * folderFound:false plus the mailbox's folder names instead of throwing, so the
19
+ * caller can ask the user whether to create it rather than fail.
20
+ */
21
+ async function readTemplateEmails(emailAccount, folderName = 'Templates', limit = 20, includeBody = true, subject = '') {
22
+ (0, run_1.requireWindows)();
23
+ const cap = (0, mail_1.clamp)(limit, 1, 50);
24
+ const wanted = (subject || '').trim();
25
+ const script = `${(0, scripts_1.accountScript)(emailAccount)}
26
+ $root = $account.DeliveryStore.GetRootFolder()
27
+ ${scripts_1.FIND_FOLDER_PS}
28
+ $folder = Find-FolderByName $root '${(0, run_1.psEscape)(folderName)}' 3
29
+ if ($folder -eq $null) {
30
+ $names = @()
31
+ foreach ($f in $root.Folders) { $names += $f.Name }
32
+ ConvertTo-Json @{ folderFound = $false; folderPath = ''; templates = @(); availableFolders = $names } -Depth 3
33
+ exit 0
34
+ }
35
+ $includeBody = ${(0, run_1.psBool)(includeBody)}
36
+ $wanted = '${(0, run_1.psEscape)(wanted)}'
37
+ $items = $folder.Items
38
+ $items.Sort('[LastModificationTime]', $true)
39
+ $results = @()
40
+ # Without a subject filter the sort order does the capping (newest first). With one,
41
+ # scan the whole folder — the wanted template need not be among the newest — and stop
42
+ # as soon as the cap is filled.
43
+ $count = $items.Count
44
+ for ($i = 1; $i -le $count; $i++) {
45
+ if ($results.Count -ge ${cap}) { break }
46
+ $item = $null
47
+ try { $item = $items.Item($i) } catch { continue }
48
+ if ($item -eq $null) { continue }
49
+ $cls = 0
50
+ try { $cls = [int]$item.Class } catch {}
51
+ if ($cls -ne 43) { continue } # olMail only
52
+ if ($wanted -ne '') {
53
+ $subj = ''
54
+ try { $subj = [string]$item.Subject } catch {}
55
+ if ($subj -eq $null) { $subj = '' }
56
+ if ($subj.Trim() -ine $wanted) { continue }
57
+ }
58
+ $html = ''
59
+ $preview = ''
60
+ $markers = ''
61
+ $plain = ''
62
+ if ($includeBody) {
63
+ try { $html = [string]$item.HTMLBody } catch {}
64
+ # Embedded (cid:) images live as attachments on the TEMPLATE item: reusing this HTML
65
+ # on a new email shows a broken "linked image" placeholder, and rewriting to file:
66
+ # URIs renders invisibly in modern Outlook. Neither works, so strip the img tags —
67
+ # template emails are text/HTML only.
68
+ $html = [regex]::Replace($html, '<img[^>]*src="cid:[^"]*"[^>]*>', '')
69
+ if ($html.Length -gt 100000) { $html = $html.Substring(0, 100000) }
70
+ } else {
71
+ # Bodies omitted: return a short plain-text preview so the caller can tell templates
72
+ # apart by subject without pulling the full (often huge) Word-generated HTML.
73
+ try { $plain = [string]$item.Body } catch { $plain = '' }
74
+ if ($plain) {
75
+ # ...plus the [[SECTION]] / {{PLACEHOLDER}} markers, scanned from the full text:
76
+ # that's what lets a caller confirm a sectioned template is intact without
77
+ # fetching its body. Word can split a marker across tags in the HTML but not
78
+ # in the plain-text body, so a literal scan is right here.
79
+ # (Backslashes in the regex are doubled because this whole script is a JS
80
+ # template literal — a single one would be eaten before PowerShell saw it.)
81
+ $found = @()
82
+ foreach ($x in [regex]::Matches($plain, '\\[\\[\\s*/?\\s*[A-Za-z0-9_-]{1,40}\\s*\\]\\]|\\{\\{\\s*[A-Za-z0-9_-]{1,40}\\s*\\}\\}')) {
83
+ $t = ($x.Value -replace '\\s', '')
84
+ if ($found -notcontains $t) { $found += $t }
85
+ }
86
+ $markers = ($found -join ' ')
87
+ $preview = $plain.Trim()
88
+ if ($preview.Length -gt 200) { $preview = $preview.Substring(0, 200) }
89
+ }
90
+ }
91
+ $results += [PSCustomObject]@{
92
+ entryId = $item.EntryID
93
+ subject = if ($item.Subject) { $item.Subject.Trim() } else { '' }
94
+ htmlBody = $html
95
+ bodyPreview = $preview
96
+ markers = $markers
97
+ lastModified = $item.LastModificationTime.ToString('yyyy-MM-dd HH:mm')
98
+ }
99
+ }
100
+ ConvertTo-Json @{ folderFound = $true; folderPath = $folder.FolderPath; templates = $results; availableFolders = @() } -Depth 4
101
+ `;
102
+ const parsed = (0, json_1.parseObject)(await (0, run_1.runPowerShell)(script, 60000));
103
+ return {
104
+ folderFound: Boolean(parsed.folderFound),
105
+ folderPath: (0, json_1.str)(parsed.folderPath),
106
+ templates: (0, json_1.toArray)(parsed.templates).map(item => {
107
+ const e = (0, json_1.record)(item);
108
+ const html = (0, json_1.str)(e.htmlBody);
109
+ // With a body in hand the HTML is authoritative (it survives Word splitting a
110
+ // marker across tags); without one, fall back to what the script scanned out
111
+ // of the plain-text body.
112
+ const markers = (0, outlookTemplateSections_1.findTemplateMarkers)(html || (0, json_1.str)(e.markers));
113
+ return {
114
+ entryId: (0, json_1.str)(e.entryId),
115
+ subject: (0, json_1.str)(e.subject),
116
+ htmlBody: html,
117
+ bodyPreview: (0, json_1.str)(e.bodyPreview),
118
+ sections: markers.sections,
119
+ placeholders: markers.placeholders,
120
+ lastModified: (0, json_1.str)(e.lastModified),
121
+ };
122
+ }),
123
+ availableFolders: (0, json_1.toArray)(parsed.availableFolders).map(json_1.str),
124
+ };
125
+ }
126
+ /**
127
+ * Save a new template email into a mailbox folder (default "Templates"),
128
+ * creating the folder at the mailbox root if it doesn't exist. The item is a
129
+ * plain unsent mail (subject + HTML body) that can be edited in Outlook.
130
+ * Never overwrites an existing item — it always adds a new one, so check with
131
+ * readTemplateEmails first and only call after the user has agreed to create it.
132
+ */
133
+ async function saveTemplateEmail(emailAccount, subject, htmlBody, folderName = 'Templates') {
134
+ (0, run_1.requireWindows)();
135
+ const body = (0, run_1.scriptInput)('template-body', htmlBody);
136
+ const script = `${(0, scripts_1.accountScript)(emailAccount)}
137
+ $root = $account.DeliveryStore.GetRootFolder()
138
+ ${scripts_1.FIND_FOLDER_PS}
139
+ $folder = Find-FolderByName $root '${(0, run_1.psEscape)(folderName)}' 3
140
+ $folderCreated = $false
141
+ if ($folder -eq $null) {
142
+ $folder = $root.Folders.Add('${(0, run_1.psEscape)(folderName)}')
143
+ $folderCreated = $true
144
+ }
145
+ $mail = $outlook.CreateItem(0)
146
+ $mail.Subject = '${(0, run_1.psEscape)(subject)}'
147
+ $mail.HTMLBody = [IO.File]::ReadAllText('${(0, run_1.psEscape)(body.path)}', [Text.Encoding]::UTF8)
148
+ $mail.Save()
149
+ $moved = $mail.Move($folder)
150
+ ConvertTo-Json @{ folderPath = $folder.FolderPath; folderCreated = $folderCreated }
151
+ `;
152
+ try {
153
+ const parsed = (0, json_1.parseObject)(await (0, run_1.runPowerShell)(script, 60000));
154
+ return {
155
+ folderPath: (0, json_1.str)(parsed.folderPath),
156
+ folderCreated: Boolean(parsed.folderCreated),
157
+ };
158
+ }
159
+ finally {
160
+ body.cleanup();
161
+ }
162
+ }
163
+ /**
164
+ * Open an Outlook compose window pre-filled with `currentHtml`, wait for the
165
+ * user to close it, and return the HTML they saved.
166
+ *
167
+ * Deliberately unbounded: the run lasts as long as the person is editing. Give
168
+ * it an AbortSignal (`bridge.withOptions({ signal })`) if the caller needs a way
169
+ * to give up on them.
170
+ */
171
+ async function editEmailTemplate(label, currentHtml) {
172
+ (0, run_1.requireWindows)();
173
+ const input = (0, run_1.scriptInput)('template-input', currentHtml);
174
+ const outputFile = (0, runtime_1.tempFile)('template-output', 'html');
175
+ const subject = `${label} - Save (Ctrl+S) and close when done`;
176
+ const script = `
177
+ $ErrorActionPreference = 'Stop'
178
+ $inputPath = '${(0, run_1.psEscape)(input.path)}'
179
+ $outputPath = '${(0, run_1.psEscape)(outputFile)}'
180
+ $subject = '${(0, run_1.psEscape)(subject)}'
181
+
182
+ $outlook = New-Object -ComObject Outlook.Application
183
+ $ns = $outlook.GetNamespace('mapi')
184
+ $mail = $outlook.CreateItem(0)
185
+ $mail.Subject = $subject
186
+ $mail.HTMLBody = [IO.File]::ReadAllText($inputPath, [Text.Encoding]::UTF8)
187
+ $mail.Display()
188
+
189
+ # Give the inspector time to fully initialize
190
+ Start-Sleep -Seconds 2
191
+
192
+ # Poll until the compose window is closed, keeping the last live body as a
193
+ # fallback for the case where the user edits but never presses Ctrl+S.
194
+ $liveBody = ''
195
+ while ($true) {
196
+ Start-Sleep -Milliseconds 700
197
+ $open = $false
198
+ try {
199
+ foreach ($insp in $outlook.Inspectors) {
200
+ $ci = $null
201
+ try { $ci = $insp.CurrentItem } catch {}
202
+ if ($ci -ne $null -and $ci.Subject -eq $subject) {
203
+ $open = $true
204
+ try { $liveBody = $ci.HTMLBody } catch {}
205
+ break
206
+ }
207
+ }
208
+ } catch {}
209
+ if (-not $open) { break }
210
+ }
211
+
212
+ # Prefer the saved draft — it reflects the user's Ctrl+S and survives the
213
+ # inspector closing, unlike the original mail reference. Also removes any
214
+ # leftover editor drafts so they don't pile up in the Drafts folder.
215
+ $savedBody = ''
216
+ try {
217
+ $drafts = $ns.GetDefaultFolder(16)
218
+ $draftItems = $drafts.Items
219
+ for ($i = $draftItems.Count; $i -ge 1; $i--) {
220
+ $it = $draftItems.Item($i)
221
+ if ($it.Subject -eq $subject) {
222
+ if (-not $savedBody) { try { $savedBody = $it.HTMLBody } catch {} }
223
+ try { $it.Delete() } catch {}
224
+ }
225
+ }
226
+ } catch {}
227
+
228
+ $finalBody = if ($savedBody) { $savedBody } else { $liveBody }
229
+ if ($finalBody) {
230
+ [IO.File]::WriteAllText($outputPath, $finalBody, [Text.Encoding]::UTF8)
231
+ }
232
+ `;
233
+ try {
234
+ // 0 disables the timeout: this run is as long as the edit takes.
235
+ await (0, run_1.runPowerShellFile)(script, 0);
236
+ }
237
+ finally {
238
+ input.cleanup();
239
+ }
240
+ const result = (0, run_1.readScriptOutput)(outputFile);
241
+ if (!result) {
242
+ throw new errors_1.NotFoundError('template', 'No template saved. Did you save (Ctrl+S) before closing?');
243
+ }
244
+ return result;
245
+ }
246
+ //# sourceMappingURL=templates.js.map