@dawnlit/outlook-bridge 1.1.0 → 2.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 (139) hide show
  1. package/README.md +150 -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 +81 -43
  9. package/dist/errors.js +98 -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 +95 -112
  22. package/dist/mac/bounces.js.map +1 -1
  23. package/dist/mac/cleanup.d.ts +11 -25
  24. package/dist/mac/cleanup.js +50 -76
  25. package/dist/mac/cleanup.js.map +1 -1
  26. package/dist/mac/drafts.d.ts +13 -34
  27. package/dist/mac/drafts.js +95 -169
  28. package/dist/mac/drafts.js.map +1 -1
  29. package/dist/mac/folders.d.ts +10 -21
  30. package/dist/mac/folders.js +33 -47
  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 +18 -36
  39. package/dist/mac/read.js +154 -208
  40. package/dist/mac/read.js.map +1 -1
  41. package/dist/mac/run.d.ts +35 -39
  42. package/dist/mac/run.js +97 -102
  43. package/dist/mac/run.js.map +1 -1
  44. package/dist/mac/scripts.d.ts +78 -86
  45. package/dist/mac/scripts.js +125 -115
  46. package/dist/mac/scripts.js.map +1 -1
  47. package/dist/mac/send.d.ts +10 -20
  48. package/dist/mac/send.js +64 -86
  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 +75 -113
  55. package/dist/mac/templates.js.map +1 -1
  56. package/dist/mail.d.ts +77 -45
  57. package/dist/mail.js +166 -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 +28 -16
  66. package/dist/shared/attachmentMatch.js +60 -22
  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 +12 -6
  78. package/dist/shared/json.js +29 -23
  79. package/dist/shared/json.js.map +1 -1
  80. package/dist/shared/locate.d.ts +39 -0
  81. package/dist/shared/locate.js +129 -0
  82. package/dist/shared/locate.js.map +1 -0
  83. package/dist/shared/replyBody.d.ts +42 -32
  84. package/dist/shared/replyBody.js +84 -71
  85. package/dist/shared/replyBody.js.map +1 -1
  86. package/dist/templateBody.d.ts +63 -0
  87. package/dist/{outlookTemplateSections.js → templateBody.js} +72 -72
  88. package/dist/templateBody.js.map +1 -0
  89. package/dist/types.d.ts +360 -165
  90. package/dist/types.js +8 -9
  91. package/dist/types.js.map +1 -1
  92. package/dist/windows/accounts.d.ts +10 -1
  93. package/dist/windows/accounts.js +25 -13
  94. package/dist/windows/accounts.js.map +1 -1
  95. package/dist/windows/attachments.d.ts +9 -24
  96. package/dist/windows/attachments.js +94 -119
  97. package/dist/windows/attachments.js.map +1 -1
  98. package/dist/windows/bounces.d.ts +13 -26
  99. package/dist/windows/bounces.js +169 -200
  100. package/dist/windows/bounces.js.map +1 -1
  101. package/dist/windows/cleanup.d.ts +19 -23
  102. package/dist/windows/cleanup.js +93 -111
  103. package/dist/windows/cleanup.js.map +1 -1
  104. package/dist/windows/drafts.d.ts +16 -35
  105. package/dist/windows/drafts.js +167 -253
  106. package/dist/windows/drafts.js.map +1 -1
  107. package/dist/windows/folders.d.ts +8 -13
  108. package/dist/windows/folders.js +45 -52
  109. package/dist/windows/folders.js.map +1 -1
  110. package/dist/windows/index.d.ts +2 -27
  111. package/dist/windows/index.js +11 -65
  112. package/dist/windows/index.js.map +1 -1
  113. package/dist/windows/read.d.ts +24 -45
  114. package/dist/windows/read.js +266 -279
  115. package/dist/windows/read.js.map +1 -1
  116. package/dist/windows/run.d.ts +39 -39
  117. package/dist/windows/run.js +151 -111
  118. package/dist/windows/run.js.map +1 -1
  119. package/dist/windows/scripts.d.ts +71 -38
  120. package/dist/windows/scripts.js +123 -71
  121. package/dist/windows/scripts.js.map +1 -1
  122. package/dist/windows/send.d.ts +6 -11
  123. package/dist/windows/send.js +70 -79
  124. package/dist/windows/send.js.map +1 -1
  125. package/dist/windows/signatures.d.ts +4 -10
  126. package/dist/windows/signatures.js +30 -37
  127. package/dist/windows/signatures.js.map +1 -1
  128. package/dist/windows/templates.d.ts +12 -18
  129. package/dist/windows/templates.js +126 -151
  130. package/dist/windows/templates.js.map +1 -1
  131. package/package.json +5 -4
  132. package/dist/OutlookService.d.ts +0 -43
  133. package/dist/OutlookService.js +0 -137
  134. package/dist/OutlookService.js.map +0 -1
  135. package/dist/outlookTemplateSections.d.ts +0 -62
  136. package/dist/outlookTemplateSections.js.map +0 -1
  137. package/dist/shared/capabilities.d.ts +0 -3
  138. package/dist/shared/capabilities.js +0 -9
  139. package/dist/shared/capabilities.js.map +0 -1
@@ -1,313 +1,227 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sendAllDrafts = sendAllDrafts;
4
3
  exports.listOutlookDrafts = listOutlookDrafts;
5
- exports.deleteOutlookDrafts = deleteOutlookDrafts;
6
4
  exports.sendDrafts = sendDrafts;
7
- // Drafts: which ones belong to an account, and sending, listing or deleting them.
5
+ exports.sendAllDrafts = sendAllDrafts;
6
+ exports.deleteOutlookDrafts = deleteOutlookDrafts;
7
+ // Drafts: which ones belong to an account, and listing, sending or deleting them.
8
8
  const run_1 = require("./run");
9
9
  const scripts_1 = require("./scripts");
10
10
  const json_1 = require("../shared/json");
11
+ const mail_1 = require("../mail");
11
12
  /**
12
13
  * Resolve the Drafts folders belonging to one account into `$scan`, and define
13
- * `Test-DraftMatches` over them. Emitted into every drafts script so listing,
14
- * deleting and sending can never disagree about which drafts are "this account's".
15
- * Expects `$target`, `$outlook`, `$ns` and `$account` to already be set.
14
+ * `Test-DraftMatches` over them. Emitted into every drafts script, so listing,
15
+ * sending and deleting can never disagree about which drafts are this account's.
16
16
  *
17
- * Two Drafts folders can hold drafts for one account, so both are scanned:
18
- * - The account's OWN mailbox store Drafts folder (account.DeliveryStore) — where
19
- * a draft composed while that mailbox is selected lands. A draft there with no
20
- * explicit SendUsingAccount still belongs to this account, so null counts.
21
- * - The DEFAULT store's Drafts folder — where the in-app "Create Drafts" flow
22
- * files drafts via $mail.Save() regardless of send-account. Only drafts stamped
23
- * with this account match here; a null SendUsingAccount means the default
24
- * account, not necessarily this one, so it's left alone.
25
- * (When those two resolve to the same folder — the account IS the default — it is
26
- * scanned once, under the more permissive "own store" rule.)
17
+ * Two Drafts folders can hold one account's drafts, so both are scanned:
18
+ * - the account's OWN store's Drafts, where a draft composed in that mailbox
19
+ * lands. A draft there with no explicit sending account still belongs to it.
20
+ * - the DEFAULT store's Drafts, where a program's `Save()` files a draft
21
+ * whatever account it sends from. Only drafts stamped with this account
22
+ * match there; an unstamped one belongs to the default account.
23
+ * When the two are the same folder it is scanned once, under the first rule.
27
24
  *
28
- * Drafts bound to another account never match, so nothing here can reach unrelated
29
- * mail. Non-mail items (meeting requests, reports) are ignored.
25
+ * Non-mail items (meeting requests, reports) never match.
30
26
  */
31
27
  const DRAFTS_SCAN_PS = `
32
- # Collect the Drafts folders to scan, deduped by id. includeNull marks the account's
33
- # own store, where a draft with no explicit send-account still belongs to it.
34
28
  $scan = @()
35
- $seen = @{}
36
-
29
+ $seenFolders = @{}
37
30
  $homeDrafts = $null
38
- try { $homeDrafts = $account.DeliveryStore.GetDefaultFolder(16) } catch {} # olFolderDrafts
31
+ try { $homeDrafts = $store.GetDefaultFolder(${(0, run_1.psInt)(mail_1.FolderId.Drafts)}) } catch {}
39
32
  if ($homeDrafts -ne $null) {
40
- $seen["$($homeDrafts.StoreID)|$($homeDrafts.EntryID)"] = $true
41
- $scan += [pscustomobject]@{ folder = $homeDrafts; includeNull = $true }
33
+ $seenFolders["$($homeDrafts.StoreID)|$($homeDrafts.EntryID)"] = $true
34
+ $scan += [PSCustomObject]@{ folder = $homeDrafts; includeUnstamped = $true }
42
35
  }
43
-
44
- $defDrafts = $null
45
- try { $defDrafts = $ns.GetDefaultFolder(16) } catch {}
46
- if ($defDrafts -ne $null) {
47
- $k = "$($defDrafts.StoreID)|$($defDrafts.EntryID)"
48
- if (-not $seen.ContainsKey($k)) {
49
- $seen[$k] = $true
50
- $scan += [pscustomobject]@{ folder = $defDrafts; includeNull = $false }
51
- }
36
+ $defaultDrafts = $null
37
+ try { $defaultDrafts = $ns.GetDefaultFolder(${(0, run_1.psInt)(mail_1.FolderId.Drafts)}) } catch {}
38
+ if ($defaultDrafts -ne $null -and -not $seenFolders.ContainsKey("$($defaultDrafts.StoreID)|$($defaultDrafts.EntryID)")) {
39
+ $scan += [PSCustomObject]@{ folder = $defaultDrafts; includeUnstamped = $false }
52
40
  }
53
-
54
- # SendUsingAccount is null for a draft that never set one, so guard the access.
55
- function Test-DraftMatches($item, $includeNull) {
41
+ function Test-DraftMatches($item, [bool]$includeUnstamped) {
56
42
  if ($item.Class -ne 43) { return $false } # olMail only
57
- $acct = $null
58
- try { $acct = $item.SendUsingAccount } catch {}
59
- if ($acct -eq $null) { return $includeNull }
60
- return ($acct.SmtpAddress -ieq $target)
61
- }
62
- `;
63
- /** Session, account and the drafts scan rule — the prelude all three share. */
64
- function draftsScript(emailAccount) {
65
- return (0, scripts_1.accountScript)(emailAccount) + DRAFTS_SCAN_PS;
43
+ $sender = $null
44
+ try { $sender = $item.SendUsingAccount } catch {}
45
+ if ($sender -eq $null) { return $includeUnstamped }
46
+ return ([string]$sender.SmtpAddress -ieq $target)
66
47
  }
67
- /**
68
- * Send the mail drafts that belong to `emailAccount`, wherever Outlook filed them.
69
- * Which drafts those are is DRAFTS_SCAN_PS's rule, not this function's.
70
- *
71
- * Item references are snapshotted before any Send() call: sending moves an item out
72
- * of Drafts, and mutating the collection mid-enumeration would skip every other one.
73
- */
74
- async function sendAllDrafts(emailAccount) {
75
- (0, run_1.requireWindows)();
76
- const script = `${draftsScript(emailAccount)}
77
- # Snapshot the matching mail items first — Send() removes each from Drafts, so
78
- # sending inside a live enumeration would skip every other item.
79
- $items = @()
80
- foreach ($entry in $scan) {
81
- foreach ($it in $entry.folder.Items) {
82
- if (Test-DraftMatches $it $entry.includeNull) { $items += $it }
48
+ # An id is acted on only once it is PROVED to be one of this account's drafts:
49
+ # resolved in one of the scanned folders' stores, sitting in that folder, and
50
+ # bound to this account (or unbound, in its own store).
51
+ $draftFolderRules = @{}
52
+ foreach ($entry in $scan) { $draftFolderRules[[string]$entry.folder.EntryID] = $entry.includeUnstamped }
53
+ function Resolve-OwnDraft([string]$id, [string]$verb) {
54
+ $draft = $null
55
+ foreach ($entry in $scan) {
56
+ try { $draft = $ns.GetItemFromID($id, $entry.folder.StoreID) } catch { $draft = $null }
57
+ if ($draft -ne $null) { break }
83
58
  }
59
+ if ($draft -eq $null) { throw "no such item in this account's Drafts folders" }
60
+ $parentId = ''
61
+ try { $parentId = [string]$draft.Parent.EntryID } catch {}
62
+ if (-not $draftFolderRules.ContainsKey($parentId)) { throw "item is not in this account's Drafts folder - refusing to $verb" }
63
+ if (-not (Test-DraftMatches $draft $draftFolderRules[$parentId])) { throw "draft is not bound to $target - refusing to $verb" }
64
+ return $draft
84
65
  }
85
-
86
- $sent = 0
87
- $failed = @()
88
- foreach ($m in $items) {
89
- try {
90
- $m.Send()
91
- $sent++
92
- } catch {
93
- $subj = ''
94
- try { $subj = $m.Subject } catch {}
95
- $failed += [pscustomobject]@{ subject = $subj; error = $_.Exception.Message }
96
- }
97
- }
98
-
99
- [pscustomobject]@{ sent = $sent; failed = @($failed) } | ConvertTo-Json -Compress -Depth 4
100
66
  `;
101
- try {
102
- const parsed = (0, json_1.parseObject)(await (0, run_1.runPowerShell)(script, 300000));
103
- return {
104
- sent: (0, json_1.num)(parsed.sent),
105
- failed: (0, json_1.toArray)(parsed.failed).map(f => {
106
- const e = (0, json_1.record)(f);
107
- return { subject: (0, json_1.str)(e.subject), error: (0, json_1.str)(e.error) };
108
- }),
109
- };
110
- }
111
- catch (error) {
112
- // A malformed report is not worth failing a send that already happened;
113
- // a run that never got that far has already rejected above.
114
- if (error instanceof SyntaxError)
115
- return { sent: 0, failed: [] };
116
- throw error;
117
- }
67
+ function draftsScript(account) {
68
+ return (0, scripts_1.accountScript)(account) + scripts_1.DELIVERY_STORE_PS + DRAFTS_SCAN_PS;
118
69
  }
119
- /**
120
- * List the mail drafts belonging to `emailAccount`, newest first. Which drafts
121
- * those are is DRAFTS_SCAN_PS's rule.
122
- *
123
- * Deliberately returns no StoreID: deleteOutlookDrafts re-resolves each EntryID
124
- * against the same folders, so repeating a ~700-char store id on every row would be
125
- * pure payload. Bodies are previewed, never returned whole — a templated reply body
126
- * runs to tens of thousands of characters and a folder's worth would blow the cap.
127
- */
128
- async function listOutlookDrafts(emailAccount, limit = 100, previewChars = 300) {
129
- (0, run_1.requireWindows)();
130
- const script = `${draftsScript(emailAccount)}
131
- $previewChars = ${previewChars}
70
+ /** The account's drafts, newest first. Bodies are previewed, never returned whole. */
71
+ async function listOutlookDrafts(request) {
72
+ const output = await (0, run_1.runPowerShellJson)(`${draftsScript(request.account)}
73
+ $previewChars = ${(0, run_1.psInt)(request.previewChars)}
74
+ $limit = ${(0, run_1.psInt)(request.limit)}
132
75
  $rows = @()
133
- $folders = @()
76
+ $folderPaths = @()
134
77
  foreach ($entry in $scan) {
135
- $fp = ''
136
- try { $fp = $entry.folder.FolderPath } catch {}
137
- $folders += $fp
78
+ $folderPath = ''
79
+ try { $folderPath = [string]$entry.folder.FolderPath } catch {}
80
+ $folderPaths += $folderPath
138
81
  foreach ($it in $entry.folder.Items) {
139
- if (-not (Test-DraftMatches $it $entry.includeNull)) { continue }
140
- $subj = ''; try { $subj = $it.Subject } catch {}
141
- $to = ''; try { $to = $it.To } catch {}
142
- $addrs = @()
82
+ if (-not (Test-DraftMatches $it $entry.includeUnstamped)) { continue }
83
+ $addresses = @()
143
84
  try {
144
85
  foreach ($r in $it.Recipients) {
145
- $a = ''
146
- try { $a = $r.Address } catch {}
147
- if ($a -and $a.StartsWith('/')) { try { $a = $r.Name } catch {} }
148
- if ($a) { $addrs += $a }
86
+ $address = ''
87
+ try { $address = [string]$r.Address } catch {}
88
+ # An unresolved Exchange entry has a DN for an address; its name reads better.
89
+ if ($address.StartsWith('/')) { try { $address = [string]$r.Name } catch {} }
90
+ if ($address) { $addresses += $address }
149
91
  }
150
92
  } catch {}
151
- $body = ''
152
- try { $body = $it.Body } catch {}
153
- if ($body -eq $null) { $body = '' }
154
- $body = ($body -replace '\\s+', ' ').Trim()
155
- if ($body.Length -gt $previewChars) { $body = $body.Substring(0, $previewChars) }
156
- $mod = ''
157
- try { $mod = $it.LastModificationTime.ToString('yyyy-MM-dd HH:mm') } catch {}
158
- $att = $false
159
- try { $att = ($it.Attachments.Count -gt 0) } catch {}
160
- $rows += [pscustomobject]@{
161
- entryId = $it.EntryID
162
- subject = $subj
163
- to = $to
164
- toEmails = @($addrs)
165
- bodyPreview = $body
166
- hasAttachments = $att
167
- lastModified = $mod
168
- folderPath = $fp
93
+ $preview = ''
94
+ if ($previewChars -gt 0) {
95
+ try { $preview = ([string]$it.Body -replace '\\s+', ' ').Trim() } catch {}
96
+ if ($preview.Length -gt $previewChars) { $preview = $preview.Substring(0, $previewChars) }
97
+ }
98
+ $modified = ''
99
+ try { $modified = $it.LastModificationTime.ToString('yyyy-MM-dd HH:mm') } catch {}
100
+ $hasAttachments = $false
101
+ try { $hasAttachments = ($it.Attachments.Count -gt 0) } catch {}
102
+ $rows += [PSCustomObject]@{
103
+ entryId = [string]$it.EntryID
104
+ subject = [string]$it.Subject
105
+ to = [string]$it.To
106
+ toEmails = @($addresses)
107
+ bodyPreview = $preview
108
+ hasAttachments = $hasAttachments
109
+ lastModified = $modified
110
+ folderPath = $folderPath
169
111
  }
170
112
  }
171
113
  }
172
114
  $sorted = @($rows | Sort-Object -Property lastModified -Descending)
173
115
  $total = $sorted.Count
174
- if ($total -gt ${limit}) { $sorted = @($sorted[0..(${limit} - 1)]) }
175
- ConvertTo-Json @{ account = $target; foldersScanned = @($folders); count = $total; truncated = ($total -gt ${limit}); drafts = @($sorted) } -Depth 4
176
- `;
177
- const parsed = (0, json_1.parseObject)(await (0, run_1.runPowerShell)(script, 300000));
116
+ if ($total -gt $limit) { $sorted = @($sorted[0..($limit - 1)]) }
117
+ ConvertTo-Json -Depth 4 -InputObject ([PSCustomObject]@{
118
+ account = $target
119
+ foldersScanned = @($folderPaths)
120
+ count = $total
121
+ truncated = ($total -gt $limit)
122
+ drafts = @($sorted)
123
+ })
124
+ `, 'scan');
125
+ const e = (0, json_1.record)(output);
178
126
  return {
179
- account: (0, json_1.str)(parsed.account) || emailAccount,
180
- foldersScanned: (0, json_1.toArray)(parsed.foldersScanned).map(json_1.str),
181
- count: (0, json_1.num)(parsed.count),
182
- truncated: Boolean(parsed.truncated),
183
- drafts: (0, json_1.toArray)(parsed.drafts).map(d => {
184
- const e = (0, json_1.record)(d);
127
+ account: (0, json_1.str)(e.account) || request.account,
128
+ foldersScanned: (0, json_1.toArray)(e.foldersScanned).map(json_1.str),
129
+ count: (0, json_1.num)(e.count),
130
+ truncated: (0, json_1.bool)(e.truncated),
131
+ drafts: (0, json_1.toArray)(e.drafts).map(row => {
132
+ const d = (0, json_1.record)(row);
185
133
  return {
186
- entryId: (0, json_1.str)(e.entryId),
187
- subject: (0, json_1.str)(e.subject),
188
- to: (0, json_1.str)(e.to),
189
- toEmails: (0, json_1.strList)(e.toEmails),
190
- bodyPreview: (0, json_1.str)(e.bodyPreview),
191
- hasAttachments: Boolean(e.hasAttachments),
192
- lastModified: (0, json_1.str)(e.lastModified),
193
- folderPath: (0, json_1.str)(e.folderPath),
134
+ entryId: (0, json_1.str)(d.entryId),
135
+ subject: (0, json_1.str)(d.subject),
136
+ to: (0, json_1.str)(d.to),
137
+ toEmails: (0, json_1.strList)(d.toEmails),
138
+ bodyPreview: (0, json_1.str)(d.bodyPreview),
139
+ hasAttachments: (0, json_1.bool)(d.hasAttachments),
140
+ lastModified: (0, json_1.str)(d.lastModified),
141
+ folderPath: (0, json_1.str)(d.folderPath),
194
142
  };
195
143
  }),
196
144
  };
197
145
  }
198
- /**
199
- * Delete mail drafts by EntryID. Outlook's Delete() moves the item to Deleted Items
200
- * rather than destroying it, so a mistaken call stays recoverable from there.
201
- *
202
- * Every id must resolve to a mail item sitting in one of THIS account's Drafts
203
- * folders before anything is deleted; an id pointing at ordinary mail, or at another
204
- * account's draft, is refused and reported. Without that gate this would be a
205
- * general-purpose "delete any email by id" tool, which is not what it is for.
206
- */
207
- async function deleteOutlookDrafts(emailAccount, entryIds) {
208
- (0, run_1.requireWindows)();
209
- if (entryIds.length === 0)
210
- return { deleted: 0, failed: [] };
211
- const script = `${draftsScript(emailAccount)}
212
- # Index this account's Drafts folders by EntryID, so a resolved item can be PROVED
213
- # to live in one of them before it is deleted.
214
- $draftFolderIds = @{}
215
- foreach ($entry in $scan) { $draftFolderIds[$entry.folder.EntryID] = $entry.includeNull }
216
-
217
- $deleted = 0
146
+ /** Send the named drafts, each proved to be this account's first. */
147
+ async function sendDrafts(request) {
148
+ const output = await (0, run_1.runPowerShellJson)(`${draftsScript(request.account)}
149
+ $sent = 0
218
150
  $failed = @()
219
- foreach ($id in @(${(0, run_1.psList)(entryIds)})) {
151
+ foreach ($id in ${(0, run_1.psArray)(request.entryIds)}) {
152
+ $subject = ''
220
153
  try {
221
- $it = $null
222
- foreach ($entry in $scan) {
223
- try {
224
- $it = $ns.GetItemFromID($id, $entry.folder.StoreID)
225
- if ($it -ne $null) { break }
226
- } catch { $it = $null }
227
- }
228
- if ($it -eq $null) { throw "no such item in this account's Drafts folders" }
229
- $parentId = ''
230
- try { $parentId = $it.Parent.EntryID } catch {}
231
- if (-not $draftFolderIds.ContainsKey($parentId)) {
232
- throw "item is not in this account's Drafts folder - refusing to delete"
233
- }
234
- if (-not (Test-DraftMatches $it $draftFolderIds[$parentId])) {
235
- throw "draft is not bound to $target - refusing to delete"
236
- }
237
- $it.Delete()
238
- $deleted++
154
+ $draft = Resolve-OwnDraft $id 'send'
155
+ try { $subject = [string]$draft.Subject } catch {}
156
+ $draft.Send()
157
+ $sent++
239
158
  } catch {
240
- $failed += [pscustomobject]@{ entryId = $id; error = $_.Exception.Message }
159
+ $failed += [PSCustomObject]@{ entryId = $id; subject = $subject; error = $_.Exception.Message }
241
160
  }
242
161
  }
243
- ConvertTo-Json @{ deleted = $deleted; failed = @($failed) } -Depth 3
244
- `;
245
- const parsed = (0, json_1.parseObject)(await (0, run_1.runPowerShell)(script, 300000));
246
- return {
247
- deleted: (0, json_1.num)(parsed.deleted),
248
- failed: (0, json_1.toArray)(parsed.failed).map(f => {
249
- const e = (0, json_1.record)(f);
250
- return { entryId: (0, json_1.str)(e.entryId), error: (0, json_1.str)(e.error) };
251
- }),
252
- };
162
+ ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ sent = $sent; failed = @($failed) })
163
+ `, 'scan');
164
+ const e = (0, json_1.record)(output);
165
+ return { sent: (0, json_1.num)(e.sent), failed: (0, json_1.itemFailures)(e.failed) };
253
166
  }
254
167
  /**
255
- * Send a chosen subset of `emailAccount`'s drafts by EntryID — e.g. after
256
- * `listOutlookDrafts` and a user review pass — instead of `sendAllDrafts`'s
257
- * account-wide sweep.
168
+ * Send every draft belonging to the account.
258
169
  *
259
- * Same ownership gate as `deleteOutlookDrafts`: every id must resolve to a mail
260
- * item sitting in one of THIS account's Drafts folders before anything is sent.
261
- * An id pointing at ordinary inbox mail, or at another account's draft, is
262
- * refused and reported rather than sent.
170
+ * The matching items are snapshotted before the first send: sending moves an
171
+ * item out of Drafts, and sending during a live enumeration would skip every
172
+ * other one.
263
173
  */
264
- async function sendDrafts(emailAccount, entryIds) {
265
- (0, run_1.requireWindows)();
266
- if (entryIds.length === 0)
267
- return { sent: 0, failed: [] };
268
- const script = `${draftsScript(emailAccount)}
269
- # Index this account's Drafts folders by EntryID, so a resolved item can be PROVED
270
- # to live in one of them before it is sent.
271
- $draftFolderIds = @{}
272
- foreach ($entry in $scan) { $draftFolderIds[$entry.folder.EntryID] = $entry.includeNull }
273
-
174
+ async function sendAllDrafts(request) {
175
+ const output = await (0, run_1.runPowerShellJson)(`${draftsScript(request.account)}
176
+ $drafts = @()
177
+ foreach ($entry in $scan) {
178
+ foreach ($it in $entry.folder.Items) {
179
+ if (Test-DraftMatches $it $entry.includeUnstamped) { $drafts += $it }
180
+ }
181
+ }
274
182
  $sent = 0
275
183
  $failed = @()
276
- foreach ($id in @(${(0, run_1.psList)(entryIds)})) {
277
- $subj = ''
184
+ foreach ($draft in $drafts) {
185
+ $entryId = ''
186
+ $subject = ''
187
+ try { $entryId = [string]$draft.EntryID } catch {}
188
+ try { $subject = [string]$draft.Subject } catch {}
278
189
  try {
279
- $it = $null
280
- foreach ($entry in $scan) {
281
- try {
282
- $it = $ns.GetItemFromID($id, $entry.folder.StoreID)
283
- if ($it -ne $null) { break }
284
- } catch { $it = $null }
285
- }
286
- if ($it -eq $null) { throw "no such item in this account's Drafts folders" }
287
- try { $subj = $it.Subject } catch {}
288
- $parentId = ''
289
- try { $parentId = $it.Parent.EntryID } catch {}
290
- if (-not $draftFolderIds.ContainsKey($parentId)) {
291
- throw "item is not in this account's Drafts folder - refusing to send"
292
- }
293
- if (-not (Test-DraftMatches $it $draftFolderIds[$parentId])) {
294
- throw "draft is not bound to $target - refusing to send"
295
- }
296
- $it.Send()
190
+ $draft.Send()
297
191
  $sent++
298
192
  } catch {
299
- $failed += [pscustomobject]@{ entryId = $id; subject = $subj; error = $_.Exception.Message }
193
+ $failed += [PSCustomObject]@{ entryId = $entryId; subject = $subject; error = $_.Exception.Message }
300
194
  }
301
195
  }
302
- [pscustomobject]@{ sent = $sent; failed = @($failed) } | ConvertTo-Json -Compress -Depth 4
303
- `;
304
- const parsed = (0, json_1.parseObject)(await (0, run_1.runPowerShell)(script, 300000));
305
- return {
306
- sent: (0, json_1.num)(parsed.sent),
307
- failed: (0, json_1.toArray)(parsed.failed).map(f => {
308
- const e = (0, json_1.record)(f);
309
- return { entryId: (0, json_1.str)(e.entryId), subject: (0, json_1.str)(e.subject), error: (0, json_1.str)(e.error) };
310
- }),
311
- };
196
+ ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ sent = $sent; failed = @($failed) })
197
+ `, 'scan');
198
+ const e = (0, json_1.record)(output);
199
+ return { sent: (0, json_1.num)(e.sent), failed: (0, json_1.itemFailures)(e.failed) };
200
+ }
201
+ /**
202
+ * Delete the named drafts, each proved to be this account's first. Outlook's
203
+ * Delete() moves an item to Deleted Items, so a mistake stays recoverable.
204
+ * Without the proof this would be a general "delete any email by id" call,
205
+ * which is not what it is for.
206
+ */
207
+ async function deleteOutlookDrafts(request) {
208
+ const output = await (0, run_1.runPowerShellJson)(`${draftsScript(request.account)}
209
+ $deleted = 0
210
+ $failed = @()
211
+ foreach ($id in ${(0, run_1.psArray)(request.entryIds)}) {
212
+ $subject = ''
213
+ try {
214
+ $draft = Resolve-OwnDraft $id 'delete'
215
+ try { $subject = [string]$draft.Subject } catch {}
216
+ $draft.Delete()
217
+ $deleted++
218
+ } catch {
219
+ $failed += [PSCustomObject]@{ entryId = $id; subject = $subject; error = $_.Exception.Message }
220
+ }
221
+ }
222
+ ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ deleted = $deleted; failed = @($failed) })
223
+ `, 'scan');
224
+ const e = (0, json_1.record)(output);
225
+ return { deleted: (0, json_1.num)(e.deleted), failed: (0, json_1.itemFailures)(e.failed) };
312
226
  }
313
227
  //# sourceMappingURL=drafts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"drafts.js","sourceRoot":"","sources":["../../src/windows/drafts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"drafts.js","sourceRoot":"","sources":["../../src/windows/drafts.ts"],"names":[],"mappings":"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","sourcesContent":["// Drafts: which ones belong to an account, and listing, sending or deleting them.\nimport { psArray, psInt, runPowerShellJson } from './run';\nimport { accountScript, DELIVERY_STORE_PS } from './scripts';\nimport { bool, itemFailures, num, record, str, strList, toArray } from '../shared/json';\nimport { FolderId } from '../mail';\nimport type { AccountRequest, EntryIdsRequest, ListDraftsRequest } from '../backend';\nimport type { DeleteDraftsResult, ListDraftsResult, SendDraftsResult } from '../types';\n\n/**\n * Resolve the Drafts folders belonging to one account into `$scan`, and define\n * `Test-DraftMatches` over them. Emitted into every drafts script, so listing,\n * sending and deleting can never disagree about which drafts are this account's.\n *\n * Two Drafts folders can hold one account's drafts, so both are scanned:\n * - the account's OWN store's Drafts, where a draft composed in that mailbox\n * lands. A draft there with no explicit sending account still belongs to it.\n * - the DEFAULT store's Drafts, where a program's `Save()` files a draft\n * whatever account it sends from. Only drafts stamped with this account\n * match there; an unstamped one belongs to the default account.\n * When the two are the same folder it is scanned once, under the first rule.\n *\n * Non-mail items (meeting requests, reports) never match.\n */\nconst DRAFTS_SCAN_PS = `\n$scan = @()\n$seenFolders = @{}\n$homeDrafts = $null\ntry { $homeDrafts = $store.GetDefaultFolder(${psInt(FolderId.Drafts)}) } catch {}\nif ($homeDrafts -ne $null) {\n $seenFolders[\"$($homeDrafts.StoreID)|$($homeDrafts.EntryID)\"] = $true\n $scan += [PSCustomObject]@{ folder = $homeDrafts; includeUnstamped = $true }\n}\n$defaultDrafts = $null\ntry { $defaultDrafts = $ns.GetDefaultFolder(${psInt(FolderId.Drafts)}) } catch {}\nif ($defaultDrafts -ne $null -and -not $seenFolders.ContainsKey(\"$($defaultDrafts.StoreID)|$($defaultDrafts.EntryID)\")) {\n $scan += [PSCustomObject]@{ folder = $defaultDrafts; includeUnstamped = $false }\n}\nfunction Test-DraftMatches($item, [bool]$includeUnstamped) {\n if ($item.Class -ne 43) { return $false } # olMail only\n $sender = $null\n try { $sender = $item.SendUsingAccount } catch {}\n if ($sender -eq $null) { return $includeUnstamped }\n return ([string]$sender.SmtpAddress -ieq $target)\n}\n# An id is acted on only once it is PROVED to be one of this account's drafts:\n# resolved in one of the scanned folders' stores, sitting in that folder, and\n# bound to this account (or unbound, in its own store).\n$draftFolderRules = @{}\nforeach ($entry in $scan) { $draftFolderRules[[string]$entry.folder.EntryID] = $entry.includeUnstamped }\nfunction Resolve-OwnDraft([string]$id, [string]$verb) {\n $draft = $null\n foreach ($entry in $scan) {\n try { $draft = $ns.GetItemFromID($id, $entry.folder.StoreID) } catch { $draft = $null }\n if ($draft -ne $null) { break }\n }\n if ($draft -eq $null) { throw \"no such item in this account's Drafts folders\" }\n $parentId = ''\n try { $parentId = [string]$draft.Parent.EntryID } catch {}\n if (-not $draftFolderRules.ContainsKey($parentId)) { throw \"item is not in this account's Drafts folder - refusing to $verb\" }\n if (-not (Test-DraftMatches $draft $draftFolderRules[$parentId])) { throw \"draft is not bound to $target - refusing to $verb\" }\n return $draft\n}\n`;\n\nfunction draftsScript(account: string): string {\n return accountScript(account) + DELIVERY_STORE_PS + DRAFTS_SCAN_PS;\n}\n\n/** The account's drafts, newest first. Bodies are previewed, never returned whole. */\nexport async function listOutlookDrafts(request: ListDraftsRequest): Promise<ListDraftsResult> {\n const output = await runPowerShellJson(`${draftsScript(request.account)}\n$previewChars = ${psInt(request.previewChars)}\n$limit = ${psInt(request.limit)}\n$rows = @()\n$folderPaths = @()\nforeach ($entry in $scan) {\n $folderPath = ''\n try { $folderPath = [string]$entry.folder.FolderPath } catch {}\n $folderPaths += $folderPath\n foreach ($it in $entry.folder.Items) {\n if (-not (Test-DraftMatches $it $entry.includeUnstamped)) { continue }\n $addresses = @()\n try {\n foreach ($r in $it.Recipients) {\n $address = ''\n try { $address = [string]$r.Address } catch {}\n # An unresolved Exchange entry has a DN for an address; its name reads better.\n if ($address.StartsWith('/')) { try { $address = [string]$r.Name } catch {} }\n if ($address) { $addresses += $address }\n }\n } catch {}\n $preview = ''\n if ($previewChars -gt 0) {\n try { $preview = ([string]$it.Body -replace '\\\\s+', ' ').Trim() } catch {}\n if ($preview.Length -gt $previewChars) { $preview = $preview.Substring(0, $previewChars) }\n }\n $modified = ''\n try { $modified = $it.LastModificationTime.ToString('yyyy-MM-dd HH:mm') } catch {}\n $hasAttachments = $false\n try { $hasAttachments = ($it.Attachments.Count -gt 0) } catch {}\n $rows += [PSCustomObject]@{\n entryId = [string]$it.EntryID\n subject = [string]$it.Subject\n to = [string]$it.To\n toEmails = @($addresses)\n bodyPreview = $preview\n hasAttachments = $hasAttachments\n lastModified = $modified\n folderPath = $folderPath\n }\n }\n}\n$sorted = @($rows | Sort-Object -Property lastModified -Descending)\n$total = $sorted.Count\nif ($total -gt $limit) { $sorted = @($sorted[0..($limit - 1)]) }\nConvertTo-Json -Depth 4 -InputObject ([PSCustomObject]@{\n account = $target\n foldersScanned = @($folderPaths)\n count = $total\n truncated = ($total -gt $limit)\n drafts = @($sorted)\n})\n`, 'scan');\n const e = record(output);\n return {\n account: str(e.account) || request.account,\n foldersScanned: toArray(e.foldersScanned).map(str),\n count: num(e.count),\n truncated: bool(e.truncated),\n drafts: toArray(e.drafts).map(row => {\n const d = record(row);\n return {\n entryId: str(d.entryId),\n subject: str(d.subject),\n to: str(d.to),\n toEmails: strList(d.toEmails),\n bodyPreview: str(d.bodyPreview),\n hasAttachments: bool(d.hasAttachments),\n lastModified: str(d.lastModified),\n folderPath: str(d.folderPath),\n };\n }),\n };\n}\n\n/** Send the named drafts, each proved to be this account's first. */\nexport async function sendDrafts(request: EntryIdsRequest): Promise<SendDraftsResult> {\n const output = await runPowerShellJson(`${draftsScript(request.account)}\n$sent = 0\n$failed = @()\nforeach ($id in ${psArray(request.entryIds)}) {\n $subject = ''\n try {\n $draft = Resolve-OwnDraft $id 'send'\n try { $subject = [string]$draft.Subject } catch {}\n $draft.Send()\n $sent++\n } catch {\n $failed += [PSCustomObject]@{ entryId = $id; subject = $subject; error = $_.Exception.Message }\n }\n}\nConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ sent = $sent; failed = @($failed) })\n`, 'scan');\n const e = record(output);\n return {sent: num(e.sent), failed: itemFailures(e.failed)};\n}\n\n/**\n * Send every draft belonging to the account.\n *\n * The matching items are snapshotted before the first send: sending moves an\n * item out of Drafts, and sending during a live enumeration would skip every\n * other one.\n */\nexport async function sendAllDrafts(request: AccountRequest): Promise<SendDraftsResult> {\n const output = await runPowerShellJson(`${draftsScript(request.account)}\n$drafts = @()\nforeach ($entry in $scan) {\n foreach ($it in $entry.folder.Items) {\n if (Test-DraftMatches $it $entry.includeUnstamped) { $drafts += $it }\n }\n}\n$sent = 0\n$failed = @()\nforeach ($draft in $drafts) {\n $entryId = ''\n $subject = ''\n try { $entryId = [string]$draft.EntryID } catch {}\n try { $subject = [string]$draft.Subject } catch {}\n try {\n $draft.Send()\n $sent++\n } catch {\n $failed += [PSCustomObject]@{ entryId = $entryId; subject = $subject; error = $_.Exception.Message }\n }\n}\nConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ sent = $sent; failed = @($failed) })\n`, 'scan');\n const e = record(output);\n return {sent: num(e.sent), failed: itemFailures(e.failed)};\n}\n\n/**\n * Delete the named drafts, each proved to be this account's first. Outlook's\n * Delete() moves an item to Deleted Items, so a mistake stays recoverable.\n * Without the proof this would be a general \"delete any email by id\" call,\n * which is not what it is for.\n */\nexport async function deleteOutlookDrafts(request: EntryIdsRequest): Promise<DeleteDraftsResult> {\n const output = await runPowerShellJson(`${draftsScript(request.account)}\n$deleted = 0\n$failed = @()\nforeach ($id in ${psArray(request.entryIds)}) {\n $subject = ''\n try {\n $draft = Resolve-OwnDraft $id 'delete'\n try { $subject = [string]$draft.Subject } catch {}\n $draft.Delete()\n $deleted++\n } catch {\n $failed += [PSCustomObject]@{ entryId = $id; subject = $subject; error = $_.Exception.Message }\n }\n}\nConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ deleted = $deleted; failed = @($failed) })\n`, 'scan');\n const e = record(output);\n return {deleted: num(e.deleted), failed: itemFailures(e.failed)};\n}\n"]}
@@ -1,16 +1,11 @@
1
+ import type { ListFoldersRequest, MoveRequest } from '../backend';
1
2
  import type { InboxFolderInfo, MoveEmailsResult } from '../types';
2
- /** List the folders under an account's Inbox (the user's filing folders). */
3
- export declare function listInboxFolders(emailAccount: string, maxDepth?: number): Promise<InboxFolderInfo[]>;
3
+ /** The folders under an account's Inbox, depth-first. */
4
+ export declare function listInboxFolders(request: ListFoldersRequest): Promise<InboxFolderInfo[]>;
4
5
  /**
5
- * Move emails (by EntryID) into any folder of the account — a filing folder under
6
- * the Inbox, or a well-known folder by name (see WELL_KNOWN_FOLDERS).
7
- *
8
- * **Moving to "Deleted Items" is how mail gets deleted reversibly**, which is why
9
- * this takes well-known roots at all: it means the destructive path and the filing
10
- * path are one tool, and the destructive one is undoable from the folder it lands in.
11
- *
12
- * `createIfMissing` builds the WHOLE missing chain, so a nested destination
13
- * ("Clients\\Acme\\2026") is one call rather than a manual mkdir first.
14
- * NOTE: moving changes an item's EntryID — the passed ids are dead afterwards.
6
+ * File emails into a folder of the account: one under the Inbox, or a
7
+ * well-known one by name. Moving to Deleted Items is how mail is deleted
8
+ * recoverably. Moving rewrites each item's entry id, so the ids passed in are
9
+ * dead afterwards.
15
10
  */
16
- export declare function moveOutlookEmails(emailAccount: string, entryIds: string[], folderName: string, createIfMissing?: boolean): Promise<MoveEmailsResult>;
11
+ export declare function moveOutlookEmails(request: MoveRequest): Promise<MoveEmailsResult>;