@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.
- package/README.md +150 -183
- package/dist/backend.d.ts +151 -0
- package/dist/backend.js +3 -0
- package/dist/backend.js.map +1 -0
- package/dist/bridge.d.ts +56 -0
- package/dist/bridge.js +507 -0
- package/dist/bridge.js.map +1 -0
- package/dist/errors.d.ts +81 -43
- package/dist/errors.js +98 -52
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.js +25 -52
- package/dist/index.js.map +1 -1
- package/dist/mac/accounts.d.ts +7 -2
- package/dist/mac/accounts.js +23 -6
- package/dist/mac/accounts.js.map +1 -1
- package/dist/mac/attachments.d.ts +5 -25
- package/dist/mac/attachments.js +40 -99
- package/dist/mac/attachments.js.map +1 -1
- package/dist/mac/bounces.d.ts +12 -27
- package/dist/mac/bounces.js +95 -112
- package/dist/mac/bounces.js.map +1 -1
- package/dist/mac/cleanup.d.ts +11 -25
- package/dist/mac/cleanup.js +50 -76
- package/dist/mac/cleanup.js.map +1 -1
- package/dist/mac/drafts.d.ts +13 -34
- package/dist/mac/drafts.js +95 -169
- package/dist/mac/drafts.js.map +1 -1
- package/dist/mac/folders.d.ts +10 -21
- package/dist/mac/folders.js +33 -47
- package/dist/mac/folders.js.map +1 -1
- package/dist/mac/index.d.ts +2 -22
- package/dist/mac/index.js +19 -81
- package/dist/mac/index.js.map +1 -1
- package/dist/mac/profile.d.ts +13 -0
- package/dist/mac/profile.js +149 -0
- package/dist/mac/profile.js.map +1 -0
- package/dist/mac/read.d.ts +18 -36
- package/dist/mac/read.js +154 -208
- package/dist/mac/read.js.map +1 -1
- package/dist/mac/run.d.ts +35 -39
- package/dist/mac/run.js +97 -102
- package/dist/mac/run.js.map +1 -1
- package/dist/mac/scripts.d.ts +78 -86
- package/dist/mac/scripts.js +125 -115
- package/dist/mac/scripts.js.map +1 -1
- package/dist/mac/send.d.ts +10 -20
- package/dist/mac/send.js +64 -86
- package/dist/mac/send.js.map +1 -1
- package/dist/mac/signatures.d.ts +2 -2
- package/dist/mac/signatures.js +9 -11
- package/dist/mac/signatures.js.map +1 -1
- package/dist/mac/templates.d.ts +14 -26
- package/dist/mac/templates.js +75 -113
- package/dist/mac/templates.js.map +1 -1
- package/dist/mail.d.ts +77 -45
- package/dist/mail.js +166 -73
- package/dist/mail.js.map +1 -1
- package/dist/runtime.d.ts +76 -43
- package/dist/runtime.js +103 -48
- package/dist/runtime.js.map +1 -1
- package/dist/shared/args.d.ts +48 -0
- package/dist/shared/args.js +216 -0
- package/dist/shared/args.js.map +1 -0
- package/dist/shared/attachmentMatch.d.ts +28 -16
- package/dist/shared/attachmentMatch.js +60 -22
- package/dist/shared/attachmentMatch.js.map +1 -1
- package/dist/shared/bounceReport.d.ts +11 -0
- package/dist/shared/bounceReport.js +45 -0
- package/dist/shared/bounceReport.js.map +1 -0
- package/dist/shared/bounceRules.d.ts +38 -10
- package/dist/shared/bounceRules.js +50 -32
- package/dist/shared/bounceRules.js.map +1 -1
- package/dist/shared/exec.d.ts +21 -0
- package/dist/shared/exec.js +47 -0
- package/dist/shared/exec.js.map +1 -0
- package/dist/shared/json.d.ts +12 -6
- package/dist/shared/json.js +29 -23
- package/dist/shared/json.js.map +1 -1
- package/dist/shared/locate.d.ts +39 -0
- package/dist/shared/locate.js +129 -0
- package/dist/shared/locate.js.map +1 -0
- package/dist/shared/replyBody.d.ts +42 -32
- package/dist/shared/replyBody.js +84 -71
- package/dist/shared/replyBody.js.map +1 -1
- package/dist/templateBody.d.ts +63 -0
- package/dist/{outlookTemplateSections.js → templateBody.js} +72 -72
- package/dist/templateBody.js.map +1 -0
- package/dist/types.d.ts +360 -165
- package/dist/types.js +8 -9
- package/dist/types.js.map +1 -1
- package/dist/windows/accounts.d.ts +10 -1
- package/dist/windows/accounts.js +25 -13
- package/dist/windows/accounts.js.map +1 -1
- package/dist/windows/attachments.d.ts +9 -24
- package/dist/windows/attachments.js +94 -119
- package/dist/windows/attachments.js.map +1 -1
- package/dist/windows/bounces.d.ts +13 -26
- package/dist/windows/bounces.js +169 -200
- package/dist/windows/bounces.js.map +1 -1
- package/dist/windows/cleanup.d.ts +19 -23
- package/dist/windows/cleanup.js +93 -111
- package/dist/windows/cleanup.js.map +1 -1
- package/dist/windows/drafts.d.ts +16 -35
- package/dist/windows/drafts.js +167 -253
- package/dist/windows/drafts.js.map +1 -1
- package/dist/windows/folders.d.ts +8 -13
- package/dist/windows/folders.js +45 -52
- package/dist/windows/folders.js.map +1 -1
- package/dist/windows/index.d.ts +2 -27
- package/dist/windows/index.js +11 -65
- package/dist/windows/index.js.map +1 -1
- package/dist/windows/read.d.ts +24 -45
- package/dist/windows/read.js +266 -279
- package/dist/windows/read.js.map +1 -1
- package/dist/windows/run.d.ts +39 -39
- package/dist/windows/run.js +151 -111
- package/dist/windows/run.js.map +1 -1
- package/dist/windows/scripts.d.ts +71 -38
- package/dist/windows/scripts.js +123 -71
- package/dist/windows/scripts.js.map +1 -1
- package/dist/windows/send.d.ts +6 -11
- package/dist/windows/send.js +70 -79
- package/dist/windows/send.js.map +1 -1
- package/dist/windows/signatures.d.ts +4 -10
- package/dist/windows/signatures.js +30 -37
- package/dist/windows/signatures.js.map +1 -1
- package/dist/windows/templates.d.ts +12 -18
- package/dist/windows/templates.js +126 -151
- package/dist/windows/templates.js.map +1 -1
- package/package.json +5 -4
- package/dist/OutlookService.d.ts +0 -43
- package/dist/OutlookService.js +0 -137
- package/dist/OutlookService.js.map +0 -1
- package/dist/outlookTemplateSections.d.ts +0 -62
- package/dist/outlookTemplateSections.js.map +0 -1
- package/dist/shared/capabilities.d.ts +0 -3
- package/dist/shared/capabilities.js +0 -9
- package/dist/shared/capabilities.js.map +0 -1
|
@@ -4,243 +4,218 @@ 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
|
-
//
|
|
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
|
|
11
|
+
const templateBody_1 = require("../templateBody");
|
|
12
12
|
const mail_1 = require("../mail");
|
|
13
13
|
const errors_1 = require("../errors");
|
|
14
14
|
const runtime_1 = require("../runtime");
|
|
15
|
+
/** How deep under the mailbox root a template folder is searched for. */
|
|
16
|
+
const TEMPLATE_SEARCH_DEPTH = 3;
|
|
17
|
+
/** Cap on one template body; a Word-generated one is large, and a runaway one shouldn't sink the call. */
|
|
18
|
+
const MAX_TEMPLATE_HTML = 100_000;
|
|
19
|
+
/** Plain text standing in for a body that wasn't asked for. */
|
|
20
|
+
const PREVIEW_CHARS = 200;
|
|
15
21
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* caller can ask the user whether to create it rather than fail.
|
|
22
|
+
* The template emails in a mailbox folder, newest first. A missing folder is
|
|
23
|
+
* reported as folderFound:false with the mailbox's top-level folder names
|
|
24
|
+
* rather than thrown, so a caller can offer to create it.
|
|
20
25
|
*/
|
|
21
|
-
async function readTemplateEmails(
|
|
22
|
-
(0, run_1.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const script = `${(0, scripts_1.accountScript)(emailAccount)}
|
|
26
|
-
$root = $account.DeliveryStore.GetRootFolder()
|
|
26
|
+
async function readTemplateEmails(request) {
|
|
27
|
+
const output = await (0, run_1.runPowerShellJson)(`${(0, scripts_1.accountScript)(request.account)}
|
|
28
|
+
${scripts_1.DELIVERY_STORE_PS}
|
|
29
|
+
$root = $store.GetRootFolder()
|
|
27
30
|
${scripts_1.FIND_FOLDER_PS}
|
|
28
|
-
$folder = Find-FolderByName $root
|
|
31
|
+
$folder = Find-FolderByName $root ${(0, run_1.psString)(request.folder)} ${(0, run_1.psInt)(TEMPLATE_SEARCH_DEPTH)}
|
|
29
32
|
if ($folder -eq $null) {
|
|
30
33
|
$names = @()
|
|
31
|
-
foreach ($f in $root.Folders) { $names += $f.Name }
|
|
32
|
-
ConvertTo-Json @{ folderFound = $false; folderPath = ''; templates = @(); availableFolders = $names }
|
|
34
|
+
foreach ($f in $root.Folders) { $names += [string]$f.Name }
|
|
35
|
+
ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ folderFound = $false; folderPath = ''; templates = @(); availableFolders = @($names) })
|
|
33
36
|
exit 0
|
|
34
37
|
}
|
|
35
|
-
$includeBody = ${(0, run_1.psBool)(includeBody)}
|
|
36
|
-
$wanted =
|
|
38
|
+
$includeBody = ${(0, run_1.psBool)(request.includeBody)}
|
|
39
|
+
$wanted = ${(0, run_1.psString)(request.subject ?? '')}
|
|
40
|
+
$limit = ${(0, run_1.psInt)(request.limit)}
|
|
41
|
+
$maxHtml = ${(0, run_1.psInt)(MAX_TEMPLATE_HTML)}
|
|
42
|
+
$previewChars = ${(0, run_1.psInt)(PREVIEW_CHARS)}
|
|
37
43
|
$items = $folder.Items
|
|
38
44
|
$items.Sort('[LastModificationTime]', $true)
|
|
39
|
-
$
|
|
40
|
-
# Without a subject filter the sort order does the capping (newest first). With one,
|
|
41
|
-
# scan the whole folder — the wanted template need not be among the newest — and stop
|
|
42
|
-
# as soon as the cap is filled.
|
|
45
|
+
$rows = @()
|
|
43
46
|
$count = $items.Count
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
# Without a subject the sort does the capping, newest first. With one, the whole
|
|
48
|
+
# folder is scanned — the template wanted need not be among the newest.
|
|
49
|
+
for ($i = 1; $i -le $count -and $rows.Count -lt $limit; $i++) {
|
|
46
50
|
$item = $null
|
|
47
51
|
try { $item = $items.Item($i) } catch { continue }
|
|
48
52
|
if ($item -eq $null) { continue }
|
|
49
|
-
$
|
|
50
|
-
try { $
|
|
51
|
-
if ($
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if ($subj -eq $null) { $subj = '' }
|
|
56
|
-
if ($subj.Trim() -ine $wanted) { continue }
|
|
57
|
-
}
|
|
53
|
+
$class = 0
|
|
54
|
+
try { $class = [int]$item.Class } catch {}
|
|
55
|
+
if ($class -ne 43) { continue }
|
|
56
|
+
$subject = ''
|
|
57
|
+
try { $subject = ([string]$item.Subject).Trim() } catch {}
|
|
58
|
+
if ($wanted -and $subject -ine $wanted) { continue }
|
|
58
59
|
$html = ''
|
|
59
60
|
$preview = ''
|
|
60
61
|
$markers = ''
|
|
61
|
-
$plain = ''
|
|
62
62
|
if ($includeBody) {
|
|
63
63
|
try { $html = [string]$item.HTMLBody } catch {}
|
|
64
|
-
# Embedded (cid:) images live as attachments on the TEMPLATE item
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
# template emails are text/HTML only.
|
|
64
|
+
# Embedded (cid:) images live as attachments on the TEMPLATE item, so its
|
|
65
|
+
# HTML reused elsewhere shows broken-image placeholders. Template emails
|
|
66
|
+
# are text and markup only.
|
|
68
67
|
$html = [regex]::Replace($html, '<img[^>]*src="cid:[^"]*"[^>]*>', '')
|
|
69
|
-
if ($html.Length -gt
|
|
68
|
+
if ($html.Length -gt $maxHtml) { $html = $html.Substring(0, $maxHtml) }
|
|
70
69
|
} else {
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
$found = @()
|
|
82
|
-
foreach ($x in [regex]::Matches($plain, '\\[\\[\\s*/?\\s*[A-Za-z0-9_-]{1,40}\\s*\\]\\]|\\{\\{\\s*[A-Za-z0-9_-]{1,40}\\s*\\}\\}')) {
|
|
83
|
-
$t = ($x.Value -replace '\\s', '')
|
|
84
|
-
if ($found -notcontains $t) { $found += $t }
|
|
85
|
-
}
|
|
86
|
-
$markers = ($found -join ' ')
|
|
87
|
-
$preview = $plain.Trim()
|
|
88
|
-
if ($preview.Length -gt 200) { $preview = $preview.Substring(0, 200) }
|
|
70
|
+
# No body asked for: a short preview, plus the section and placeholder
|
|
71
|
+
# markers scanned here from the full plain text — so a caller can confirm
|
|
72
|
+
# a template is intact without its body crossing stdout. Word can split a
|
|
73
|
+
# marker across tags in the HTML but not in the plain text.
|
|
74
|
+
$plain = ''
|
|
75
|
+
try { $plain = [string]$item.Body } catch {}
|
|
76
|
+
$found = @()
|
|
77
|
+
foreach ($m in [regex]::Matches($plain, '\\[\\[\\s*/?\\s*[A-Za-z0-9_-]{1,40}\\s*\\]\\]|\\{\\{\\s*[A-Za-z0-9_-]{1,40}\\s*\\}\\}')) {
|
|
78
|
+
$token = $m.Value -replace '\\s', ''
|
|
79
|
+
if ($found -notcontains $token) { $found += $token }
|
|
89
80
|
}
|
|
81
|
+
$markers = $found -join ' '
|
|
82
|
+
$preview = $plain.Trim()
|
|
83
|
+
if ($preview.Length -gt $previewChars) { $preview = $preview.Substring(0, $previewChars) }
|
|
90
84
|
}
|
|
91
|
-
$
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
$modified = ''
|
|
86
|
+
try { $modified = $item.LastModificationTime.ToString('yyyy-MM-dd HH:mm') } catch {}
|
|
87
|
+
$rows += [PSCustomObject]@{
|
|
88
|
+
entryId = [string]$item.EntryID
|
|
89
|
+
subject = $subject
|
|
94
90
|
htmlBody = $html
|
|
95
91
|
bodyPreview = $preview
|
|
96
92
|
markers = $markers
|
|
97
|
-
lastModified = $
|
|
93
|
+
lastModified = $modified
|
|
98
94
|
}
|
|
99
95
|
}
|
|
100
|
-
ConvertTo-Json
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
ConvertTo-Json -Depth 4 -InputObject ([PSCustomObject]@{
|
|
97
|
+
folderFound = $true
|
|
98
|
+
folderPath = [string]$folder.FolderPath
|
|
99
|
+
templates = @($rows)
|
|
100
|
+
availableFolders = @()
|
|
101
|
+
})
|
|
102
|
+
`, 'standard');
|
|
103
|
+
const e = (0, json_1.record)(output);
|
|
103
104
|
return {
|
|
104
|
-
folderFound:
|
|
105
|
-
folderPath: (0, json_1.str)(
|
|
106
|
-
templates: (0, json_1.toArray)(
|
|
107
|
-
const
|
|
108
|
-
const html = (0, json_1.str)(
|
|
109
|
-
// With
|
|
110
|
-
//
|
|
111
|
-
// of the plain
|
|
112
|
-
const markers = (0,
|
|
105
|
+
folderFound: (0, json_1.bool)(e.folderFound),
|
|
106
|
+
folderPath: (0, json_1.str)(e.folderPath),
|
|
107
|
+
templates: (0, json_1.toArray)(e.templates).map(row => {
|
|
108
|
+
const t = (0, json_1.record)(row);
|
|
109
|
+
const html = (0, json_1.str)(t.htmlBody);
|
|
110
|
+
// With the HTML in hand it is authoritative (markers survive Word
|
|
111
|
+
// splitting them across tags); without it, the markers the script
|
|
112
|
+
// scanned out of the plain text stand in.
|
|
113
|
+
const markers = (0, templateBody_1.findTemplateMarkers)(request.includeBody ? html : (0, json_1.str)(t.markers));
|
|
113
114
|
return {
|
|
114
|
-
entryId: (0, json_1.str)(
|
|
115
|
-
subject: (0, json_1.str)(
|
|
115
|
+
entryId: (0, json_1.str)(t.entryId),
|
|
116
|
+
subject: (0, json_1.str)(t.subject),
|
|
116
117
|
htmlBody: html,
|
|
117
|
-
bodyPreview: (0, json_1.str)(
|
|
118
|
+
bodyPreview: (0, json_1.str)(t.bodyPreview),
|
|
118
119
|
sections: markers.sections,
|
|
119
120
|
placeholders: markers.placeholders,
|
|
120
|
-
lastModified: (0, json_1.str)(
|
|
121
|
+
lastModified: (0, json_1.str)(t.lastModified),
|
|
121
122
|
};
|
|
122
123
|
}),
|
|
123
|
-
availableFolders: (0, json_1.toArray)(
|
|
124
|
+
availableFolders: (0, json_1.toArray)(e.availableFolders).map(json_1.str),
|
|
124
125
|
};
|
|
125
126
|
}
|
|
126
127
|
/**
|
|
127
|
-
* Save a new template email into a
|
|
128
|
-
*
|
|
129
|
-
* plain unsent mail (subject + HTML body) that can be edited in Outlook.
|
|
130
|
-
* Never overwrites an existing item — it always adds a new one, so check with
|
|
131
|
-
* readTemplateEmails first and only call after the user has agreed to create it.
|
|
128
|
+
* Save a new template email into a folder, creating the folder at the mailbox
|
|
129
|
+
* root when absent. Always adds a new item — never overwrites one.
|
|
132
130
|
*/
|
|
133
|
-
async function saveTemplateEmail(
|
|
134
|
-
(0, run_1.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
$
|
|
131
|
+
async function saveTemplateEmail(request) {
|
|
132
|
+
const body = (0, run_1.scriptInput)('template-body', request.htmlBody);
|
|
133
|
+
try {
|
|
134
|
+
const output = await (0, run_1.runPowerShellJson)(`${(0, scripts_1.accountScript)(request.account)}
|
|
135
|
+
${scripts_1.DELIVERY_STORE_PS}
|
|
136
|
+
$root = $store.GetRootFolder()
|
|
138
137
|
${scripts_1.FIND_FOLDER_PS}
|
|
139
|
-
$
|
|
138
|
+
$folderName = ${(0, run_1.psString)(request.folder)}
|
|
139
|
+
$folder = Find-FolderByName $root $folderName ${(0, run_1.psInt)(TEMPLATE_SEARCH_DEPTH)}
|
|
140
140
|
$folderCreated = $false
|
|
141
141
|
if ($folder -eq $null) {
|
|
142
|
-
$folder = $root.Folders.Add(
|
|
142
|
+
$folder = $root.Folders.Add($folderName)
|
|
143
143
|
$folderCreated = $true
|
|
144
144
|
}
|
|
145
145
|
$mail = $outlook.CreateItem(0)
|
|
146
|
-
$mail.Subject =
|
|
147
|
-
$mail.HTMLBody = [IO.File]::ReadAllText(
|
|
146
|
+
$mail.Subject = ${(0, run_1.psString)(request.subject)}
|
|
147
|
+
$mail.HTMLBody = [IO.File]::ReadAllText(${(0, run_1.psString)(body.path)}, [Text.Encoding]::UTF8)
|
|
148
148
|
$mail.Save()
|
|
149
|
-
$
|
|
150
|
-
ConvertTo-Json @{ folderPath = $folder.FolderPath; folderCreated = $folderCreated }
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
folderPath: (0, json_1.str)(parsed.folderPath),
|
|
156
|
-
folderCreated: Boolean(parsed.folderCreated),
|
|
157
|
-
};
|
|
149
|
+
[void]$mail.Move($folder)
|
|
150
|
+
ConvertTo-Json -Compress -InputObject ([PSCustomObject]@{ folderPath = [string]$folder.FolderPath; folderCreated = $folderCreated })
|
|
151
|
+
`, 'standard');
|
|
152
|
+
const e = (0, json_1.record)(output);
|
|
153
|
+
return { folderPath: (0, json_1.str)(e.folderPath), folderCreated: (0, json_1.bool)(e.folderCreated) };
|
|
158
154
|
}
|
|
159
155
|
finally {
|
|
160
156
|
body.cleanup();
|
|
161
157
|
}
|
|
162
158
|
}
|
|
163
159
|
/**
|
|
164
|
-
* Open
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
* Deliberately unbounded: the run lasts as long as the person is editing. Give
|
|
168
|
-
* it an AbortSignal (`bridge.withOptions({ signal })`) if the caller needs a way
|
|
169
|
-
* to give up on them.
|
|
160
|
+
* Open a compose window pre-filled with the HTML, wait for it to close, and
|
|
161
|
+
* return what was saved (Ctrl+S) — or, failing a save, the body as it last
|
|
162
|
+
* stood while the window was open. Leftover editor drafts are removed.
|
|
170
163
|
*/
|
|
171
|
-
async function editEmailTemplate(
|
|
172
|
-
(0, run_1.
|
|
173
|
-
const input = (0, run_1.scriptInput)('template-input', currentHtml);
|
|
164
|
+
async function editEmailTemplate(request) {
|
|
165
|
+
const input = (0, run_1.scriptInput)('template-input', request.html);
|
|
174
166
|
const outputFile = (0, runtime_1.tempFile)('template-output', 'html');
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
$
|
|
178
|
-
$
|
|
179
|
-
$outputPath = '${(0, run_1.psEscape)(outputFile)}'
|
|
180
|
-
$subject = '${(0, run_1.psEscape)(subject)}'
|
|
181
|
-
|
|
182
|
-
$outlook = New-Object -ComObject Outlook.Application
|
|
183
|
-
$ns = $outlook.GetNamespace('mapi')
|
|
167
|
+
try {
|
|
168
|
+
await (0, run_1.runPowerShell)(`${scripts_1.SESSION_PS}
|
|
169
|
+
$subject = ${(0, run_1.psString)(`${request.label} - Save (Ctrl+S) and close when done`)}
|
|
170
|
+
$outputPath = ${(0, run_1.psString)(outputFile)}
|
|
184
171
|
$mail = $outlook.CreateItem(0)
|
|
185
172
|
$mail.Subject = $subject
|
|
186
|
-
$mail.HTMLBody = [IO.File]::ReadAllText($
|
|
173
|
+
$mail.HTMLBody = [IO.File]::ReadAllText(${(0, run_1.psString)(input.path)}, [Text.Encoding]::UTF8)
|
|
187
174
|
$mail.Display()
|
|
188
|
-
|
|
189
|
-
# Give the inspector time to fully initialize
|
|
190
175
|
Start-Sleep -Seconds 2
|
|
191
|
-
|
|
192
|
-
#
|
|
193
|
-
# fallback for the case where the user edits but never presses Ctrl+S.
|
|
176
|
+
# Poll until the compose window closes, keeping the last live body as the
|
|
177
|
+
# fallback for an edit that was never saved.
|
|
194
178
|
$liveBody = ''
|
|
195
179
|
while ($true) {
|
|
196
180
|
Start-Sleep -Milliseconds 700
|
|
197
181
|
$open = $false
|
|
198
182
|
try {
|
|
199
|
-
foreach ($
|
|
200
|
-
$
|
|
201
|
-
try { $
|
|
202
|
-
if ($
|
|
183
|
+
foreach ($inspector in $outlook.Inspectors) {
|
|
184
|
+
$current = $null
|
|
185
|
+
try { $current = $inspector.CurrentItem } catch {}
|
|
186
|
+
if ($current -ne $null -and [string]$current.Subject -eq $subject) {
|
|
203
187
|
$open = $true
|
|
204
|
-
try { $liveBody = $
|
|
188
|
+
try { $liveBody = [string]$current.HTMLBody } catch {}
|
|
205
189
|
break
|
|
206
190
|
}
|
|
207
191
|
}
|
|
208
192
|
} catch {}
|
|
209
193
|
if (-not $open) { break }
|
|
210
194
|
}
|
|
211
|
-
|
|
212
|
-
#
|
|
213
|
-
# inspector closing, unlike the original mail reference. Also removes any
|
|
214
|
-
# leftover editor drafts so they don't pile up in the Drafts folder.
|
|
195
|
+
# Prefer the saved draft: it is what the person chose to keep, and it outlives
|
|
196
|
+
# the window. Every editor draft is removed so none pile up in Drafts.
|
|
215
197
|
$savedBody = ''
|
|
216
198
|
try {
|
|
217
|
-
$
|
|
218
|
-
$draftItems = $drafts.Items
|
|
199
|
+
$draftItems = $ns.GetDefaultFolder(${(0, run_1.psInt)(mail_1.FolderId.Drafts)}).Items
|
|
219
200
|
for ($i = $draftItems.Count; $i -ge 1; $i--) {
|
|
220
|
-
$
|
|
221
|
-
if ($
|
|
222
|
-
if (-not $savedBody) { try { $savedBody = $
|
|
223
|
-
try { $
|
|
201
|
+
$draft = $draftItems.Item($i)
|
|
202
|
+
if ([string]$draft.Subject -eq $subject) {
|
|
203
|
+
if (-not $savedBody) { try { $savedBody = [string]$draft.HTMLBody } catch {} }
|
|
204
|
+
try { $draft.Delete() } catch {}
|
|
224
205
|
}
|
|
225
206
|
}
|
|
226
207
|
} catch {}
|
|
227
|
-
|
|
228
208
|
$finalBody = if ($savedBody) { $savedBody } else { $liveBody }
|
|
229
|
-
if ($finalBody) {
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
`;
|
|
233
|
-
try {
|
|
234
|
-
// 0 disables the timeout: this run is as long as the edit takes.
|
|
235
|
-
await (0, run_1.runPowerShellFile)(script, 0);
|
|
209
|
+
if ($finalBody) { [IO.File]::WriteAllText($outputPath, $finalBody, [Text.Encoding]::UTF8) }
|
|
210
|
+
`, 'interactive');
|
|
236
211
|
}
|
|
237
212
|
finally {
|
|
238
213
|
input.cleanup();
|
|
239
214
|
}
|
|
240
|
-
const
|
|
241
|
-
if (!
|
|
242
|
-
throw new errors_1.NotFoundError('template', 'No template saved.
|
|
215
|
+
const saved = (0, run_1.readScriptOutput)(outputFile);
|
|
216
|
+
if (!saved) {
|
|
217
|
+
throw new errors_1.NotFoundError('template', 'No template was saved. Save (Ctrl+S) before closing the window.');
|
|
243
218
|
}
|
|
244
|
-
return
|
|
219
|
+
return saved;
|
|
245
220
|
}
|
|
246
221
|
//# 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 { findTemplateMarkers } 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/** How deep under the mailbox root a template folder is searched for. */\nconst TEMPLATE_SEARCH_DEPTH = 3;\n\n/** Cap on one template body; a Word-generated one is large, and a runaway one shouldn't sink the call. */\nconst MAX_TEMPLATE_HTML = 100_000;\n\n/** Plain text standing in for a body that wasn't asked for. */\nconst PREVIEW_CHARS = 200;\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(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 live as attachments on the TEMPLATE item, so its\n # HTML reused elsewhere shows broken-image placeholders. Template emails\n # are text and markup only.\n $html = [regex]::Replace($html, '<img[^>]*src=\"cid:[^\"]*\"[^>]*>', '')\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, '\\\\[\\\\[\\\\s*/?\\\\s*[A-Za-z0-9_-]{1,40}\\\\s*\\\\]\\\\]|\\\\{\\\\{\\\\s*[A-Za-z0-9_-]{1,40}\\\\s*\\\\}\\\\}')) {\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": "
|
|
4
|
-
"description": "Desktop Outlook automation for Node — read, send, reply, file, and template mail by driving the real Outlook client.",
|
|
3
|
+
"version": "2.0.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
|
-
"
|
|
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.
|
|
48
|
+
"@types/node": "^24.13.3",
|
|
48
49
|
"typescript": "~6.0.3"
|
|
49
50
|
}
|
|
50
51
|
}
|
package/dist/OutlookService.d.ts
DELETED
|
@@ -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) => 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>;
|
package/dist/OutlookService.js
DELETED
|
@@ -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
|