@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,372 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readInboxEmails = readInboxEmails;
4
+ exports.readEmailBody = readEmailBody;
5
+ exports.searchInboxByFilter = searchInboxByFilter;
6
+ exports.readSelectedEmail = readSelectedEmail;
7
+ exports.openOutlookEmail = openOutlookEmail;
8
+ // Reading mail: one folder, one item, a whole Inbox tree, or whatever is
9
+ // selected in the running Outlook.
10
+ const run_1 = require("./run");
11
+ const scripts_1 = require("./scripts");
12
+ const json_1 = require("../shared/json");
13
+ const mail_1 = require("../mail");
14
+ const errors_1 = require("../errors");
15
+ /**
16
+ * An arbitrary email body crosses the PowerShell boundary base64'd: it carries
17
+ * quotes, control characters and non-ASCII that would otherwise have to survive
18
+ * both the console codepage and JSON string escaping intact.
19
+ */
20
+ function decodeBody(value) {
21
+ return Buffer.from((0, json_1.str)(value), 'base64').toString('utf8');
22
+ }
23
+ // Outgoing roots keep their timestamp under a different COM property; anything
24
+ // else carries ReceivedTime.
25
+ const OUTGOING_DATE_PROPS = {
26
+ [mail_1.WELL_KNOWN_FOLDERS['sent items']]: 'SentOn',
27
+ [mail_1.WELL_KNOWN_FOLDERS.outbox]: 'SentOn',
28
+ [mail_1.WELL_KNOWN_FOLDERS.drafts]: 'LastModificationTime',
29
+ };
30
+ /**
31
+ * Read recent emails from the Outlook inbox for the given account.
32
+ * No subject filtering — returns everything within the date window, up to the limit.
33
+ * Does NOT download attachments; returns attachment names only.
34
+ *
35
+ * `folder` scopes the read to one folder under the Inbox instead of the Inbox
36
+ * root. Without it, mail already filed away (Inbox\\Invoices) is unreachable —
37
+ * which is the normal state of any mailbox its owner keeps tidy. Segments are
38
+ * matched as direct children; a bare name that isn't a direct child falls back to
39
+ * the same recursive by-name search moveOutlookEmails uses, so one folder string
40
+ * works in both. Only the named folder is read — its own subfolders are not.
41
+ */
42
+ async function readInboxEmails(emailAccount, daysBack = 60, limit = 50, folder) {
43
+ if (process.platform !== 'win32')
44
+ return [];
45
+ const ref = folder ? (0, mail_1.mailFolderRef)(folder) : { rootId: 6, rootLabel: 'Inbox', segments: [] };
46
+ // A folder argument that trims away to nothing ("\\", " ") would otherwise
47
+ // read the Inbox root and look like it had scoped — the exact silent
48
+ // mis-scoping this parameter exists to prevent. A bare well-known name
49
+ // ("Sent Items") legitimately has no segments, so it is not that case.
50
+ if (folder && folder.trim() && ref.segments.length === 0 && ref.rootId === 6
51
+ && !Object.prototype.hasOwnProperty.call(mail_1.WELL_KNOWN_FOLDERS, folder.trim().toLowerCase())) {
52
+ throw new errors_1.NotFoundError('folder', `Folder '${folder}' does not name a folder under the Inbox.`);
53
+ }
54
+ // Which timestamp the folder's items actually carry (see the note in the script).
55
+ const dateProp = OUTGOING_DATE_PROPS[ref.rootId] ?? 'ReceivedTime';
56
+ const isOutgoing = (0, run_1.psBool)((0, mail_1.isOutgoingRoot)(ref.rootId));
57
+ const script = `${(0, scripts_1.accountScript)(emailAccount)}
58
+ ${(0, scripts_1.namedStoreScript)(emailAccount)}
59
+ ${(0, scripts_1.mailScopeScript)(ref, folder || '')}
60
+ $scopePath = $scope.FolderPath
61
+ $cutoff = (Get-Date).AddDays(-${daysBack}).ToString('MM/dd/yyyy HH:mm')
62
+ # Sent Items and Drafts carry no ReceivedTime, so restricting on it there returns
63
+ # an empty set that looks exactly like an empty folder. Filter each on the date
64
+ # property it actually has.
65
+ $filtered = $scope.Items.Restrict("[${dateProp}] >= '$cutoff'")
66
+ $filtered.Sort('[${dateProp}]', $true)
67
+ $results = @()
68
+ $cap = [Math]::Min($filtered.Count, ${limit})
69
+ for ($i = 1; $i -le $cap; $i++) {
70
+ $item = $filtered.Item($i)
71
+ $subj = if ($item.Subject) { $item.Subject.Trim() } else { '' }
72
+ $bodyText = if ($item.Body) { $item.Body } else { '' }
73
+ $bodyPreview = if ($bodyText.Length -gt 600) { $bodyText.Substring(0, 600) } else { $bodyText }
74
+ $attNames = @()
75
+ foreach ($att in $item.Attachments) { $attNames += $att.FileName }
76
+ $stamp = ''
77
+ try { $stamp = $item.${dateProp}.ToString('yyyy-MM-dd HH:mm') } catch {}
78
+ # Outgoing mail has no meaningful sender line of its own — report who it is TO,
79
+ # or a Sent Items listing reads as a folder of mail from yourself.
80
+ $who = ''
81
+ try { $who = if ($item.To) { $item.To } else { '' } } catch {}
82
+ $results += [PSCustomObject]@{
83
+ entryId = $item.EntryID
84
+ storeId = $storeId
85
+ folderPath = $scopePath
86
+ subject = $subj
87
+ senderName = if (${isOutgoing}) { $who } elseif ($item.SenderName) { $item.SenderName } else { '' }
88
+ senderEmail = if (${isOutgoing}) { $who } elseif ($item.SenderEmailAddress) { $item.SenderEmailAddress } else { '' }
89
+ receivedTime = $stamp
90
+ bodyPreview = $bodyPreview
91
+ attachmentNames = $attNames
92
+ attachmentCount = $item.Attachments.Count
93
+ }
94
+ }
95
+ ConvertTo-Json $results -Depth 3
96
+ `;
97
+ return (0, json_1.parseArray)(await (0, run_1.runPowerShell)(script, 30000)).map(item => {
98
+ const e = (0, json_1.record)(item);
99
+ return {
100
+ entryId: (0, json_1.str)(e.entryId),
101
+ storeId: (0, json_1.str)(e.storeId),
102
+ folderPath: (0, json_1.str)(e.folderPath),
103
+ subject: (0, json_1.str)(e.subject),
104
+ senderName: (0, json_1.str)(e.senderName),
105
+ senderEmail: (0, json_1.str)(e.senderEmail),
106
+ receivedTime: (0, json_1.str)(e.receivedTime),
107
+ bodyPreview: (0, json_1.str)(e.bodyPreview),
108
+ attachmentNames: (0, json_1.toArray)(e.attachmentNames).map(json_1.str),
109
+ attachmentCount: typeof e.attachmentCount === 'number' ? e.attachmentCount : 0,
110
+ };
111
+ });
112
+ }
113
+ /**
114
+ * Read one email's full plain-text body by EntryID.
115
+ *
116
+ * Uses `MailItem.Body`, not `HTMLBody`: Outlook's own plain-text rendering is
117
+ * what every other reader here consumes, and the markup costs an order of
118
+ * magnitude more for the same sentences. The tradeoff is that HTML tables
119
+ * flatten, so figures broken out in a table lose their row/column pairing — read
120
+ * those from the attachment where there is one.
121
+ *
122
+ * Exists because `readInboxEmails` caps its preview at 600 chars, and a reply
123
+ * whose first 600 chars read as complete can still carry a material detail below
124
+ * the cut. Nothing detects that from the preview alone.
125
+ *
126
+ * `includeQuoted` is off by default — see splitQuotedOriginal for why the
127
+ * quoted thread is a hazard to a caller reading figures out of a reply.
128
+ * `quotedLength` is reported either way so a caller can tell it exists and ask again.
129
+ */
130
+ async function readEmailBody(entryId, storeId, maxChars = 8000, includeQuoted = false) {
131
+ (0, run_1.requireWindows)();
132
+ const script = `
133
+ $outlook = New-Object -ComObject Outlook.Application
134
+ $ns = $outlook.GetNamespace('mapi')
135
+ $ns.Logon()
136
+ ${(0, scripts_1.getItemScript)(entryId, storeId)}
137
+ if ($item -eq $null) { throw "Email not found for EntryID '${(0, run_1.psEscape)(entryId)}'" }
138
+ $cls = 0
139
+ try { $cls = [int]$item.Class } catch {}
140
+ if ($cls -ne 43 -and $cls -ne 46) { throw "The item for this EntryID is not an email (Class=$cls). Re-run readInboxEmails for a current EntryID." }
141
+ $bodyRaw = ''
142
+ try { if ($item.Body) { $bodyRaw = [string]$item.Body } } catch {}
143
+ $attNames = @()
144
+ try { foreach ($att in $item.Attachments) { $attNames += $att.FileName } } catch {}
145
+ ${scripts_1.SENDER_SMTP_PS}
146
+ $received = ''
147
+ try { $received = $item.ReceivedTime.ToString('yyyy-MM-dd HH:mm') } catch {}
148
+ $out = [PSCustomObject]@{
149
+ entryId = $item.EntryID
150
+ subject = if ($item.Subject) { $item.Subject.Trim() } else { '' }
151
+ senderName = if ($item.SenderName) { $item.SenderName } else { '' }
152
+ senderEmail = $senderSmtp
153
+ receivedTime = $received
154
+ body = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($bodyRaw))
155
+ attachmentNames = $attNames
156
+ }
157
+ ConvertTo-Json $out -Depth 3 -Compress
158
+ `;
159
+ const raw = await (0, run_1.runPowerShell)(script, 20000);
160
+ if (!raw || !raw.trim())
161
+ throw new errors_1.NotFoundError('email', 'Failed to read email body.');
162
+ const parsed = (0, json_1.parseObject)(raw);
163
+ const { body, quoted, separator } = (0, mail_1.splitQuotedOriginal)(decodeBody(parsed.body));
164
+ const attachmentNames = (0, json_1.toArray)(parsed.attachmentNames).map(json_1.str);
165
+ // The quoted thread is context, never the sender's own answer, so it is capped
166
+ // harder than the reply itself — its useful part (what was asked) is at the
167
+ // top of it.
168
+ const quotedCap = Math.min(maxChars, 4000);
169
+ return {
170
+ entryId: (0, json_1.str)(parsed.entryId),
171
+ subject: (0, json_1.str)(parsed.subject),
172
+ senderName: (0, json_1.str)(parsed.senderName),
173
+ senderEmail: (0, json_1.str)(parsed.senderEmail),
174
+ receivedTime: (0, json_1.str)(parsed.receivedTime),
175
+ body: body.length > maxChars ? body.slice(0, maxChars) : body,
176
+ truncated: body.length > maxChars,
177
+ bodyLength: body.length,
178
+ quoteSeparator: separator,
179
+ quotedLength: quoted.length,
180
+ quotedOriginal: includeQuoted ? quoted.slice(0, quotedCap) : '',
181
+ attachmentNames,
182
+ attachmentCount: attachmentNames.length,
183
+ };
184
+ }
185
+ /**
186
+ * Walk every folder under the Inbox (recursively) for one account and return
187
+ * the emails matching `filter` — full body included, attachments listed by
188
+ * name but not saved.
189
+ *
190
+ * Built for a scan that doesn't know in advance which subfolder holds what
191
+ * it's after; `readInboxEmails` covers the cheaper "one known folder" case.
192
+ *
193
+ * `daysBack` bounds the scan to items received within that many days — what a
194
+ * daily batch run wants, since a subject filter alone walks the whole Inbox
195
+ * tree and returns every match ever received. 0 keeps that unbounded
196
+ * behavior, and is only sane paired with `filter.subjectLike` so Restrict can
197
+ * narrow the set server-side before anything crosses COM.
198
+ */
199
+ async function searchInboxByFilter(emailAccount, filter = {}, daysBack = 0) {
200
+ if (process.platform !== 'win32')
201
+ return [];
202
+ const subjectLike = filter.subjectLike ? (0, run_1.psEscape)(filter.subjectLike) : '';
203
+ const subjectPatternSrc = filter.subjectPattern ? (0, run_1.psEscape)(filter.subjectPattern.source) : '';
204
+ // The subject filter reaches PowerShell as a single-quoted literal assigned to
205
+ // $subjLike, and the Restrict query is CONCATENATED from it. Interpolating it
206
+ // into the double-quoted query string directly — which is what the query needs
207
+ // to be, so `$cutoff` expands — would let a caller's `$(...)` execute, and
208
+ // would break the query outright on an apostrophe.
209
+ const boundedRestrict = subjectLike
210
+ ? `$folder.Items.Restrict("[ReceivedTime] >= '$cutoff' AND [Subject] like '" + $subjLike + "'")`
211
+ : `$folder.Items.Restrict("[ReceivedTime] >= '$cutoff'")`;
212
+ const unboundedRestrict = subjectLike
213
+ ? `$folder.Items.Restrict("[Subject] like '" + $subjLike + "'")`
214
+ : `$folder.Items`;
215
+ const script = `
216
+ $subjLike = '${subjectLike}'
217
+ ${(0, scripts_1.accountScript)(emailAccount)}
218
+ ${(0, scripts_1.namedStoreScript)(emailAccount)}
219
+ $inbox = $store.GetDefaultFolder(6)
220
+ $results = @()
221
+ $seen = @{}
222
+ $folders = [System.Collections.ArrayList]@($inbox)
223
+ $fi = 0
224
+ while ($fi -lt $folders.Count) {
225
+ try { foreach ($sub in $folders[$fi].Folders) { [void]$folders.Add($sub) } } catch {}
226
+ $fi++
227
+ }
228
+ $daysBack = ${daysBack}
229
+ foreach ($folder in $folders) {
230
+ if ($daysBack -gt 0) {
231
+ # Bounded scan. Filter on BOTH date and subject in the Restrict so Outlook does the
232
+ # work server-side — a date-only restrict hands back every item in the window for
233
+ # every folder in the tree, which is thousands of COM round-trips on a busy mailbox.
234
+ # Any stricter subject regex still runs on whatever survives.
235
+ $cutoff = (Get-Date).AddDays(-$daysBack).ToString('MM/dd/yyyy HH:mm')
236
+ try {
237
+ $filtered = ${boundedRestrict}
238
+ $fCount = $filtered.Count
239
+ } catch {
240
+ # Some stores reject the compound query; fall back to date-only.
241
+ $filtered = $folder.Items.Restrict("[ReceivedTime] >= '$cutoff'")
242
+ $fCount = $filtered.Count
243
+ }
244
+ } else {
245
+ $filtered = ${unboundedRestrict}
246
+ $fCount = $filtered.Count
247
+ ${subjectLike ? `if ($fCount -eq 0) {
248
+ $cutoff = (Get-Date).AddDays(-60).ToString('MM/dd/yyyy HH:mm')
249
+ $filtered = $folder.Items.Restrict("[ReceivedTime] >= '$cutoff'")
250
+ $fCount = $filtered.Count
251
+ }` : ''}
252
+ }
253
+ for ($i = 1; $i -le $fCount; $i++) {
254
+ $item = $filtered.Item($i)
255
+ if ($seen.ContainsKey($item.EntryID)) { continue }
256
+ $seen[$item.EntryID] = $true
257
+ $subject = $item.Subject
258
+ if (-not $subject) { continue }
259
+ $subject = $subject.Trim()
260
+ ${subjectPatternSrc ? `if ($subject -notmatch '${subjectPatternSrc}') { continue }` : ''}
261
+ ${filter.excludeReplies ? `if ($subject -imatch '^(re|fw[d]?)\\s*:') { continue }` : ''}
262
+ ${filter.requireAttachment ? `if ($item.Attachments.Count -eq 0) { continue }` : ''}
263
+ $attNames = @()
264
+ foreach ($att in $item.Attachments) { $attNames += $att.FileName }
265
+ $bodyRaw = if ($item.Body) { $item.Body } else { '' }
266
+ $bodyB64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($bodyRaw))
267
+ ${scripts_1.SENDER_SMTP_PS}
268
+ $received = ''
269
+ try { $received = $item.ReceivedTime.ToString('yyyy-MM-dd HH:mm') } catch {}
270
+ $results += [PSCustomObject]@{
271
+ entryId = $item.EntryID
272
+ storeId = $storeId
273
+ subject = $item.Subject.Trim()
274
+ senderName = if ($item.SenderName) { $item.SenderName } else { '' }
275
+ senderEmail = $senderSmtp
276
+ receivedTime = $received
277
+ body = $bodyB64
278
+ attachmentNames = $attNames
279
+ folderPath = $folder.FolderPath
280
+ }
281
+ }
282
+ }
283
+ ConvertTo-Json $results -Depth 3
284
+ `;
285
+ // Returns whole message bodies, so this is the call most likely to push against
286
+ // the stdout cap — raise `maxBufferBytes` via configure() before a scan that
287
+ // matches thousands of emails.
288
+ return (0, json_1.parseArray)(await (0, run_1.runPowerShell)(script, 300000)).map(item => {
289
+ const e = (0, json_1.record)(item);
290
+ return {
291
+ entryId: (0, json_1.str)(e.entryId),
292
+ storeId: (0, json_1.str)(e.storeId),
293
+ subject: (0, json_1.str)(e.subject),
294
+ senderName: (0, json_1.str)(e.senderName),
295
+ senderEmail: (0, json_1.str)(e.senderEmail),
296
+ receivedTime: (0, json_1.str)(e.receivedTime),
297
+ body: decodeBody(e.body),
298
+ attachmentNames: (0, json_1.toArray)(e.attachmentNames).map(json_1.str),
299
+ folderPath: (0, json_1.str)(e.folderPath),
300
+ };
301
+ });
302
+ }
303
+ /**
304
+ * Read the email currently selected (or open) in Outlook — full body,
305
+ * attachments listed by name but not saved.
306
+ */
307
+ async function readSelectedEmail() {
308
+ (0, run_1.requireWindows)();
309
+ const script = `
310
+ $outlook = New-Object -ComObject Outlook.Application
311
+ $item = $null
312
+ try {
313
+ $explorer = $outlook.ActiveExplorer()
314
+ if ($explorer -ne $null) {
315
+ $sel = $explorer.Selection
316
+ if ($sel -ne $null -and $sel.Count -ge 1) { $item = $sel.Item(1) }
317
+ }
318
+ } catch {}
319
+ if ($item -eq $null) {
320
+ try {
321
+ $insp = $outlook.ActiveInspector()
322
+ if ($insp -ne $null) { $item = $insp.CurrentItem }
323
+ } catch {}
324
+ }
325
+ if ($item -eq $null) { throw 'No email is selected in Outlook. Open Outlook, select (or open) an email, then try again.' }
326
+ if ($item.MessageClass -notlike 'IPM.Note*') { throw 'The selected Outlook item is not an email.' }
327
+ $attNames = @()
328
+ foreach ($att in $item.Attachments) { $attNames += $att.FileName }
329
+ $subject = ''
330
+ if ($item.Subject) { $subject = $item.Subject.Trim() }
331
+ $bodyRaw = if ($item.Body) { $item.Body } else { '' }
332
+ $bodyB64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($bodyRaw))
333
+ $storeId = ''
334
+ try { $storeId = $item.Parent.Store.StoreID } catch {}
335
+ ${scripts_1.SENDER_SMTP_PS}
336
+ $received = ''
337
+ try { $received = $item.ReceivedTime.ToString('yyyy-MM-dd HH:mm') } catch {}
338
+ $result = [PSCustomObject]@{
339
+ entryId = $item.EntryID
340
+ storeId = $storeId
341
+ subject = $subject
342
+ senderName = if ($item.SenderName) { $item.SenderName } else { '' }
343
+ senderEmail = $senderSmtp
344
+ receivedTime = $received
345
+ body = $bodyB64
346
+ attachmentNames = $attNames
347
+ }
348
+ ConvertTo-Json $result -Depth 3
349
+ `;
350
+ const raw = await (0, run_1.runPowerShell)(script, 30000);
351
+ if (!raw || raw.trim() === '' || raw.trim() === 'null') {
352
+ throw new errors_1.NotFoundError('email', 'Failed to read the selected Outlook email.');
353
+ }
354
+ const e = (0, json_1.parseObject)(raw);
355
+ return {
356
+ entryId: (0, json_1.str)(e.entryId),
357
+ storeId: (0, json_1.str)(e.storeId),
358
+ subject: (0, json_1.str)(e.subject),
359
+ senderName: (0, json_1.str)(e.senderName),
360
+ senderEmail: (0, json_1.str)(e.senderEmail),
361
+ receivedTime: (0, json_1.str)(e.receivedTime),
362
+ body: decodeBody(e.body),
363
+ attachmentNames: (0, json_1.toArray)(e.attachmentNames).map(json_1.str),
364
+ };
365
+ }
366
+ /** Open an email in Outlook by its EntryID. */
367
+ async function openOutlookEmail(entryId) {
368
+ (0, run_1.requireWindows)();
369
+ const script = `(New-Object -ComObject Outlook.Application).GetNamespace('mapi').GetItemFromID('${(0, run_1.psEscape)(entryId)}').Display()`;
370
+ await (0, run_1.runPowerShell)(script);
371
+ }
372
+ //# sourceMappingURL=read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/windows/read.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Refuse before generating anything when this isn't Windows.
3
+ *
4
+ * Called at the top of each operation as well as inside the two runners, so a
5
+ * call that would have written temp files or made preparatory reads doesn't do
6
+ * any of that first. The runners hold the only copy of the check itself — three
7
+ * near-identical guards worded three different ways is how "not supported" came
8
+ * to mean two different sentences.
9
+ */
10
+ export declare function requireWindows(): void;
11
+ /**
12
+ * Escape a string for a PowerShell SINGLE-quoted literal — the only context
13
+ * caller-supplied text may go in.
14
+ *
15
+ * A double-quoted PowerShell string expands `$(...)` subexpressions, so text
16
+ * placed there would execute. Where a generated script needs user text inside a
17
+ * double-quoted string (a Restrict query, which must expand `$cutoff`), assign
18
+ * it to a variable with this first and concatenate — see read.ts.
19
+ */
20
+ export declare function psEscape(s: string): string;
21
+ /** A PowerShell array literal of single-quoted strings. */
22
+ export declare function psList(values: readonly string[]): string;
23
+ /** `$true` / `$false` — PowerShell has no bare boolean literal. */
24
+ export declare function psBool(value: boolean): string;
25
+ /**
26
+ * Run a generated script and return its stdout.
27
+ *
28
+ * `timeout` overrides the configured default for this one call — pass it where a
29
+ * call has a genuinely different budget (a full-mailbox walk, a purge) rather
30
+ * than relying on the global. See `configure()` for both knobs.
31
+ */
32
+ export declare function runPowerShell(script: string, timeout?: number): Promise<string>;
33
+ /**
34
+ * Run a generated script from a temp `.ps1` file rather than `-Command`.
35
+ *
36
+ * For the two calls that drive a compose window: a script passed on the command
37
+ * line competes with the command-line length limit, and these carry enough
38
+ * inline text to reach it. Failures classify exactly as `runPowerShell`'s do —
39
+ * the file is an implementation detail, not a second error contract.
40
+ */
41
+ export declare function runPowerShellFile(script: string, timeout?: number): Promise<string>;
42
+ /**
43
+ * Write text a script will read back, and hand back a cleanup callback.
44
+ *
45
+ * Large HTML — an email body, a Word-sized template — goes to a file rather than
46
+ * into the script text: inline, it is what pushes a generated script past the
47
+ * command-line length limit.
48
+ */
49
+ export declare function scriptInput(kind: string, contents: string): {
50
+ path: string;
51
+ cleanup(): void;
52
+ };
53
+ /** Read a file written by a script, as UTF-8, or '' when it never appeared. */
54
+ export declare function readScriptOutput(path: string): string;
@@ -0,0 +1,161 @@
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.requireWindows = requireWindows;
7
+ exports.psEscape = psEscape;
8
+ exports.psList = psList;
9
+ exports.psBool = psBool;
10
+ exports.runPowerShell = runPowerShell;
11
+ exports.runPowerShellFile = runPowerShellFile;
12
+ exports.scriptInput = scriptInput;
13
+ exports.readScriptOutput = readScriptOutput;
14
+ // Getting a generated script to Windows PowerShell and its output back.
15
+ //
16
+ // Every Windows function here is "build a script, run it, coerce the JSON", so
17
+ // this module owns the two halves nobody should reimplement: the run itself
18
+ // (encoding, timeout, cancellation, failure classification, the debug hook) and
19
+ // the escaping rules for putting caller text into a script safely.
20
+ const child_process_1 = require("child_process");
21
+ const fs_1 = __importDefault(require("fs"));
22
+ const runtime_1 = require("../runtime");
23
+ const errors_1 = require("../errors");
24
+ const RUNNER = 'powershell';
25
+ /**
26
+ * Refuse before generating anything when this isn't Windows.
27
+ *
28
+ * Called at the top of each operation as well as inside the two runners, so a
29
+ * call that would have written temp files or made preparatory reads doesn't do
30
+ * any of that first. The runners hold the only copy of the check itself — three
31
+ * near-identical guards worded three different ways is how "not supported" came
32
+ * to mean two different sentences.
33
+ */
34
+ function requireWindows() {
35
+ if (process.platform !== 'win32') {
36
+ throw new errors_1.UnsupportedPlatformError(process.platform, 'Outlook COM automation is only supported on Windows.');
37
+ }
38
+ }
39
+ /**
40
+ * Escape a string for a PowerShell SINGLE-quoted literal — the only context
41
+ * caller-supplied text may go in.
42
+ *
43
+ * A double-quoted PowerShell string expands `$(...)` subexpressions, so text
44
+ * placed there would execute. Where a generated script needs user text inside a
45
+ * double-quoted string (a Restrict query, which must expand `$cutoff`), assign
46
+ * it to a variable with this first and concatenate — see read.ts.
47
+ */
48
+ function psEscape(s) {
49
+ return s.replace(/'/g, "''");
50
+ }
51
+ /** A PowerShell array literal of single-quoted strings. */
52
+ function psList(values) {
53
+ return values.map(v => `'${psEscape(v)}'`).join(',');
54
+ }
55
+ /** `$true` / `$false` — PowerShell has no bare boolean literal. */
56
+ function psBool(value) {
57
+ return value ? '$true' : '$false';
58
+ }
59
+ // Force UTF-8 on stdout. Windows PowerShell 5.1 otherwise encodes output in the
60
+ // OEM/ANSI console code page, whose "best-fit" mapping silently rewrites
61
+ // non-ASCII characters — e.g. a curly quote (U+201C) in an email body becomes a
62
+ // plain " — which corrupts the JSON these scripts emit (an unescaped quote) and
63
+ // breaks JSON.parse. Setting the output encoding first makes non-ASCII survive
64
+ // as real UTF-8 bytes, which Node then decodes correctly.
65
+ const UTF8_PRELUDE = '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n';
66
+ /**
67
+ * Run a generated script and return its stdout.
68
+ *
69
+ * `timeout` overrides the configured default for this one call — pass it where a
70
+ * call has a genuinely different budget (a full-mailbox walk, a purge) rather
71
+ * than relying on the global. See `configure()` for both knobs.
72
+ */
73
+ function runPowerShell(script, timeout) {
74
+ requireWindows();
75
+ const utf8Script = UTF8_PRELUDE + script;
76
+ return spawnPowerShell(['-NoProfile', '-NonInteractive', '-Command', utf8Script], utf8Script, timeout);
77
+ }
78
+ /**
79
+ * Run a generated script from a temp `.ps1` file rather than `-Command`.
80
+ *
81
+ * For the two calls that drive a compose window: a script passed on the command
82
+ * line competes with the command-line length limit, and these carry enough
83
+ * inline text to reach it. Failures classify exactly as `runPowerShell`'s do —
84
+ * the file is an implementation detail, not a second error contract.
85
+ */
86
+ async function runPowerShellFile(script, timeout) {
87
+ requireWindows();
88
+ const scriptFile = (0, runtime_1.tempFile)('script', 'ps1');
89
+ fs_1.default.writeFileSync(scriptFile, UTF8_PRELUDE + script, 'utf-8');
90
+ try {
91
+ return await spawnPowerShell(['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', scriptFile], script, timeout);
92
+ }
93
+ finally {
94
+ try {
95
+ fs_1.default.unlinkSync(scriptFile);
96
+ }
97
+ catch { /* the run is done; a leftover scratch file is not worth failing on */
98
+ }
99
+ }
100
+ }
101
+ /** The one place a PowerShell child is spawned, timed, classified and reported. */
102
+ function spawnPowerShell(args, script, timeout) {
103
+ const { timeoutMs, maxBufferBytes, signal } = (0, runtime_1.getConfig)();
104
+ const effectiveTimeout = timeout ?? timeoutMs;
105
+ const startedAt = Date.now();
106
+ return new Promise((resolve, reject) => {
107
+ (0, child_process_1.execFile)('powershell.exe', args, { maxBuffer: maxBufferBytes, timeout: effectiveTimeout, signal }, (error, stdout, stderr) => {
108
+ const durationMs = Date.now() - startedAt;
109
+ if (error) {
110
+ const failure = (0, errors_1.classifyRunFailure)({
111
+ runner: RUNNER,
112
+ script,
113
+ stderr: (stderr || error.message).trim(),
114
+ durationMs,
115
+ nodeError: error,
116
+ timeoutMs: effectiveTimeout,
117
+ signal,
118
+ });
119
+ (0, runtime_1.reportRun)({ runner: RUNNER, script, durationMs, error: failure.message });
120
+ reject(failure);
121
+ }
122
+ else {
123
+ (0, runtime_1.reportRun)({ runner: RUNNER, script, durationMs });
124
+ resolve(stdout.trim());
125
+ }
126
+ });
127
+ });
128
+ }
129
+ /**
130
+ * Write text a script will read back, and hand back a cleanup callback.
131
+ *
132
+ * Large HTML — an email body, a Word-sized template — goes to a file rather than
133
+ * into the script text: inline, it is what pushes a generated script past the
134
+ * command-line length limit.
135
+ */
136
+ function scriptInput(kind, contents) {
137
+ const path = (0, runtime_1.tempFile)(kind, 'html');
138
+ fs_1.default.writeFileSync(path, contents, 'utf-8');
139
+ return {
140
+ path,
141
+ cleanup() {
142
+ try {
143
+ fs_1.default.unlinkSync(path);
144
+ }
145
+ catch { /* ignore */
146
+ }
147
+ },
148
+ };
149
+ }
150
+ /** Read a file written by a script, as UTF-8, or '' when it never appeared. */
151
+ function readScriptOutput(path) {
152
+ try {
153
+ const contents = fs_1.default.readFileSync(path, 'utf-8');
154
+ fs_1.default.unlinkSync(path);
155
+ return contents;
156
+ }
157
+ catch {
158
+ return '';
159
+ }
160
+ }
161
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/windows/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import type { MailFolderRef } from '../types';
2
+ /**
3
+ * Session plus the account whose SMTP address matches; binds `$target` and
4
+ * `$account`.
5
+ *
6
+ * Fails rather than falling back to the default account. A bad address must
7
+ * never silently send from — or read — the wrong mailbox, and the thrown
8
+ * sentence is the one `classifyRunFailure` recognises to raise
9
+ * `AccountNotFoundError`, so the wording is load-bearing.
10
+ */
11
+ export declare function accountScript(emailAccount: string): string;
12
+ /** The account's own delivery store, as `$store`. Requires `accountScript`. */
13
+ export declare const DELIVERY_STORE_PS = "\n$store = $account.DeliveryStore\n$storeId = $store.StoreID\n";
14
+ /**
15
+ * The account's store reached through the namespace's top-level folders, as
16
+ * `$storeFolder` / `$store`.
17
+ *
18
+ * Kept alongside `DELIVERY_STORE_PS` rather than replaced by it: the reading
19
+ * operations resolve the store this way, and on a profile where a mailbox is
20
+ * open under a display name that differs from the delivery store's, the two do
21
+ * not select the same folders. Changing which one a reader uses changes what it
22
+ * returns, so each keeps the route it was verified against.
23
+ */
24
+ export declare function namedStoreScript(emailAccount: string): string;
25
+ /**
26
+ * Resolve one item into `$item`. A StoreID disambiguates across mailboxes, so
27
+ * pass it whenever the listing that produced the id carried one.
28
+ */
29
+ export declare function getItemScript(entryId: string, storeId?: string): string;
30
+ /**
31
+ * The sender's real SMTP address, as `$senderSmtp`. Requires `$item`.
32
+ *
33
+ * An Exchange sender's `SenderEmailAddress` is an X500 DN rather than an
34
+ * address; a caller that means to reply to it, or correlate it with anything,
35
+ * needs the address it resolves to.
36
+ */
37
+ export declare const SENDER_SMTP_PS = "\n$senderSmtp = ''\ntry {\n if ($item.SenderEmailType -eq 'EX') { $senderSmtp = $item.Sender.GetExchangeUser().PrimarySmtpAddress }\n} catch {}\nif (-not $senderSmtp) { $senderSmtp = if ($item.SenderEmailAddress) { $item.SenderEmailAddress } else { '' } }\n";
38
+ /**
39
+ * Recursive folder-by-name search. Case-insensitive, depth-capped so a huge
40
+ * mailbox tree can't hang the scan.
41
+ */
42
+ export declare const FIND_FOLDER_PS = "\nfunction Find-FolderByName($root, $name, $depth) {\n foreach ($f in $root.Folders) {\n if ($f.Name -ieq $name) { return $f }\n }\n if ($depth -le 1) { return $null }\n foreach ($f in $root.Folders) {\n $hit = Find-FolderByName $f $name ($depth - 1)\n if ($hit -ne $null) { return $hit }\n }\n return $null\n}\n";
43
+ /**
44
+ * Emit the PowerShell that resolves `$scope` from `$store`, walking a well-known
45
+ * root down through any further segments. Also binds `$scopeCreated`.
46
+ *
47
+ * Split out so the emitted script can be parse-checked without an Outlook
48
+ * session — the folder walk is the only part that varies per call, and a syntax
49
+ * error in it would only ever surface as a failed live run.
50
+ *
51
+ * `folderLabel` is the caller's original string, used verbatim in the error so
52
+ * the message names what they typed rather than the parsed segments. Set
53
+ * `createMissing` to build absent segments instead of throwing — the whole
54
+ * chain, so a nested destination can be created in one call.
55
+ */
56
+ export declare function mailScopeScript(ref: MailFolderRef, folderLabel?: string, createMissing?: boolean): string;