@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
@@ -7,159 +7,141 @@ const run_1 = require("./run");
7
7
  const scripts_1 = require("./scripts");
8
8
  const json_1 = require("../shared/json");
9
9
  const mail_1 = require("../mail");
10
- /** Folders whose contents are received or already-sent mail, not working state. */
11
- const PROTECTED_FOLDER_IDS = [6, 5];
12
- /** Walks an item's parent chain, so Inbox/Sent protection covers their subfolders too. */
13
- const FOLDER_CHAIN_PS = `
14
- function Get-FolderChainIds($folder) {
15
- $ids = @()
16
- $cur = $folder
17
- $guard = 0
18
- while ($cur -ne $null -and $guard -lt 25) {
19
- $cid = $null
20
- try { $cid = $cur.EntryID } catch { $cid = $null }
21
- if (-not $cid) { break }
22
- $ids += $cid
23
- $parent = $null
24
- try { $parent = $cur.Parent } catch { $parent = $null }
25
- if ($parent -eq $null) { break }
26
- $cur = $parent
27
- $guard++
28
- }
29
- return $ids
30
- }
31
- `;
10
+ /** Folders whose contents are received or sent mail rather than working state. */
11
+ const PROTECTED_FOLDER_IDS = [mail_1.FolderId.Inbox, mail_1.FolderId.SentMail];
12
+ const OUTCOME_STATUSES = ['deleted', 'would-delete', 'refused', 'failed'];
32
13
  /**
33
- * Delete mail by EntryID from anywhere in the account. Delete() moves each item to
34
- * Deleted Items, so this is recoverable — purgeDeletedItems is what destroys.
14
+ * Delete mail by entry id from anywhere in the account. Delete() moves each item
15
+ * to Deleted Items, so this is recoverable — purgeDeletedItems is what destroys.
35
16
  *
36
- * ⚠️ This is the one tool here that can reach received mail, and EntryIDs are a
37
- * genuinely unsafe key for it: GetItemFromID silently returns a DIFFERENT message
38
- * when handed a stale or wrong id, which is common precisely when many replies share
39
- * one subject (see saveEmailAttachmentDetailed). Three things hold the line:
40
- *
41
- * - **Inbox and Sent Items are refused by default, INCLUDING their subfolders** —
42
- * a filed subfolder is still received mail. `allowProtected` lifts that, and is
43
- * the caller explicitly taking responsibility.
44
- * - **`dryRun` resolves and reports without deleting**, so the exact subjects and
45
- * folders can be shown to the user before anything happens. Use it first.
46
- * - **Every outcome echoes the subject and folderPath** of the item actually
47
- * resolved, so a wrong id is visible after the fact rather than silent.
17
+ * This is the one operation that can reach received mail, and an entry id is an
18
+ * unsafe key for it: a stale or wrong id can resolve to a DIFFERENT message.
19
+ * Three things hold the line:
20
+ * - Inbox and Sent Items are refused, INCLUDING their subfolders (a filed
21
+ * folder is still received mail), unless `allowProtected`.
22
+ * - `dryRun` resolves and reports without deleting, so the exact subjects and
23
+ * folders can be checked before anything happens.
24
+ * - Every outcome echoes the subject and folder of the item actually resolved.
48
25
  */
49
- async function deleteOutlookEmails(emailAccount, entryIds, options = {}) {
50
- (0, run_1.requireWindows)();
51
- const { allowProtected = false, dryRun = false } = options;
52
- if (entryIds.length === 0) {
53
- return { dryRun, deleted: 0, refused: 0, failed: 0, items: [] };
54
- }
55
- const script = `${(0, scripts_1.accountScript)(emailAccount)}
26
+ async function deleteOutlookEmails(request) {
27
+ const output = await (0, run_1.runPowerShellJson)(`${(0, scripts_1.accountScript)(request.account)}
56
28
  ${scripts_1.DELIVERY_STORE_PS}
57
- ${FOLDER_CHAIN_PS}
29
+ $allowProtected = ${(0, run_1.psBool)(request.allowProtected)}
30
+ $dryRun = ${(0, run_1.psBool)(request.dryRun)}
31
+ $protectedReason = ${(0, run_1.psString)(mail_1.PROTECTED_MAIL_REASON)}
58
32
  $protectedIds = @{}
59
- foreach ($fid in @(${PROTECTED_FOLDER_IDS.join(',')})) {
60
- try { $protectedIds[$store.GetDefaultFolder($fid).EntryID] = $true } catch {}
33
+ foreach ($role in @(${PROTECTED_FOLDER_IDS.map(run_1.psInt).join(', ')})) {
34
+ try { $protectedIds[[string]$store.GetDefaultFolder($role).EntryID] = $true } catch {}
35
+ }
36
+ function Test-UnderProtectedFolder($folder) {
37
+ $current = $folder
38
+ for ($guard = 0; $current -ne $null -and $guard -lt 25; $guard++) {
39
+ $folderId = $null
40
+ try { $folderId = [string]$current.EntryID } catch {}
41
+ if (-not $folderId) { return $false }
42
+ if ($protectedIds.ContainsKey($folderId)) { return $true }
43
+ $parent = $null
44
+ try { $parent = $current.Parent } catch {}
45
+ $current = $parent
46
+ }
47
+ return $false
61
48
  }
62
-
63
49
  $items = @()
64
- $deleted = 0
65
- $refused = 0
66
- $failed = 0
67
- foreach ($id in @(${(0, run_1.psList)(entryIds)})) {
50
+ foreach ($id in ${(0, run_1.psArray)(request.entryIds)}) {
68
51
  $subject = ''
69
52
  $folderPath = ''
70
53
  try {
71
54
  $it = $null
72
55
  try { $it = $ns.GetItemFromID($id, $store.StoreID) } catch { $it = $null }
73
56
  if ($it -eq $null) { try { $it = $ns.GetItemFromID($id) } catch { $it = $null } }
74
- if ($it -eq $null) { throw "no item with that EntryID in this account" }
75
- try { $subject = $it.Subject } catch {}
57
+ if ($it -eq $null) { throw "no item with that entry id in this account" }
58
+ try { $subject = [string]$it.Subject } catch {}
76
59
  $parent = $null
77
60
  try { $parent = $it.Parent } catch {}
78
61
  if ($parent -eq $null) { throw "item has no parent folder - refusing to delete" }
79
- try { $folderPath = $parent.FolderPath } catch {}
80
-
81
- $chain = Get-FolderChainIds $parent
82
- $isProtected = $false
83
- foreach ($cid in $chain) { if ($protectedIds.ContainsKey($cid)) { $isProtected = $true; break } }
84
-
85
- if ($isProtected -and -not ${(0, run_1.psBool)(allowProtected)}) {
86
- $refused++
87
- $items += [pscustomobject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'refused'; reason = '${(0, run_1.psEscape)(mail_1.PROTECTED_MAIL_REASON)}' }
88
- } elseif (${(0, run_1.psBool)(dryRun)}) {
89
- $items += [pscustomobject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'would-delete'; reason = '' }
62
+ try { $folderPath = [string]$parent.FolderPath } catch {}
63
+ if ((Test-UnderProtectedFolder $parent) -and -not $allowProtected) {
64
+ $items += [PSCustomObject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'refused'; reason = $protectedReason }
65
+ } elseif ($dryRun) {
66
+ $items += [PSCustomObject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'would-delete'; reason = '' }
90
67
  } else {
91
68
  $it.Delete()
92
- $deleted++
93
- $items += [pscustomobject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'deleted'; reason = '' }
69
+ $items += [PSCustomObject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'deleted'; reason = '' }
94
70
  }
95
71
  } catch {
96
- $failed++
97
- $items += [pscustomobject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'failed'; reason = $_.Exception.Message }
72
+ $items += [PSCustomObject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'failed'; reason = $_.Exception.Message }
98
73
  }
99
74
  }
100
- ConvertTo-Json @{ dryRun = ${(0, run_1.psBool)(dryRun)}; deleted = $deleted; refused = $refused; failed = $failed; items = @($items) } -Depth 4
101
- `;
102
- const parsed = (0, json_1.parseObject)(await (0, run_1.runPowerShell)(script, 300000));
75
+ ConvertTo-Json -Depth 3 -InputObject @($items)
76
+ `, 'scan');
77
+ const items = (0, json_1.toArray)(output).map(row => {
78
+ const e = (0, json_1.record)(row);
79
+ const status = OUTCOME_STATUSES.find(s => s === (0, json_1.str)(e.status)) ?? 'failed';
80
+ return {
81
+ entryId: (0, json_1.str)(e.entryId),
82
+ subject: (0, json_1.str)(e.subject),
83
+ folderPath: (0, json_1.str)(e.folderPath),
84
+ status,
85
+ reason: (0, json_1.str)(e.reason),
86
+ };
87
+ });
103
88
  return {
104
- dryRun,
105
- deleted: (0, json_1.num)(parsed.deleted),
106
- refused: (0, json_1.num)(parsed.refused),
107
- failed: (0, json_1.num)(parsed.failed),
108
- items: (0, json_1.toArray)(parsed.items).map(i => {
109
- const e = (0, json_1.record)(i);
110
- return {
111
- entryId: (0, json_1.str)(e.entryId),
112
- subject: (0, json_1.str)(e.subject),
113
- folderPath: (0, json_1.str)(e.folderPath),
114
- status: ((0, json_1.str)(e.status) || 'failed'),
115
- reason: (0, json_1.str)(e.reason),
116
- };
117
- }),
89
+ dryRun: request.dryRun,
90
+ deleted: items.filter(i => i.status === 'deleted').length,
91
+ refused: items.filter(i => i.status === 'refused').length,
92
+ failed: items.filter(i => i.status === 'failed').length,
93
+ items,
118
94
  };
119
95
  }
120
96
  /**
121
- * Permanently remove items from the account's Deleted Items folder. This is the ONE
122
- * genuinely irreversible operation here — nothing recovers from it — which is why it
123
- * is folder-scoped rather than keyed on an EntryID: it can only ever destroy what the
124
- * user already threw away.
97
+ * Permanently remove items from the account's Deleted Items. The ONE irreversible
98
+ * operation here, which is why it is folder-scoped rather than keyed on ids: it
99
+ * can only ever destroy what was already thrown away.
125
100
  *
126
- * `olderThanDays` keeps recent items (0 = purge everything). Iterates backwards, as
127
- * deleting mutates the collection and a forward walk would skip every other item.
101
+ * Walks backwards, since deleting shifts the collection and a forward walk would
102
+ * skip every other item.
128
103
  */
129
- async function purgeDeletedItems(emailAccount, olderThanDays = 0, dryRun = false) {
130
- (0, run_1.requireWindows)();
131
- const script = `${(0, scripts_1.accountScript)(emailAccount)}
132
- $folder = $account.DeliveryStore.GetDefaultFolder(3)
133
- $cutoff = (Get-Date).AddDays(-${olderThanDays})
104
+ async function purgeDeletedItems(request) {
105
+ const output = await (0, run_1.runPowerShellJson)(`${(0, scripts_1.accountScript)(request.account)}
106
+ ${scripts_1.DELIVERY_STORE_PS}
107
+ $folder = $store.GetDefaultFolder(${(0, run_1.psInt)(mail_1.FolderId.DeletedItems)})
108
+ $olderThanDays = ${(0, run_1.psInt)(request.olderThanDays)}
109
+ $dryRun = ${(0, run_1.psBool)(request.dryRun)}
110
+ ${(0, scripts_1.cutoffScript)(request.olderThanDays)}
134
111
  $matched = 0
135
112
  $purged = 0
136
113
  $kept = 0
137
114
  $failed = 0
138
- # Bound once: every $folder.Items is a COM property get that materialises a
139
- # fresh collection object, and this loop runs once per message in Deleted Items.
115
+ # Bound once: each $folder.Items is a COM call that builds a fresh collection.
140
116
  $folderItems = $folder.Items
141
117
  for ($i = $folderItems.Count; $i -ge 1; $i--) {
142
118
  $it = $null
143
119
  try { $it = $folderItems.Item($i) } catch { continue }
144
120
  $stamp = $null
145
- foreach ($p in @('ReceivedTime','LastModificationTime','CreationTime')) {
146
- try { $stamp = $it.$p; if ($stamp -ne $null) { break } } catch {}
121
+ foreach ($property in @('ReceivedTime', 'LastModificationTime', 'CreationTime')) {
122
+ try { $stamp = $it.$property; if ($stamp -ne $null) { break } } catch {}
147
123
  }
148
- if (${olderThanDays} -gt 0 -and $stamp -ne $null -and $stamp -gt $cutoff) { $kept++; continue }
124
+ if ($olderThanDays -gt 0 -and $stamp -ne $null -and $stamp -gt $cutoff) { $kept++; continue }
149
125
  $matched++
150
- if (${(0, run_1.psBool)(dryRun)}) { continue }
126
+ if ($dryRun) { continue }
151
127
  try { $it.Delete(); $purged++ } catch { $failed++ }
152
128
  }
153
- ConvertTo-Json @{ folderPath = $folder.FolderPath; matched = $matched; purged = $purged; kept = $kept; failed = $failed } -Depth 3
154
- `;
155
- const parsed = (0, json_1.parseObject)(await (0, run_1.runPowerShell)(script, 600000));
129
+ ConvertTo-Json -Compress -InputObject ([PSCustomObject]@{
130
+ folderPath = [string]$folder.FolderPath
131
+ matched = $matched
132
+ purged = $purged
133
+ kept = $kept
134
+ failed = $failed
135
+ })
136
+ `, 'purge');
137
+ const e = (0, json_1.record)(output);
156
138
  return {
157
- folderPath: (0, json_1.str)(parsed.folderPath),
158
- dryRun,
159
- matched: (0, json_1.num)(parsed.matched),
160
- purged: (0, json_1.num)(parsed.purged),
161
- kept: (0, json_1.num)(parsed.kept),
162
- failed: (0, json_1.num)(parsed.failed),
139
+ folderPath: (0, json_1.str)(e.folderPath),
140
+ dryRun: request.dryRun,
141
+ matched: (0, json_1.num)(e.matched),
142
+ purged: (0, json_1.num)(e.purged),
143
+ kept: (0, json_1.num)(e.kept),
144
+ failed: (0, json_1.num)(e.failed),
163
145
  };
164
146
  }
165
147
  //# sourceMappingURL=cleanup.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/windows/cleanup.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/windows/cleanup.ts"],"names":[],"mappings":"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","sourcesContent":["// Deleting mail, and emptying what was already deleted.\nimport { psArray, psBool, psInt, psString, runPowerShellJson } from './run';\nimport { accountScript, cutoffScript, DELIVERY_STORE_PS } from './scripts';\nimport { num, record, str, toArray } from '../shared/json';\nimport { FolderId, PROTECTED_MAIL_REASON } from '../mail';\nimport type { DeleteEmailsRequest, PurgeRequest } from '../backend';\nimport type { DeleteMailOutcome, DeleteMailResult, PurgeDeletedItemsResult } from '../types';\n\n/** Folders whose contents are received or sent mail rather than working state. */\nconst PROTECTED_FOLDER_IDS = [FolderId.Inbox, FolderId.SentMail];\n\nconst OUTCOME_STATUSES: readonly DeleteMailOutcome['status'][] = ['deleted', 'would-delete', 'refused', 'failed'];\n\n/**\n * Delete mail by entry id from anywhere in the account. Delete() moves each item\n * to Deleted Items, so this is recoverable — purgeDeletedItems is what destroys.\n *\n * This is the one operation that can reach received mail, and an entry id is an\n * unsafe key for it: a stale or wrong id can resolve to a DIFFERENT message.\n * Three things hold the line:\n * - Inbox and Sent Items are refused, INCLUDING their subfolders (a filed\n * folder is still received mail), unless `allowProtected`.\n * - `dryRun` resolves and reports without deleting, so the exact subjects and\n * folders can be checked before anything happens.\n * - Every outcome echoes the subject and folder of the item actually resolved.\n */\nexport async function deleteOutlookEmails(request: DeleteEmailsRequest): Promise<DeleteMailResult> {\n const output = await runPowerShellJson(`${accountScript(request.account)}\n${DELIVERY_STORE_PS}\n$allowProtected = ${psBool(request.allowProtected)}\n$dryRun = ${psBool(request.dryRun)}\n$protectedReason = ${psString(PROTECTED_MAIL_REASON)}\n$protectedIds = @{}\nforeach ($role in @(${PROTECTED_FOLDER_IDS.map(psInt).join(', ')})) {\n try { $protectedIds[[string]$store.GetDefaultFolder($role).EntryID] = $true } catch {}\n}\nfunction Test-UnderProtectedFolder($folder) {\n $current = $folder\n for ($guard = 0; $current -ne $null -and $guard -lt 25; $guard++) {\n $folderId = $null\n try { $folderId = [string]$current.EntryID } catch {}\n if (-not $folderId) { return $false }\n if ($protectedIds.ContainsKey($folderId)) { return $true }\n $parent = $null\n try { $parent = $current.Parent } catch {}\n $current = $parent\n }\n return $false\n}\n$items = @()\nforeach ($id in ${psArray(request.entryIds)}) {\n $subject = ''\n $folderPath = ''\n try {\n $it = $null\n try { $it = $ns.GetItemFromID($id, $store.StoreID) } catch { $it = $null }\n if ($it -eq $null) { try { $it = $ns.GetItemFromID($id) } catch { $it = $null } }\n if ($it -eq $null) { throw \"no item with that entry id in this account\" }\n try { $subject = [string]$it.Subject } catch {}\n $parent = $null\n try { $parent = $it.Parent } catch {}\n if ($parent -eq $null) { throw \"item has no parent folder - refusing to delete\" }\n try { $folderPath = [string]$parent.FolderPath } catch {}\n if ((Test-UnderProtectedFolder $parent) -and -not $allowProtected) {\n $items += [PSCustomObject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'refused'; reason = $protectedReason }\n } elseif ($dryRun) {\n $items += [PSCustomObject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'would-delete'; reason = '' }\n } else {\n $it.Delete()\n $items += [PSCustomObject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'deleted'; reason = '' }\n }\n } catch {\n $items += [PSCustomObject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'failed'; reason = $_.Exception.Message }\n }\n}\nConvertTo-Json -Depth 3 -InputObject @($items)\n`, 'scan');\n const items: DeleteMailOutcome[] = toArray(output).map(row => {\n const e = record(row);\n const status = OUTCOME_STATUSES.find(s => s === str(e.status)) ?? 'failed';\n return {\n entryId: str(e.entryId),\n subject: str(e.subject),\n folderPath: str(e.folderPath),\n status,\n reason: str(e.reason),\n };\n });\n return {\n dryRun: request.dryRun,\n deleted: items.filter(i => i.status === 'deleted').length,\n refused: items.filter(i => i.status === 'refused').length,\n failed: items.filter(i => i.status === 'failed').length,\n items,\n };\n}\n\n/**\n * Permanently remove items from the account's Deleted Items. The ONE irreversible\n * operation here, which is why it is folder-scoped rather than keyed on ids: it\n * can only ever destroy what was already thrown away.\n *\n * Walks backwards, since deleting shifts the collection and a forward walk would\n * skip every other item.\n */\nexport async function purgeDeletedItems(request: PurgeRequest): Promise<PurgeDeletedItemsResult> {\n const output = await runPowerShellJson(`${accountScript(request.account)}\n${DELIVERY_STORE_PS}\n$folder = $store.GetDefaultFolder(${psInt(FolderId.DeletedItems)})\n$olderThanDays = ${psInt(request.olderThanDays)}\n$dryRun = ${psBool(request.dryRun)}\n${cutoffScript(request.olderThanDays)}\n$matched = 0\n$purged = 0\n$kept = 0\n$failed = 0\n# Bound once: each $folder.Items is a COM call that builds a fresh collection.\n$folderItems = $folder.Items\nfor ($i = $folderItems.Count; $i -ge 1; $i--) {\n $it = $null\n try { $it = $folderItems.Item($i) } catch { continue }\n $stamp = $null\n foreach ($property in @('ReceivedTime', 'LastModificationTime', 'CreationTime')) {\n try { $stamp = $it.$property; if ($stamp -ne $null) { break } } catch {}\n }\n if ($olderThanDays -gt 0 -and $stamp -ne $null -and $stamp -gt $cutoff) { $kept++; continue }\n $matched++\n if ($dryRun) { continue }\n try { $it.Delete(); $purged++ } catch { $failed++ }\n}\nConvertTo-Json -Compress -InputObject ([PSCustomObject]@{\n folderPath = [string]$folder.FolderPath\n matched = $matched\n purged = $purged\n kept = $kept\n failed = $failed\n})\n`, 'purge');\n const e = record(output);\n return {\n folderPath: str(e.folderPath),\n dryRun: request.dryRun,\n matched: num(e.matched),\n purged: num(e.purged),\n kept: num(e.kept),\n failed: num(e.failed),\n };\n}\n"]}
@@ -1,40 +1,21 @@
1
- import type { DeleteDraftsResult, ListDraftsResult, SendAllDraftsResult, SendDraftsResult } from '../types';
1
+ import type { AccountRequest, EntryIdsRequest, ListDraftsRequest } from '../backend';
2
+ import type { DeleteDraftsResult, ListDraftsResult, SendDraftsResult } from '../types';
3
+ /** The account's drafts, newest first. Bodies are previewed, never returned whole. */
4
+ export declare function listOutlookDrafts(request: ListDraftsRequest): Promise<ListDraftsResult>;
5
+ /** Send the named drafts, each proved to be this account's first. */
6
+ export declare function sendDrafts(request: EntryIdsRequest): Promise<SendDraftsResult>;
2
7
  /**
3
- * Send the mail drafts that belong to `emailAccount`, wherever Outlook filed them.
4
- * Which drafts those are is DRAFTS_SCAN_PS's rule, not this function's.
8
+ * Send every draft belonging to the account.
5
9
  *
6
- * Item references are snapshotted before any Send() call: sending moves an item out
7
- * of Drafts, and mutating the collection mid-enumeration would skip every other one.
10
+ * The matching items are snapshotted before the first send: sending moves an
11
+ * item out of Drafts, and sending during a live enumeration would skip every
12
+ * other one.
8
13
  */
9
- export declare function sendAllDrafts(emailAccount: string): Promise<SendAllDraftsResult>;
14
+ export declare function sendAllDrafts(request: AccountRequest): Promise<SendDraftsResult>;
10
15
  /**
11
- * List the mail drafts belonging to `emailAccount`, newest first. Which drafts
12
- * those are is DRAFTS_SCAN_PS's rule.
13
- *
14
- * Deliberately returns no StoreID: deleteOutlookDrafts re-resolves each EntryID
15
- * against the same folders, so repeating a ~700-char store id on every row would be
16
- * pure payload. Bodies are previewed, never returned whole — a templated reply body
17
- * runs to tens of thousands of characters and a folder's worth would blow the cap.
18
- */
19
- export declare function listOutlookDrafts(emailAccount: string, limit?: number, previewChars?: number): Promise<ListDraftsResult>;
20
- /**
21
- * Delete mail drafts by EntryID. Outlook's Delete() moves the item to Deleted Items
22
- * rather than destroying it, so a mistaken call stays recoverable from there.
23
- *
24
- * Every id must resolve to a mail item sitting in one of THIS account's Drafts
25
- * folders before anything is deleted; an id pointing at ordinary mail, or at another
26
- * account's draft, is refused and reported. Without that gate this would be a
27
- * general-purpose "delete any email by id" tool, which is not what it is for.
28
- */
29
- export declare function deleteOutlookDrafts(emailAccount: string, entryIds: string[]): Promise<DeleteDraftsResult>;
30
- /**
31
- * Send a chosen subset of `emailAccount`'s drafts by EntryID — e.g. after
32
- * `listOutlookDrafts` and a user review pass — instead of `sendAllDrafts`'s
33
- * account-wide sweep.
34
- *
35
- * Same ownership gate as `deleteOutlookDrafts`: every id must resolve to a mail
36
- * item sitting in one of THIS account's Drafts folders before anything is sent.
37
- * An id pointing at ordinary inbox mail, or at another account's draft, is
38
- * refused and reported rather than sent.
16
+ * Delete the named drafts, each proved to be this account's first. Outlook's
17
+ * Delete() moves an item to Deleted Items, so a mistake stays recoverable.
18
+ * Without the proof this would be a general "delete any email by id" call,
19
+ * which is not what it is for.
39
20
  */
40
- export declare function sendDrafts(emailAccount: string, entryIds: string[]): Promise<SendDraftsResult>;
21
+ export declare function deleteOutlookDrafts(request: EntryIdsRequest): Promise<DeleteDraftsResult>;