@dawnlit/outlook-bridge 1.1.1 → 2.1.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 (142) hide show
  1. package/README.md +152 -183
  2. package/dist/backend.d.ts +151 -0
  3. package/dist/backend.js +3 -0
  4. package/dist/backend.js.map +1 -0
  5. package/dist/bridge.d.ts +56 -0
  6. package/dist/bridge.js +507 -0
  7. package/dist/bridge.js.map +1 -0
  8. package/dist/errors.d.ts +79 -43
  9. package/dist/errors.js +93 -52
  10. package/dist/errors.js.map +1 -1
  11. package/dist/index.d.ts +10 -10
  12. package/dist/index.js +25 -52
  13. package/dist/index.js.map +1 -1
  14. package/dist/mac/accounts.d.ts +7 -2
  15. package/dist/mac/accounts.js +23 -6
  16. package/dist/mac/accounts.js.map +1 -1
  17. package/dist/mac/attachments.d.ts +5 -25
  18. package/dist/mac/attachments.js +40 -99
  19. package/dist/mac/attachments.js.map +1 -1
  20. package/dist/mac/bounces.d.ts +12 -27
  21. package/dist/mac/bounces.js +96 -117
  22. package/dist/mac/bounces.js.map +1 -1
  23. package/dist/mac/cleanup.d.ts +11 -25
  24. package/dist/mac/cleanup.js +44 -107
  25. package/dist/mac/cleanup.js.map +1 -1
  26. package/dist/mac/drafts.d.ts +15 -34
  27. package/dist/mac/drafts.js +113 -186
  28. package/dist/mac/drafts.js.map +1 -1
  29. package/dist/mac/folders.d.ts +12 -21
  30. package/dist/mac/folders.js +44 -59
  31. package/dist/mac/folders.js.map +1 -1
  32. package/dist/mac/index.d.ts +2 -22
  33. package/dist/mac/index.js +19 -81
  34. package/dist/mac/index.js.map +1 -1
  35. package/dist/mac/profile.d.ts +13 -0
  36. package/dist/mac/profile.js +149 -0
  37. package/dist/mac/profile.js.map +1 -0
  38. package/dist/mac/read.d.ts +21 -37
  39. package/dist/mac/read.js +198 -216
  40. package/dist/mac/read.js.map +1 -1
  41. package/dist/mac/run.d.ts +51 -38
  42. package/dist/mac/run.js +138 -103
  43. package/dist/mac/run.js.map +1 -1
  44. package/dist/mac/scripts.d.ts +90 -91
  45. package/dist/mac/scripts.js +167 -126
  46. package/dist/mac/scripts.js.map +1 -1
  47. package/dist/mac/send.d.ts +10 -20
  48. package/dist/mac/send.js +99 -89
  49. package/dist/mac/send.js.map +1 -1
  50. package/dist/mac/signatures.d.ts +2 -2
  51. package/dist/mac/signatures.js +9 -11
  52. package/dist/mac/signatures.js.map +1 -1
  53. package/dist/mac/templates.d.ts +14 -26
  54. package/dist/mac/templates.js +79 -120
  55. package/dist/mac/templates.js.map +1 -1
  56. package/dist/mail.d.ts +80 -46
  57. package/dist/mail.js +179 -73
  58. package/dist/mail.js.map +1 -1
  59. package/dist/runtime.d.ts +76 -43
  60. package/dist/runtime.js +103 -48
  61. package/dist/runtime.js.map +1 -1
  62. package/dist/shared/args.d.ts +48 -0
  63. package/dist/shared/args.js +216 -0
  64. package/dist/shared/args.js.map +1 -0
  65. package/dist/shared/attachmentMatch.d.ts +26 -20
  66. package/dist/shared/attachmentMatch.js +60 -23
  67. package/dist/shared/attachmentMatch.js.map +1 -1
  68. package/dist/shared/bounceReport.d.ts +11 -0
  69. package/dist/shared/bounceReport.js +45 -0
  70. package/dist/shared/bounceReport.js.map +1 -0
  71. package/dist/shared/bounceRules.d.ts +38 -10
  72. package/dist/shared/bounceRules.js +50 -32
  73. package/dist/shared/bounceRules.js.map +1 -1
  74. package/dist/shared/exec.d.ts +21 -0
  75. package/dist/shared/exec.js +47 -0
  76. package/dist/shared/exec.js.map +1 -0
  77. package/dist/shared/json.d.ts +17 -6
  78. package/dist/shared/json.js +35 -24
  79. package/dist/shared/json.js.map +1 -1
  80. package/dist/shared/locate.d.ts +31 -0
  81. package/dist/shared/locate.js +128 -0
  82. package/dist/shared/locate.js.map +1 -0
  83. package/dist/shared/replyBody.d.ts +31 -36
  84. package/dist/shared/replyBody.js +83 -72
  85. package/dist/shared/replyBody.js.map +1 -1
  86. package/dist/shared/templateLimits.d.ts +14 -0
  87. package/dist/shared/templateLimits.js +21 -0
  88. package/dist/shared/templateLimits.js.map +1 -0
  89. package/dist/templateBody.d.ts +69 -0
  90. package/dist/{outlookTemplateSections.js → templateBody.js} +80 -73
  91. package/dist/templateBody.js.map +1 -0
  92. package/dist/types.d.ts +381 -168
  93. package/dist/types.js +8 -9
  94. package/dist/types.js.map +1 -1
  95. package/dist/windows/accounts.d.ts +7 -12
  96. package/dist/windows/accounts.js +19 -34
  97. package/dist/windows/accounts.js.map +1 -1
  98. package/dist/windows/attachments.d.ts +9 -24
  99. package/dist/windows/attachments.js +94 -119
  100. package/dist/windows/attachments.js.map +1 -1
  101. package/dist/windows/bounces.d.ts +13 -26
  102. package/dist/windows/bounces.js +168 -200
  103. package/dist/windows/bounces.js.map +1 -1
  104. package/dist/windows/cleanup.d.ts +23 -23
  105. package/dist/windows/cleanup.js +99 -113
  106. package/dist/windows/cleanup.js.map +1 -1
  107. package/dist/windows/drafts.d.ts +16 -35
  108. package/dist/windows/drafts.js +156 -253
  109. package/dist/windows/drafts.js.map +1 -1
  110. package/dist/windows/folders.d.ts +8 -13
  111. package/dist/windows/folders.js +44 -52
  112. package/dist/windows/folders.js.map +1 -1
  113. package/dist/windows/index.d.ts +2 -27
  114. package/dist/windows/index.js +11 -66
  115. package/dist/windows/index.js.map +1 -1
  116. package/dist/windows/read.d.ts +24 -60
  117. package/dist/windows/read.js +265 -306
  118. package/dist/windows/read.js.map +1 -1
  119. package/dist/windows/run.d.ts +38 -40
  120. package/dist/windows/run.js +151 -112
  121. package/dist/windows/run.js.map +1 -1
  122. package/dist/windows/scripts.d.ts +65 -55
  123. package/dist/windows/scripts.js +109 -85
  124. package/dist/windows/scripts.js.map +1 -1
  125. package/dist/windows/send.d.ts +9 -9
  126. package/dist/windows/send.js +76 -88
  127. package/dist/windows/send.js.map +1 -1
  128. package/dist/windows/signatures.d.ts +4 -10
  129. package/dist/windows/signatures.js +30 -37
  130. package/dist/windows/signatures.js.map +1 -1
  131. package/dist/windows/templates.d.ts +12 -18
  132. package/dist/windows/templates.js +117 -150
  133. package/dist/windows/templates.js.map +1 -1
  134. package/package.json +5 -4
  135. package/dist/OutlookService.d.ts +0 -43
  136. package/dist/OutlookService.js +0 -137
  137. package/dist/OutlookService.js.map +0 -1
  138. package/dist/outlookTemplateSections.d.ts +0 -62
  139. package/dist/outlookTemplateSections.js.map +0 -1
  140. package/dist/shared/capabilities.d.ts +0 -3
  141. package/dist/shared/capabilities.js +0 -9
  142. package/dist/shared/capabilities.js.map +0 -1
@@ -4,245 +4,212 @@ exports.readTemplateEmails = readTemplateEmails;
4
4
  exports.saveTemplateEmail = saveTemplateEmail;
5
5
  exports.editEmailTemplate = editEmailTemplate;
6
6
  // Template emails: ordinary mail items kept in a mailbox folder and reused as
7
- // reply bodies, plus the compose-window editor for one.
7
+ // email bodies, plus the compose-window editor for one.
8
8
  const run_1 = require("./run");
9
9
  const scripts_1 = require("./scripts");
10
10
  const json_1 = require("../shared/json");
11
- const outlookTemplateSections_1 = require("../outlookTemplateSections");
11
+ const templateLimits_1 = require("../shared/templateLimits");
12
+ const templateBody_1 = require("../templateBody");
12
13
  const mail_1 = require("../mail");
13
14
  const errors_1 = require("../errors");
14
15
  const runtime_1 = require("../runtime");
15
16
  /**
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.
17
+ * The template emails in a mailbox folder, newest first. A missing folder is
18
+ * reported as folderFound:false with the mailbox's top-level folder names
19
+ * rather than thrown, so a caller can offer to create it.
20
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)}
21
+ async function readTemplateEmails(request) {
22
+ const output = await (0, run_1.runPowerShellJson)(`${(0, scripts_1.accountScript)(request.account)}
26
23
  ${scripts_1.DELIVERY_STORE_PS}
27
24
  $root = $store.GetRootFolder()
28
25
  ${scripts_1.FIND_FOLDER_PS}
29
- $folder = Find-FolderByName $root '${(0, run_1.psEscape)(folderName)}' 3
26
+ $folder = Find-FolderByName $root ${(0, run_1.psString)(request.folder)} ${(0, run_1.psInt)(templateLimits_1.TEMPLATE_SEARCH_DEPTH)}
30
27
  if ($folder -eq $null) {
31
28
  $names = @()
32
- foreach ($f in $root.Folders) { $names += $f.Name }
33
- ConvertTo-Json @{ folderFound = $false; folderPath = ''; templates = @(); availableFolders = $names } -Depth 3
29
+ foreach ($f in $root.Folders) { $names += [string]$f.Name }
30
+ ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ folderFound = $false; folderPath = ''; templates = @(); availableFolders = @($names) })
34
31
  exit 0
35
32
  }
36
- $includeBody = ${(0, run_1.psBool)(includeBody)}
37
- $wanted = '${(0, run_1.psEscape)(wanted)}'
33
+ $includeBody = ${(0, run_1.psBool)(request.includeBody)}
34
+ $wanted = ${(0, run_1.psString)(request.subject ?? '')}
35
+ $limit = ${(0, run_1.psInt)(request.limit)}
36
+ $maxHtml = ${(0, run_1.psInt)(templateLimits_1.MAX_TEMPLATE_HTML)}
37
+ $previewChars = ${(0, run_1.psInt)(templateLimits_1.TEMPLATE_PREVIEW_CHARS)}
38
38
  $items = $folder.Items
39
39
  $items.Sort('[LastModificationTime]', $true)
40
- $results = @()
41
- # Without a subject filter the sort order does the capping (newest first). With one,
42
- # scan the whole folder — the wanted template need not be among the newest — and stop
43
- # as soon as the cap is filled.
40
+ $rows = @()
44
41
  $count = $items.Count
45
- for ($i = 1; $i -le $count; $i++) {
46
- if ($results.Count -ge ${cap}) { break }
42
+ # Without a subject the sort does the capping, newest first. With one, the whole
43
+ # folder is scanned — the template wanted need not be among the newest.
44
+ for ($i = 1; $i -le $count -and $rows.Count -lt $limit; $i++) {
47
45
  $item = $null
48
46
  try { $item = $items.Item($i) } catch { continue }
49
47
  if ($item -eq $null) { continue }
50
- $cls = 0
51
- try { $cls = [int]$item.Class } catch {}
52
- if ($cls -ne 43) { continue } # olMail only
53
- if ($wanted -ne '') {
54
- $subj = ''
55
- try { $subj = [string]$item.Subject } catch {}
56
- if ($subj -eq $null) { $subj = '' }
57
- if ($subj.Trim() -ine $wanted) { continue }
58
- }
48
+ $class = 0
49
+ try { $class = [int]$item.Class } catch {}
50
+ if ($class -ne 43) { continue }
51
+ $subject = ''
52
+ try { $subject = ([string]$item.Subject).Trim() } catch {}
53
+ if ($wanted -and $subject -ine $wanted) { continue }
59
54
  $html = ''
60
55
  $preview = ''
61
56
  $markers = ''
62
- $plain = ''
63
57
  if ($includeBody) {
64
58
  try { $html = [string]$item.HTMLBody } catch {}
65
- # Embedded (cid:) images live as attachments on the TEMPLATE item: reusing this HTML
66
- # on a new email shows a broken "linked image" placeholder, and rewriting to file:
67
- # URIs renders invisibly in modern Outlook. Neither works, so strip the img tags —
68
- # template emails are text/HTML only.
69
- $html = [regex]::Replace($html, '<img[^>]*src="cid:[^"]*"[^>]*>', '')
70
- if ($html.Length -gt 100000) { $html = $html.Substring(0, 100000) }
59
+ # Embedded (cid:) images are attachments of the TEMPLATE item, so they
60
+ # would show as broken placeholders anywhere else.
61
+ $html = [regex]::Replace($html, ${(0, run_1.psString)(templateLimits_1.EMBEDDED_IMAGE_SOURCE)}, '', [Text.RegularExpressions.RegexOptions]::IgnoreCase)
62
+ if ($html.Length -gt $maxHtml) { $html = $html.Substring(0, $maxHtml) }
71
63
  } else {
72
- # Bodies omitted: return a short plain-text preview so the caller can tell templates
73
- # apart by subject without pulling the full (often huge) Word-generated HTML.
74
- try { $plain = [string]$item.Body } catch { $plain = '' }
75
- if ($plain) {
76
- # ...plus the [[SECTION]] / {{PLACEHOLDER}} markers, scanned from the full text:
77
- # that's what lets a caller confirm a sectioned template is intact without
78
- # fetching its body. Word can split a marker across tags in the HTML but not
79
- # in the plain-text body, so a literal scan is right here.
80
- # (Backslashes in the regex are doubled because this whole script is a JS
81
- # template literal — a single one would be eaten before PowerShell saw it.)
82
- $found = @()
83
- foreach ($x in [regex]::Matches($plain, '\\[\\[\\s*/?\\s*[A-Za-z0-9_-]{1,40}\\s*\\]\\]|\\{\\{\\s*[A-Za-z0-9_-]{1,40}\\s*\\}\\}')) {
84
- $t = ($x.Value -replace '\\s', '')
85
- if ($found -notcontains $t) { $found += $t }
86
- }
87
- $markers = ($found -join ' ')
88
- $preview = $plain.Trim()
89
- if ($preview.Length -gt 200) { $preview = $preview.Substring(0, 200) }
64
+ # No body asked for: a short preview, plus the section and placeholder
65
+ # markers scanned here from the full plain text — so a caller can confirm
66
+ # a template is intact without its body crossing stdout. Word can split a
67
+ # marker across tags in the HTML but not in the plain text.
68
+ $plain = ''
69
+ try { $plain = [string]$item.Body } catch {}
70
+ $found = @()
71
+ foreach ($m in [regex]::Matches($plain, ${(0, run_1.psString)(templateBody_1.MARKER_TOKEN_SOURCE)})) {
72
+ $token = $m.Value -replace '\\s', ''
73
+ if ($found -notcontains $token) { $found += $token }
90
74
  }
75
+ $markers = $found -join ' '
76
+ $preview = $plain.Trim()
77
+ if ($preview.Length -gt $previewChars) { $preview = $preview.Substring(0, $previewChars) }
91
78
  }
92
- $results += [PSCustomObject]@{
93
- entryId = $item.EntryID
94
- subject = if ($item.Subject) { $item.Subject.Trim() } else { '' }
79
+ $modified = ''
80
+ try { $modified = $item.LastModificationTime.ToString('yyyy-MM-dd HH:mm') } catch {}
81
+ $rows += [PSCustomObject]@{
82
+ entryId = [string]$item.EntryID
83
+ subject = $subject
95
84
  htmlBody = $html
96
85
  bodyPreview = $preview
97
86
  markers = $markers
98
- lastModified = $item.LastModificationTime.ToString('yyyy-MM-dd HH:mm')
87
+ lastModified = $modified
99
88
  }
100
89
  }
101
- ConvertTo-Json @{ folderFound = $true; folderPath = $folder.FolderPath; templates = $results; availableFolders = @() } -Depth 4
102
- `;
103
- const parsed = (0, json_1.parseObject)(await (0, run_1.runPowerShell)(script, 60000));
90
+ ConvertTo-Json -Depth 4 -InputObject ([PSCustomObject]@{
91
+ folderFound = $true
92
+ folderPath = [string]$folder.FolderPath
93
+ templates = @($rows)
94
+ availableFolders = @()
95
+ })
96
+ `, 'standard');
97
+ const e = (0, json_1.record)(output);
104
98
  return {
105
- folderFound: Boolean(parsed.folderFound),
106
- folderPath: (0, json_1.str)(parsed.folderPath),
107
- templates: (0, json_1.toArray)(parsed.templates).map(item => {
108
- const e = (0, json_1.record)(item);
109
- const html = (0, json_1.str)(e.htmlBody);
110
- // With a body in hand the HTML is authoritative (it survives Word splitting a
111
- // marker across tags); without one, fall back to what the script scanned out
112
- // of the plain-text body.
113
- const markers = (0, outlookTemplateSections_1.findTemplateMarkers)(html || (0, json_1.str)(e.markers));
99
+ folderFound: (0, json_1.bool)(e.folderFound),
100
+ folderPath: (0, json_1.str)(e.folderPath),
101
+ templates: (0, json_1.toArray)(e.templates).map(row => {
102
+ const t = (0, json_1.record)(row);
103
+ const html = (0, json_1.str)(t.htmlBody);
104
+ // With the HTML in hand it is authoritative (markers survive Word
105
+ // splitting them across tags); without it, the markers the script
106
+ // scanned out of the plain text stand in.
107
+ const markers = (0, templateBody_1.findTemplateMarkers)(request.includeBody ? html : (0, json_1.str)(t.markers));
114
108
  return {
115
- entryId: (0, json_1.str)(e.entryId),
116
- subject: (0, json_1.str)(e.subject),
109
+ entryId: (0, json_1.str)(t.entryId),
110
+ subject: (0, json_1.str)(t.subject),
117
111
  htmlBody: html,
118
- bodyPreview: (0, json_1.str)(e.bodyPreview),
112
+ bodyPreview: (0, json_1.str)(t.bodyPreview),
119
113
  sections: markers.sections,
120
114
  placeholders: markers.placeholders,
121
- lastModified: (0, json_1.str)(e.lastModified),
115
+ lastModified: (0, json_1.str)(t.lastModified),
122
116
  };
123
117
  }),
124
- availableFolders: (0, json_1.toArray)(parsed.availableFolders).map(json_1.str),
118
+ availableFolders: (0, json_1.toArray)(e.availableFolders).map(json_1.str),
125
119
  };
126
120
  }
127
121
  /**
128
- * Save a new template email into a mailbox folder (default "Templates"),
129
- * creating the folder at the mailbox root if it doesn't exist. The item is a
130
- * plain unsent mail (subject + HTML body) that can be edited in Outlook.
131
- * Never overwrites an existing item — it always adds a new one, so check with
132
- * readTemplateEmails first and only call after the user has agreed to create it.
122
+ * Save a new template email into a folder, creating the folder at the mailbox
123
+ * root when absent. Always adds a new item — never overwrites one.
133
124
  */
134
- async function saveTemplateEmail(emailAccount, subject, htmlBody, folderName = 'Templates') {
135
- (0, run_1.requireWindows)();
136
- const body = (0, run_1.scriptInput)('template-body', htmlBody);
137
- const script = `${(0, scripts_1.accountScript)(emailAccount)}
125
+ async function saveTemplateEmail(request) {
126
+ const body = (0, run_1.scriptInput)('template-body', request.htmlBody);
127
+ try {
128
+ const output = await (0, run_1.runPowerShellJson)(`${(0, scripts_1.accountScript)(request.account)}
138
129
  ${scripts_1.DELIVERY_STORE_PS}
139
130
  $root = $store.GetRootFolder()
140
131
  ${scripts_1.FIND_FOLDER_PS}
141
- $folder = Find-FolderByName $root '${(0, run_1.psEscape)(folderName)}' 3
132
+ $folderName = ${(0, run_1.psString)(request.folder)}
133
+ $folder = Find-FolderByName $root $folderName ${(0, run_1.psInt)(templateLimits_1.TEMPLATE_SEARCH_DEPTH)}
142
134
  $folderCreated = $false
143
135
  if ($folder -eq $null) {
144
- $folder = $root.Folders.Add('${(0, run_1.psEscape)(folderName)}')
136
+ $folder = $root.Folders.Add($folderName)
145
137
  $folderCreated = $true
146
138
  }
147
139
  $mail = $outlook.CreateItem(0)
148
- $mail.Subject = '${(0, run_1.psEscape)(subject)}'
149
- $mail.HTMLBody = [IO.File]::ReadAllText('${(0, run_1.psEscape)(body.path)}', [Text.Encoding]::UTF8)
140
+ $mail.Subject = ${(0, run_1.psString)(request.subject)}
141
+ $mail.HTMLBody = [IO.File]::ReadAllText(${(0, run_1.psString)(body.path)}, [Text.Encoding]::UTF8)
150
142
  $mail.Save()
151
- $moved = $mail.Move($folder)
152
- ConvertTo-Json @{ folderPath = $folder.FolderPath; folderCreated = $folderCreated }
153
- `;
154
- try {
155
- const parsed = (0, json_1.parseObject)(await (0, run_1.runPowerShell)(script, 60000));
156
- return {
157
- folderPath: (0, json_1.str)(parsed.folderPath),
158
- folderCreated: Boolean(parsed.folderCreated),
159
- };
143
+ [void]$mail.Move($folder)
144
+ ConvertTo-Json -Compress -InputObject ([PSCustomObject]@{ folderPath = [string]$folder.FolderPath; folderCreated = $folderCreated })
145
+ `, 'standard');
146
+ const e = (0, json_1.record)(output);
147
+ return { folderPath: (0, json_1.str)(e.folderPath), folderCreated: (0, json_1.bool)(e.folderCreated) };
160
148
  }
161
149
  finally {
162
150
  body.cleanup();
163
151
  }
164
152
  }
165
153
  /**
166
- * Open an Outlook compose window pre-filled with `currentHtml`, wait for the
167
- * user to close it, and return the HTML they saved.
168
- *
169
- * Deliberately unbounded: the run lasts as long as the person is editing. Give
170
- * it an AbortSignal (`bridge.withOptions({ signal })`) if the caller needs a way
171
- * to give up on them.
154
+ * Open a compose window pre-filled with the HTML, wait for it to close, and
155
+ * return what was saved (Ctrl+S) — or, failing a save, the body as it last
156
+ * stood while the window was open. Leftover editor drafts are removed.
172
157
  */
173
- async function editEmailTemplate(label, currentHtml) {
174
- (0, run_1.requireWindows)();
175
- const input = (0, run_1.scriptInput)('template-input', currentHtml);
158
+ async function editEmailTemplate(request) {
159
+ const input = (0, run_1.scriptInput)('template-input', request.html);
176
160
  const outputFile = (0, runtime_1.tempFile)('template-output', 'html');
177
- const subject = `${label} - Save (Ctrl+S) and close when done`;
178
- const script = `
179
- $ErrorActionPreference = 'Stop'
180
- $inputPath = '${(0, run_1.psEscape)(input.path)}'
181
- $outputPath = '${(0, run_1.psEscape)(outputFile)}'
182
- $subject = '${(0, run_1.psEscape)(subject)}'
183
-
184
- $outlook = New-Object -ComObject Outlook.Application
185
- $ns = $outlook.GetNamespace('mapi')
161
+ try {
162
+ await (0, run_1.runPowerShell)(`${scripts_1.SESSION_PS}
163
+ $subject = ${(0, run_1.psString)(`${request.label} - Save (Ctrl+S) and close when done`)}
164
+ $outputPath = ${(0, run_1.psString)(outputFile)}
186
165
  $mail = $outlook.CreateItem(0)
187
166
  $mail.Subject = $subject
188
- $mail.HTMLBody = [IO.File]::ReadAllText($inputPath, [Text.Encoding]::UTF8)
167
+ $mail.HTMLBody = [IO.File]::ReadAllText(${(0, run_1.psString)(input.path)}, [Text.Encoding]::UTF8)
189
168
  $mail.Display()
190
-
191
- # Give the inspector time to fully initialize
192
169
  Start-Sleep -Seconds 2
193
-
194
- # Poll until the compose window is closed, keeping the last live body as a
195
- # fallback for the case where the user edits but never presses Ctrl+S.
170
+ # Poll until the compose window closes, keeping the last live body as the
171
+ # fallback for an edit that was never saved.
196
172
  $liveBody = ''
197
173
  while ($true) {
198
174
  Start-Sleep -Milliseconds 700
199
175
  $open = $false
200
176
  try {
201
- foreach ($insp in $outlook.Inspectors) {
202
- $ci = $null
203
- try { $ci = $insp.CurrentItem } catch {}
204
- if ($ci -ne $null -and $ci.Subject -eq $subject) {
177
+ foreach ($inspector in $outlook.Inspectors) {
178
+ $current = $null
179
+ try { $current = $inspector.CurrentItem } catch {}
180
+ if ($current -ne $null -and [string]$current.Subject -eq $subject) {
205
181
  $open = $true
206
- try { $liveBody = $ci.HTMLBody } catch {}
182
+ try { $liveBody = [string]$current.HTMLBody } catch {}
207
183
  break
208
184
  }
209
185
  }
210
186
  } catch {}
211
187
  if (-not $open) { break }
212
188
  }
213
-
214
- # Prefer the saved draft — it reflects the user's Ctrl+S and survives the
215
- # inspector closing, unlike the original mail reference. Also removes any
216
- # leftover editor drafts so they don't pile up in the Drafts folder.
189
+ # Prefer the saved draft: it is what the person chose to keep, and it outlives
190
+ # the window. Every editor draft is removed so none pile up in Drafts.
217
191
  $savedBody = ''
218
192
  try {
219
- $drafts = $ns.GetDefaultFolder(16)
220
- $draftItems = $drafts.Items
193
+ $draftItems = $ns.GetDefaultFolder(${(0, run_1.psInt)(mail_1.FolderId.Drafts)}).Items
221
194
  for ($i = $draftItems.Count; $i -ge 1; $i--) {
222
- $it = $draftItems.Item($i)
223
- if ($it.Subject -eq $subject) {
224
- if (-not $savedBody) { try { $savedBody = $it.HTMLBody } catch {} }
225
- try { $it.Delete() } catch {}
195
+ $draft = $draftItems.Item($i)
196
+ if ([string]$draft.Subject -eq $subject) {
197
+ if (-not $savedBody) { try { $savedBody = [string]$draft.HTMLBody } catch {} }
198
+ try { $draft.Delete() } catch {}
226
199
  }
227
200
  }
228
201
  } catch {}
229
-
230
202
  $finalBody = if ($savedBody) { $savedBody } else { $liveBody }
231
- if ($finalBody) {
232
- [IO.File]::WriteAllText($outputPath, $finalBody, [Text.Encoding]::UTF8)
233
- }
234
- `;
235
- try {
236
- // 0 disables the timeout: this run is as long as the edit takes.
237
- await (0, run_1.runPowerShellFile)(script, 0);
203
+ if ($finalBody) { [IO.File]::WriteAllText($outputPath, $finalBody, [Text.Encoding]::UTF8) }
204
+ `, 'interactive');
238
205
  }
239
206
  finally {
240
207
  input.cleanup();
241
208
  }
242
- const result = (0, run_1.readScriptOutput)(outputFile);
243
- if (!result) {
244
- throw new errors_1.NotFoundError('template', 'No template saved. Did you save (Ctrl+S) before closing?');
209
+ const saved = (0, run_1.readScriptOutput)(outputFile);
210
+ if (!saved) {
211
+ throw new errors_1.NotFoundError('template', 'No template was saved. Save (Ctrl+S) before closing the window.');
245
212
  }
246
- return result;
213
+ return saved;
247
214
  }
248
215
  //# sourceMappingURL=templates.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/windows/templates.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/windows/templates.ts"],"names":[],"mappings":"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","sourcesContent":["// Template emails: ordinary mail items kept in a mailbox folder and reused as\n// email bodies, plus the compose-window editor for one.\nimport { psBool, psInt, psString, readScriptOutput, runPowerShell, runPowerShellJson, scriptInput } from './run';\nimport { accountScript, DELIVERY_STORE_PS, FIND_FOLDER_PS, SESSION_PS } from './scripts';\nimport { bool, record, str, toArray } from '../shared/json';\nimport {\n EMBEDDED_IMAGE_SOURCE,\n MAX_TEMPLATE_HTML,\n TEMPLATE_PREVIEW_CHARS,\n TEMPLATE_SEARCH_DEPTH,\n} from '../shared/templateLimits';\nimport { findTemplateMarkers, MARKER_TOKEN_SOURCE } from '../templateBody';\nimport { FolderId } from '../mail';\nimport { NotFoundError } from '../errors';\nimport { tempFile } from '../runtime';\nimport type { EditTemplateRequest, ReadTemplatesRequest, SaveTemplateRequest } from '../backend';\nimport type { SaveTemplateResult, TemplateFolderResult } from '../types';\n\n/**\n * The template emails in a mailbox folder, newest first. A missing folder is\n * reported as folderFound:false with the mailbox's top-level folder names\n * rather than thrown, so a caller can offer to create it.\n */\nexport async function readTemplateEmails(request: ReadTemplatesRequest): Promise<TemplateFolderResult> {\n const output = await runPowerShellJson(`${accountScript(request.account)}\n${DELIVERY_STORE_PS}\n$root = $store.GetRootFolder()\n${FIND_FOLDER_PS}\n$folder = Find-FolderByName $root ${psString(request.folder)} ${psInt(TEMPLATE_SEARCH_DEPTH)}\nif ($folder -eq $null) {\n $names = @()\n foreach ($f in $root.Folders) { $names += [string]$f.Name }\n ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ folderFound = $false; folderPath = ''; templates = @(); availableFolders = @($names) })\n exit 0\n}\n$includeBody = ${psBool(request.includeBody)}\n$wanted = ${psString(request.subject ?? '')}\n$limit = ${psInt(request.limit)}\n$maxHtml = ${psInt(MAX_TEMPLATE_HTML)}\n$previewChars = ${psInt(TEMPLATE_PREVIEW_CHARS)}\n$items = $folder.Items\n$items.Sort('[LastModificationTime]', $true)\n$rows = @()\n$count = $items.Count\n# Without a subject the sort does the capping, newest first. With one, the whole\n# folder is scanned — the template wanted need not be among the newest.\nfor ($i = 1; $i -le $count -and $rows.Count -lt $limit; $i++) {\n $item = $null\n try { $item = $items.Item($i) } catch { continue }\n if ($item -eq $null) { continue }\n $class = 0\n try { $class = [int]$item.Class } catch {}\n if ($class -ne 43) { continue }\n $subject = ''\n try { $subject = ([string]$item.Subject).Trim() } catch {}\n if ($wanted -and $subject -ine $wanted) { continue }\n $html = ''\n $preview = ''\n $markers = ''\n if ($includeBody) {\n try { $html = [string]$item.HTMLBody } catch {}\n # Embedded (cid:) images are attachments of the TEMPLATE item, so they\n # would show as broken placeholders anywhere else.\n $html = [regex]::Replace($html, ${psString(EMBEDDED_IMAGE_SOURCE)}, '', [Text.RegularExpressions.RegexOptions]::IgnoreCase)\n if ($html.Length -gt $maxHtml) { $html = $html.Substring(0, $maxHtml) }\n } else {\n # No body asked for: a short preview, plus the section and placeholder\n # markers scanned here from the full plain text — so a caller can confirm\n # a template is intact without its body crossing stdout. Word can split a\n # marker across tags in the HTML but not in the plain text.\n $plain = ''\n try { $plain = [string]$item.Body } catch {}\n $found = @()\n foreach ($m in [regex]::Matches($plain, ${psString(MARKER_TOKEN_SOURCE)})) {\n $token = $m.Value -replace '\\\\s', ''\n if ($found -notcontains $token) { $found += $token }\n }\n $markers = $found -join ' '\n $preview = $plain.Trim()\n if ($preview.Length -gt $previewChars) { $preview = $preview.Substring(0, $previewChars) }\n }\n $modified = ''\n try { $modified = $item.LastModificationTime.ToString('yyyy-MM-dd HH:mm') } catch {}\n $rows += [PSCustomObject]@{\n entryId = [string]$item.EntryID\n subject = $subject\n htmlBody = $html\n bodyPreview = $preview\n markers = $markers\n lastModified = $modified\n }\n}\nConvertTo-Json -Depth 4 -InputObject ([PSCustomObject]@{\n folderFound = $true\n folderPath = [string]$folder.FolderPath\n templates = @($rows)\n availableFolders = @()\n})\n`, 'standard');\n const e = record(output);\n return {\n folderFound: bool(e.folderFound),\n folderPath: str(e.folderPath),\n templates: toArray(e.templates).map(row => {\n const t = record(row);\n const html = str(t.htmlBody);\n // With the HTML in hand it is authoritative (markers survive Word\n // splitting them across tags); without it, the markers the script\n // scanned out of the plain text stand in.\n const markers = findTemplateMarkers(request.includeBody ? html : str(t.markers));\n return {\n entryId: str(t.entryId),\n subject: str(t.subject),\n htmlBody: html,\n bodyPreview: str(t.bodyPreview),\n sections: markers.sections,\n placeholders: markers.placeholders,\n lastModified: str(t.lastModified),\n };\n }),\n availableFolders: toArray(e.availableFolders).map(str),\n };\n}\n\n/**\n * Save a new template email into a folder, creating the folder at the mailbox\n * root when absent. Always adds a new item — never overwrites one.\n */\nexport async function saveTemplateEmail(request: SaveTemplateRequest): Promise<SaveTemplateResult> {\n const body = scriptInput('template-body', request.htmlBody);\n try {\n const output = await runPowerShellJson(`${accountScript(request.account)}\n${DELIVERY_STORE_PS}\n$root = $store.GetRootFolder()\n${FIND_FOLDER_PS}\n$folderName = ${psString(request.folder)}\n$folder = Find-FolderByName $root $folderName ${psInt(TEMPLATE_SEARCH_DEPTH)}\n$folderCreated = $false\nif ($folder -eq $null) {\n $folder = $root.Folders.Add($folderName)\n $folderCreated = $true\n}\n$mail = $outlook.CreateItem(0)\n$mail.Subject = ${psString(request.subject)}\n$mail.HTMLBody = [IO.File]::ReadAllText(${psString(body.path)}, [Text.Encoding]::UTF8)\n$mail.Save()\n[void]$mail.Move($folder)\nConvertTo-Json -Compress -InputObject ([PSCustomObject]@{ folderPath = [string]$folder.FolderPath; folderCreated = $folderCreated })\n`, 'standard');\n const e = record(output);\n return { folderPath: str(e.folderPath), folderCreated: bool(e.folderCreated) };\n } finally {\n body.cleanup();\n }\n}\n\n/**\n * Open a compose window pre-filled with the HTML, wait for it to close, and\n * return what was saved (Ctrl+S) — or, failing a save, the body as it last\n * stood while the window was open. Leftover editor drafts are removed.\n */\nexport async function editEmailTemplate(request: EditTemplateRequest): Promise<string> {\n const input = scriptInput('template-input', request.html);\n const outputFile = tempFile('template-output', 'html');\n try {\n await runPowerShell(`${SESSION_PS}\n$subject = ${psString(`${request.label} - Save (Ctrl+S) and close when done`)}\n$outputPath = ${psString(outputFile)}\n$mail = $outlook.CreateItem(0)\n$mail.Subject = $subject\n$mail.HTMLBody = [IO.File]::ReadAllText(${psString(input.path)}, [Text.Encoding]::UTF8)\n$mail.Display()\nStart-Sleep -Seconds 2\n# Poll until the compose window closes, keeping the last live body as the\n# fallback for an edit that was never saved.\n$liveBody = ''\nwhile ($true) {\n Start-Sleep -Milliseconds 700\n $open = $false\n try {\n foreach ($inspector in $outlook.Inspectors) {\n $current = $null\n try { $current = $inspector.CurrentItem } catch {}\n if ($current -ne $null -and [string]$current.Subject -eq $subject) {\n $open = $true\n try { $liveBody = [string]$current.HTMLBody } catch {}\n break\n }\n }\n } catch {}\n if (-not $open) { break }\n}\n# Prefer the saved draft: it is what the person chose to keep, and it outlives\n# the window. Every editor draft is removed so none pile up in Drafts.\n$savedBody = ''\ntry {\n $draftItems = $ns.GetDefaultFolder(${psInt(FolderId.Drafts)}).Items\n for ($i = $draftItems.Count; $i -ge 1; $i--) {\n $draft = $draftItems.Item($i)\n if ([string]$draft.Subject -eq $subject) {\n if (-not $savedBody) { try { $savedBody = [string]$draft.HTMLBody } catch {} }\n try { $draft.Delete() } catch {}\n }\n }\n} catch {}\n$finalBody = if ($savedBody) { $savedBody } else { $liveBody }\nif ($finalBody) { [IO.File]::WriteAllText($outputPath, $finalBody, [Text.Encoding]::UTF8) }\n`, 'interactive');\n } finally {\n input.cleanup();\n }\n const saved = readScriptOutput(outputFile);\n if (!saved) {\n throw new NotFoundError('template', 'No template was saved. Save (Ctrl+S) before closing the window.');\n }\n return saved;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dawnlit/outlook-bridge",
3
- "version": "1.1.1",
4
- "description": "Desktop Outlook automation for Node — read, send, reply, file, and template mail by driving the real Outlook client.",
3
+ "version": "2.1.0",
4
+ "description": "Desktop Outlook automation for Node — read, search, send, reply, file, and template mail by driving the real Outlook client on Windows and macOS.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Dawnlit",
7
7
  "homepage": "https://github.com/dawnlit-ai/outlook-bridge#readme",
@@ -39,12 +39,13 @@
39
39
  "access": "public"
40
40
  },
41
41
  "scripts": {
42
- "build": "tsc -p .",
42
+ "clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
43
+ "build": "npm run clean && tsc -p .",
43
44
  "test": "npm run build && node --test \"test/*.test.js\"",
44
45
  "prepare": "npm run build"
45
46
  },
46
47
  "devDependencies": {
47
- "@types/node": "^24.13.3",
48
+ "@types/node": "^24.19.2",
48
49
  "typescript": "~6.0.3"
49
50
  }
50
51
  }
@@ -1,43 +0,0 @@
1
- import type { BridgeOptions, ResolvedConfig } from './runtime';
2
- import type { BridgeCapability, CapabilityMap, OutlookBridge } from './types';
3
- /** Which operations work on this machine. See `CapabilityMap`. */
4
- export declare function capabilities(): CapabilityMap;
5
- /** Whether one operation works here — `capabilities()` for a single name. */
6
- export declare function supports(operation: BridgeCapability): boolean;
7
- /**
8
- * An Outlook bridge carrying its own settings, plus capability discovery.
9
- *
10
- * The free functions exported below are the same automation against the
11
- * process-wide config; this is what you want when the process holds more than
12
- * one caller, or when a call needs its own timeout or AbortSignal.
13
- */
14
- export interface OutlookBridgeInstance extends OutlookBridge {
15
- /** The settings this instance runs with. */
16
- readonly options: Readonly<ResolvedConfig>;
17
- /** Which operations work on this machine. */
18
- capabilities(): CapabilityMap;
19
- /** Whether one operation works here. */
20
- supports(operation: BridgeCapability): boolean;
21
- /**
22
- * A bridge like this one with some settings changed — the per-call escape
23
- * hatch, and how a cancellable call is made:
24
- * `bridge.withOptions({ signal }).searchInboxByFilter(...)`.
25
- */
26
- withOptions(options: BridgeOptions): OutlookBridgeInstance;
27
- }
28
- /**
29
- * A bridge with its own settings, isolated from `configure()` and from every
30
- * other instance.
31
- *
32
- * ```ts
33
- * const bridge = createOutlookBridge({ timeoutMs: 30_000 });
34
- * if (bridge.supports('searchInboxByFilter')) {
35
- * await bridge.withOptions({ signal }).searchInboxByFilter(account, filter);
36
- * }
37
- * ```
38
- *
39
- * Options left out fall back to the process-wide values as they stand when this
40
- * is called.
41
- */
42
- export declare function createOutlookBridge(options?: BridgeOptions): OutlookBridgeInstance;
43
- export declare const getOutlookAccounts: () => Promise<string[]>, sendOutlookEmail: (params: import("./types").SendEmailParams) => Promise<void>, replyOutlookEmail: (params: import("./types").ReplyEmailParams) => Promise<import("./types").ReplyEmailResult>, sendAllDrafts: (emailAccount: string) => Promise<import("./types").SendAllDraftsResult>, readInboxEmails: (emailAccount: string, daysBack?: number, limit?: number, folder?: string) => Promise<import("./types").InboxEmail[]>, searchInboxByFilter: (emailAccount: string, filter?: import("./types").InboxSearchFilter, daysBack?: number) => Promise<import("./types").InboxSearchMatch[]>, readSelectedEmail: () => Promise<import("./types").SelectedEmail>, readEmailBody: (entryId: string, storeId?: string, maxChars?: number, includeQuoted?: boolean) => Promise<import("./types").EmailBodyResult>, openOutlookEmail: (entryId: string, storeId?: string) => Promise<void>, listInboxFolders: (emailAccount: string, maxDepth?: number) => Promise<import("./types").InboxFolderInfo[]>, moveOutlookEmails: (emailAccount: string, entryIds: string[], folderName: string, createIfMissing?: boolean) => Promise<import("./types").MoveEmailsResult>, listOutlookDrafts: (emailAccount: string, limit?: number, previewChars?: number) => Promise<import("./types").ListDraftsResult>, deleteOutlookDrafts: (emailAccount: string, entryIds: string[]) => Promise<import("./types").DeleteDraftsResult>, sendDrafts: (emailAccount: string, entryIds: string[]) => Promise<import("./types").SendDraftsResult>, deleteOutlookEmails: (emailAccount: string, entryIds: string[], options?: import("./types").DeleteMailOptions) => Promise<import("./types").DeleteMailResult>, purgeDeletedItems: (emailAccount: string, olderThanDays?: number, dryRun?: boolean) => Promise<import("./types").PurgeDeletedItemsResult>, saveEmailAttachment: (entryId: string, fileName: string, storeId?: string, destDir?: string) => Promise<string>, saveEmailAttachmentDetailed: (entryId: string, fileName: string, storeId?: string, destDir?: string) => Promise<import("./types").SavedAttachment>, saveEmailAttachments: (entryId: string, fileNames: string[], storeId?: string, destDir?: string) => Promise<import("./types").SavedAttachment[]>, cleanUndeliverableEmails: (emailAccount: string, daysBack?: number, dryRun?: boolean) => Promise<import("./types").CleanUndeliverableResult>, collectBouncedRecipients: (emailAccount: string, daysBack?: number, scanDeleted?: boolean) => Promise<string[]>, readSentRecipientGroups: (emailAccount: string, daysBack?: number, limit?: number) => Promise<import("./types").SentRecipientGroup[]>, listOutlookSignatures: () => Promise<string[]>, readOutlookSignatureHtml: (name: string) => Promise<string>, readTemplateEmails: (emailAccount: string, folderName?: string, limit?: number, includeBody?: boolean, subject?: string) => Promise<import("./types").TemplateFolderResult>, saveTemplateEmail: (emailAccount: string, subject: string, htmlBody: string, folderName?: string) => Promise<import("./types").SaveTemplateResult>, editEmailTemplate: (label: string, currentHtml: string) => Promise<string>;
@@ -1,137 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.editEmailTemplate = exports.saveTemplateEmail = exports.readTemplateEmails = exports.readOutlookSignatureHtml = exports.listOutlookSignatures = exports.readSentRecipientGroups = exports.collectBouncedRecipients = exports.cleanUndeliverableEmails = exports.saveEmailAttachments = exports.saveEmailAttachmentDetailed = exports.saveEmailAttachment = exports.purgeDeletedItems = exports.deleteOutlookEmails = exports.sendDrafts = exports.deleteOutlookDrafts = exports.listOutlookDrafts = exports.moveOutlookEmails = exports.listInboxFolders = exports.openOutlookEmail = exports.readEmailBody = exports.readSelectedEmail = exports.searchInboxByFilter = exports.readInboxEmails = exports.sendAllDrafts = exports.replyOutlookEmail = exports.sendOutlookEmail = exports.getOutlookAccounts = void 0;
37
- exports.capabilities = capabilities;
38
- exports.supports = supports;
39
- exports.createOutlookBridge = createOutlookBridge;
40
- // Platform dispatcher for Outlook automation: Windows drives Outlook via
41
- // PowerShell + COM, macOS via AppleScript (see each implementation's index for
42
- // its platform notes — New Outlook for Mac supports composing but not mailbox
43
- // reading). Other platforms report no capabilities and every call rejects.
44
- //
45
- // Both implementations are pinned to `OutlookBridge` here rather than being
46
- // destructured straight off the module namespaces. That is the difference
47
- // between a consumer seeing `Promise<CleanUndeliverableResult>` and seeing
48
- // `Promise<CleanUndeliverableResult | { matched: unknown[] }>` — a union of
49
- // whatever the two platforms happened to declare, which is what this used to
50
- // publish.
51
- const windowsBridge = __importStar(require("./windows"));
52
- const macBridge = __importStar(require("./mac"));
53
- const runtime_1 = require("./runtime");
54
- const errors_1 = require("./errors");
55
- /** Every operation name, taken from the Windows bridge — the complete one. */
56
- const ALL_CAPABILITIES = Object.keys(windowsBridge.bridge);
57
- /**
58
- * The backend for an OS with no Outlook automation at all.
59
- *
60
- * A third implementation rather than a fallback to the PowerShell one: making
61
- * Windows the default for Linux and everything else meant the dispatcher held a
62
- * bridge whose capability map had to be second-guessed afterwards, and whose
63
- * functions each had to remember to refuse. Answering "not here" is itself an
64
- * implementation of the contract, so it is written as one, once.
65
- */
66
- const unsupportedBridge = {
67
- bridge: Object.fromEntries(ALL_CAPABILITIES.map(name => [
68
- name,
69
- () => Promise.reject(new errors_1.UnsupportedPlatformError(process.platform)),
70
- ])),
71
- capabilities: Object.freeze(Object.fromEntries(ALL_CAPABILITIES.map(name => [name, false]))),
72
- };
73
- // The platform decision, made once. Everything below reads `impl` and
74
- // `implCapabilities` without asking which OS this is.
75
- const { bridge: impl, capabilities: implCapabilities } = process.platform === 'win32' ? windowsBridge
76
- : process.platform === 'darwin' ? macBridge
77
- : unsupportedBridge;
78
- /** Which operations work on this machine. See `CapabilityMap`. */
79
- function capabilities() {
80
- return implCapabilities;
81
- }
82
- /** Whether one operation works here — `capabilities()` for a single name. */
83
- function supports(operation) {
84
- return implCapabilities[operation] === true;
85
- }
86
- /**
87
- * Build a bridge whose every call runs with `config` in force.
88
- *
89
- * The wrapper is what makes per-instance settings work at all: the platform
90
- * implementations read their config from a module-level accessor, so the config
91
- * has to be established around the call rather than passed into it. `withConfig`
92
- * puts it in an AsyncLocalStorage that the whole async subtree inherits, which is
93
- * why two bridges with different timeouts can run concurrently without seeing
94
- * each other's.
95
- */
96
- function build(config) {
97
- const instance = {
98
- capabilities,
99
- supports,
100
- withOptions: (options) => build((0, runtime_1.mergeOptions)(config, options)),
101
- options: Object.freeze({ ...config }),
102
- };
103
- const source = impl;
104
- for (const name of ALL_CAPABILITIES) {
105
- const fn = source[name];
106
- instance[name] = (...args) => {
107
- // Errors thrown synchronously (an argument check ahead of the first
108
- // await) become rejections here, so a caller has one failure channel.
109
- try {
110
- return (0, runtime_1.withConfig)(config, () => fn(...args));
111
- }
112
- catch (error) {
113
- return Promise.reject(error);
114
- }
115
- };
116
- }
117
- return instance;
118
- }
119
- /**
120
- * A bridge with its own settings, isolated from `configure()` and from every
121
- * other instance.
122
- *
123
- * ```ts
124
- * const bridge = createOutlookBridge({ timeoutMs: 30_000 });
125
- * if (bridge.supports('searchInboxByFilter')) {
126
- * await bridge.withOptions({ signal }).searchInboxByFilter(account, filter);
127
- * }
128
- * ```
129
- *
130
- * Options left out fall back to the process-wide values as they stand when this
131
- * is called.
132
- */
133
- function createOutlookBridge(options = {}) {
134
- return build((0, runtime_1.mergeOptions)((0, runtime_1.getGlobalConfig)(), options));
135
- }
136
- exports.getOutlookAccounts = impl.getOutlookAccounts, exports.sendOutlookEmail = impl.sendOutlookEmail, exports.replyOutlookEmail = impl.replyOutlookEmail, exports.sendAllDrafts = impl.sendAllDrafts, exports.readInboxEmails = impl.readInboxEmails, exports.searchInboxByFilter = impl.searchInboxByFilter, exports.readSelectedEmail = impl.readSelectedEmail, exports.readEmailBody = impl.readEmailBody, exports.openOutlookEmail = impl.openOutlookEmail, exports.listInboxFolders = impl.listInboxFolders, exports.moveOutlookEmails = impl.moveOutlookEmails, exports.listOutlookDrafts = impl.listOutlookDrafts, exports.deleteOutlookDrafts = impl.deleteOutlookDrafts, exports.sendDrafts = impl.sendDrafts, exports.deleteOutlookEmails = impl.deleteOutlookEmails, exports.purgeDeletedItems = impl.purgeDeletedItems, exports.saveEmailAttachment = impl.saveEmailAttachment, exports.saveEmailAttachmentDetailed = impl.saveEmailAttachmentDetailed, exports.saveEmailAttachments = impl.saveEmailAttachments, exports.cleanUndeliverableEmails = impl.cleanUndeliverableEmails, exports.collectBouncedRecipients = impl.collectBouncedRecipients, exports.readSentRecipientGroups = impl.readSentRecipientGroups, exports.listOutlookSignatures = impl.listOutlookSignatures, exports.readOutlookSignatureHtml = impl.readOutlookSignatureHtml, exports.readTemplateEmails = impl.readTemplateEmails, exports.saveTemplateEmail = impl.saveTemplateEmail, exports.editEmailTemplate = impl.editEmailTemplate;
137
- //# sourceMappingURL=OutlookService.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"OutlookService.js","sourceRoot":"","sources":["../src/OutlookService.ts"],"names":[],"mappings":"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"}