@push.rocks/smartjmap 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.smartconfig.json +56 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/classes.jmapclient.d.ts +236 -0
- package/dist_ts/classes.jmapclient.js +650 -0
- package/dist_ts/classes.jmapserver.d.ts +110 -0
- package/dist_ts/classes.jmapserver.js +873 -0
- package/dist_ts/index.d.ts +2 -0
- package/dist_ts/index.js +3 -0
- package/dist_ts/smartjmap.plugins.d.ts +4 -0
- package/dist_ts/smartjmap.plugins.js +6 -0
- package/license +19 -0
- package/npmextra.json +39 -0
- package/package.json +68 -0
- package/readme.hints.md +34 -0
- package/readme.md +287 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/classes.jmapclient.ts +872 -0
- package/ts/classes.jmapserver.ts +1019 -0
- package/ts/index.ts +2 -0
- package/ts/smartjmap.plugins.ts +10 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
import * as plugins from './smartjmap.plugins.js';
|
|
2
|
+
export const JMAP_CORE_CAPABILITY = 'urn:ietf:params:jmap:core';
|
|
3
|
+
export const JMAP_MAIL_CAPABILITY = 'urn:ietf:params:jmap:mail';
|
|
4
|
+
export const JMAP_SUBMISSION_CAPABILITY = 'urn:ietf:params:jmap:submission';
|
|
5
|
+
/** Error thrown for HTTP-level (RFC 7807 problem details) and JMAP method-level errors. */
|
|
6
|
+
export class JmapError extends Error {
|
|
7
|
+
constructor(message, fields = {}) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'JmapError';
|
|
10
|
+
Object.assign(this, fields);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const EMAIL_PROPERTIES = [
|
|
14
|
+
'id',
|
|
15
|
+
'blobId',
|
|
16
|
+
'threadId',
|
|
17
|
+
'mailboxIds',
|
|
18
|
+
'keywords',
|
|
19
|
+
'size',
|
|
20
|
+
'receivedAt',
|
|
21
|
+
'sentAt',
|
|
22
|
+
'subject',
|
|
23
|
+
'from',
|
|
24
|
+
'to',
|
|
25
|
+
'cc',
|
|
26
|
+
'bcc',
|
|
27
|
+
'replyTo',
|
|
28
|
+
'hasAttachment',
|
|
29
|
+
'preview',
|
|
30
|
+
'bodyValues',
|
|
31
|
+
'textBody',
|
|
32
|
+
'htmlBody',
|
|
33
|
+
'attachments',
|
|
34
|
+
];
|
|
35
|
+
export class JmapClient extends plugins.events.EventEmitter {
|
|
36
|
+
constructor(config) {
|
|
37
|
+
super();
|
|
38
|
+
this.session = null;
|
|
39
|
+
this.accountId = '';
|
|
40
|
+
this.apiUrl = '';
|
|
41
|
+
this.mailbox = null;
|
|
42
|
+
this.connected = false;
|
|
43
|
+
this.stopped = false;
|
|
44
|
+
this.lastEmailState = null;
|
|
45
|
+
this.seenEmailIds = new Set();
|
|
46
|
+
this.pollTimer = null;
|
|
47
|
+
this.sseAbortController = null;
|
|
48
|
+
this.fetchingDeltas = false;
|
|
49
|
+
this.deltaQueued = false;
|
|
50
|
+
const auth = config.auth;
|
|
51
|
+
const hasBearer = typeof auth.accessToken === 'string';
|
|
52
|
+
const hasBasic = typeof auth.user === 'string' && typeof auth.pass === 'string';
|
|
53
|
+
if (!hasBearer && !hasBasic) {
|
|
54
|
+
throw new Error('JmapClient requires auth as either { accessToken } (Bearer) or { user, pass } (Basic).');
|
|
55
|
+
}
|
|
56
|
+
if (hasBearer && (typeof auth.user === 'string' || typeof auth.pass === 'string')) {
|
|
57
|
+
throw new Error('JmapClient auth accepts either { accessToken } or { user, pass }, not both.');
|
|
58
|
+
}
|
|
59
|
+
if (typeof config.sessionUrl !== 'string' || !config.sessionUrl) {
|
|
60
|
+
throw new Error('JmapClient requires a sessionUrl, e.g. https://host/.well-known/jmap');
|
|
61
|
+
}
|
|
62
|
+
this.config = config;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Fetches and validates the JMAP session, resolves the target mailbox,
|
|
66
|
+
* emits 'connected', emits 'message' for every email currently in the
|
|
67
|
+
* mailbox, then watches for new mail via the JMAP event source (SSE),
|
|
68
|
+
* falling back to polling when the event stream is unavailable.
|
|
69
|
+
* Failures are emitted as 'error' events; connect() does not throw.
|
|
70
|
+
*/
|
|
71
|
+
async connect() {
|
|
72
|
+
// Reconnecting on the same instance is supported; release any watchers
|
|
73
|
+
// from a previous connect() so no SSE stream or poll timer is stranded.
|
|
74
|
+
this.teardownWatchers();
|
|
75
|
+
this.stopped = false;
|
|
76
|
+
try {
|
|
77
|
+
const response = await fetch(this.config.sessionUrl, {
|
|
78
|
+
headers: {
|
|
79
|
+
authorization: this.getAuthHeader(),
|
|
80
|
+
accept: 'application/json',
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
throw await this.createHttpError(response, 'Failed to fetch JMAP session');
|
|
85
|
+
}
|
|
86
|
+
const session = (await response.json());
|
|
87
|
+
if (!session.capabilities || !(JMAP_CORE_CAPABILITY in session.capabilities)) {
|
|
88
|
+
throw new Error(`JMAP session is missing the ${JMAP_CORE_CAPABILITY} capability.`);
|
|
89
|
+
}
|
|
90
|
+
if (!(JMAP_MAIL_CAPABILITY in session.capabilities)) {
|
|
91
|
+
throw new Error(`JMAP session is missing the ${JMAP_MAIL_CAPABILITY} capability.`);
|
|
92
|
+
}
|
|
93
|
+
const accountId = session.primaryAccounts?.[JMAP_MAIL_CAPABILITY] ??
|
|
94
|
+
session.primaryAccounts?.[JMAP_CORE_CAPABILITY];
|
|
95
|
+
if (!accountId) {
|
|
96
|
+
throw new Error('JMAP session has no primary account for urn:ietf:params:jmap:mail.');
|
|
97
|
+
}
|
|
98
|
+
this.session = session;
|
|
99
|
+
this.accountId = accountId;
|
|
100
|
+
this.apiUrl = new URL(session.apiUrl, this.config.sessionUrl).toString();
|
|
101
|
+
this.mailbox = await this.resolveMailbox();
|
|
102
|
+
// Capture the Email state before the initial sweep so no change is lost
|
|
103
|
+
// between the sweep and the start of watching (dedupe handles overlap).
|
|
104
|
+
this.lastEmailState = await this.fetchEmailState();
|
|
105
|
+
if (this.stopped) {
|
|
106
|
+
// disconnect() was called while connect() was in flight.
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
this.connected = true;
|
|
110
|
+
this.emit('connected');
|
|
111
|
+
await this.emitInitialEmails();
|
|
112
|
+
this.startWatching();
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
// Clean up anything that may have been started; this instance holds no
|
|
116
|
+
// sockets or timers after a failed connect.
|
|
117
|
+
this.teardownWatchers();
|
|
118
|
+
this.connected = false;
|
|
119
|
+
this.emit('error', error);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Stops watching (event stream and/or polling timer) and emits
|
|
124
|
+
* 'disconnected' when the client was connected. Leaves no open handles.
|
|
125
|
+
*/
|
|
126
|
+
async disconnect() {
|
|
127
|
+
this.stopped = true;
|
|
128
|
+
this.teardownWatchers();
|
|
129
|
+
if (this.connected) {
|
|
130
|
+
this.connected = false;
|
|
131
|
+
this.emit('disconnected');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Core JMAP method-call helper (RFC 8620 §3.2): POSTs { using, methodCalls }
|
|
136
|
+
* to the session's apiUrl and returns the methodResponses. HTTP problem
|
|
137
|
+
* details (RFC 7807) and JMAP `error` method responses are thrown as
|
|
138
|
+
* JmapError with `httpStatus`/`problemType`/`jmapErrorType` fields.
|
|
139
|
+
*/
|
|
140
|
+
async request(methodCalls) {
|
|
141
|
+
if (!this.apiUrl) {
|
|
142
|
+
throw new Error('JmapClient is not connected yet — call connect() first.');
|
|
143
|
+
}
|
|
144
|
+
const response = await fetch(this.apiUrl, {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
headers: {
|
|
147
|
+
authorization: this.getAuthHeader(),
|
|
148
|
+
'content-type': 'application/json',
|
|
149
|
+
accept: 'application/json',
|
|
150
|
+
},
|
|
151
|
+
body: JSON.stringify({
|
|
152
|
+
using: [JMAP_CORE_CAPABILITY, JMAP_MAIL_CAPABILITY, JMAP_SUBMISSION_CAPABILITY],
|
|
153
|
+
methodCalls,
|
|
154
|
+
}),
|
|
155
|
+
});
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
throw await this.createHttpError(response, 'JMAP API request failed');
|
|
158
|
+
}
|
|
159
|
+
const parsed = (await response.json());
|
|
160
|
+
const methodResponses = parsed.methodResponses ?? [];
|
|
161
|
+
for (const [name, args, callId] of methodResponses) {
|
|
162
|
+
if (name === 'error') {
|
|
163
|
+
throw new JmapError(`JMAP method error "${args?.type}"${args?.description ? `: ${args.description}` : ''} (call ${callId})`, {
|
|
164
|
+
jmapErrorType: args?.type,
|
|
165
|
+
jmapErrorDescription: args?.description,
|
|
166
|
+
jmapCallId: callId,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return methodResponses;
|
|
171
|
+
}
|
|
172
|
+
/** Returns all mailboxes of the account (Mailbox/get with ids: null). */
|
|
173
|
+
async getMailboxes() {
|
|
174
|
+
const responses = await this.request([
|
|
175
|
+
['Mailbox/get', { accountId: this.accountId, ids: null }, 'mb0'],
|
|
176
|
+
]);
|
|
177
|
+
return (responses[0][1].list ?? []);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Runs Email/query (default filter: the watched mailbox) followed by a full
|
|
181
|
+
* Email/get and returns resolved messages, newest first.
|
|
182
|
+
*/
|
|
183
|
+
async queryEmails(filter, limit) {
|
|
184
|
+
const effectiveFilter = filter ?? (this.mailbox ? { inMailbox: this.mailbox.id } : {});
|
|
185
|
+
const queryArgs = {
|
|
186
|
+
accountId: this.accountId,
|
|
187
|
+
filter: effectiveFilter,
|
|
188
|
+
sort: [{ property: 'receivedAt', isAscending: false }],
|
|
189
|
+
};
|
|
190
|
+
if (typeof limit === 'number') {
|
|
191
|
+
queryArgs.limit = limit;
|
|
192
|
+
}
|
|
193
|
+
const responses = await this.request([['Email/query', queryArgs, 'q0']]);
|
|
194
|
+
const ids = responses[0][1].ids ?? [];
|
|
195
|
+
if (!ids.length) {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
const emails = await this.getEmailObjects(ids);
|
|
199
|
+
return emails.map((email) => this.toMessage(email));
|
|
200
|
+
}
|
|
201
|
+
/** Fetches a single email with all body values resolved (RFC 8621 §4.2, fetchAllBodyValues). */
|
|
202
|
+
async getEmail(id) {
|
|
203
|
+
const [email] = await this.getEmailObjects([id]);
|
|
204
|
+
if (!email) {
|
|
205
|
+
throw new Error(`Email "${id}" not found.`);
|
|
206
|
+
}
|
|
207
|
+
return this.toMessage(email);
|
|
208
|
+
}
|
|
209
|
+
/** Replaces the full keywords object of an email via Email/set. */
|
|
210
|
+
async setKeywords(emailId, keywords) {
|
|
211
|
+
await this.updateEmail(emailId, { keywords });
|
|
212
|
+
}
|
|
213
|
+
/** Convenience: sets the $seen keyword via an Email/set keywords patch. */
|
|
214
|
+
async markSeen(emailId) {
|
|
215
|
+
await this.updateEmail(emailId, { 'keywords/$seen': true });
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Sends an email: Identity/get lookup, Email/set create (into the mailbox
|
|
219
|
+
* with role 'sent', falling back to 'drafts', then the watched mailbox) and
|
|
220
|
+
* EmailSubmission/set referencing the created email.
|
|
221
|
+
*/
|
|
222
|
+
async sendEmail(outgoing) {
|
|
223
|
+
const identityResponses = await this.request([
|
|
224
|
+
['Identity/get', { accountId: this.accountId }, 'i0'],
|
|
225
|
+
]);
|
|
226
|
+
const identities = identityResponses[0][1].list ?? [];
|
|
227
|
+
const identity = (outgoing.from
|
|
228
|
+
? identities.find((candidate) => candidate.email === outgoing.from.email)
|
|
229
|
+
: undefined) ?? identities[0];
|
|
230
|
+
if (!identity) {
|
|
231
|
+
throw new Error('No JMAP identity available for sending.');
|
|
232
|
+
}
|
|
233
|
+
const from = outgoing.from ?? { name: identity.name ?? null, email: identity.email };
|
|
234
|
+
const mailboxes = await this.getMailboxes();
|
|
235
|
+
const targetMailbox = mailboxes.find((mb) => mb.role === 'sent') ??
|
|
236
|
+
mailboxes.find((mb) => mb.role === 'drafts') ??
|
|
237
|
+
this.mailbox;
|
|
238
|
+
if (!targetMailbox) {
|
|
239
|
+
throw new Error('No mailbox available to store the outgoing email in.');
|
|
240
|
+
}
|
|
241
|
+
const bodyValues = {};
|
|
242
|
+
const textBodyParts = [];
|
|
243
|
+
const htmlBodyParts = [];
|
|
244
|
+
if (typeof outgoing.textBody === 'string') {
|
|
245
|
+
bodyValues.text = { value: outgoing.textBody };
|
|
246
|
+
textBodyParts.push({ partId: 'text', type: 'text/plain' });
|
|
247
|
+
}
|
|
248
|
+
if (typeof outgoing.htmlBody === 'string') {
|
|
249
|
+
bodyValues.html = { value: outgoing.htmlBody };
|
|
250
|
+
htmlBodyParts.push({ partId: 'html', type: 'text/html' });
|
|
251
|
+
}
|
|
252
|
+
if (!textBodyParts.length && !htmlBodyParts.length) {
|
|
253
|
+
throw new Error('sendEmail requires textBody and/or htmlBody.');
|
|
254
|
+
}
|
|
255
|
+
const emailCreate = {
|
|
256
|
+
mailboxIds: { [targetMailbox.id]: true },
|
|
257
|
+
keywords: { $seen: true },
|
|
258
|
+
from: [from],
|
|
259
|
+
to: outgoing.to,
|
|
260
|
+
subject: outgoing.subject,
|
|
261
|
+
bodyValues,
|
|
262
|
+
};
|
|
263
|
+
if (outgoing.cc) {
|
|
264
|
+
emailCreate.cc = outgoing.cc;
|
|
265
|
+
}
|
|
266
|
+
if (outgoing.bcc) {
|
|
267
|
+
emailCreate.bcc = outgoing.bcc;
|
|
268
|
+
}
|
|
269
|
+
if (textBodyParts.length) {
|
|
270
|
+
emailCreate.textBody = textBodyParts;
|
|
271
|
+
}
|
|
272
|
+
if (htmlBodyParts.length) {
|
|
273
|
+
emailCreate.htmlBody = htmlBodyParts;
|
|
274
|
+
}
|
|
275
|
+
const responses = await this.request([
|
|
276
|
+
['Email/set', { accountId: this.accountId, create: { newEmail: emailCreate } }, 'c0'],
|
|
277
|
+
[
|
|
278
|
+
'EmailSubmission/set',
|
|
279
|
+
{
|
|
280
|
+
accountId: this.accountId,
|
|
281
|
+
create: { newSubmission: { emailId: '#newEmail', identityId: identity.id } },
|
|
282
|
+
},
|
|
283
|
+
'c1',
|
|
284
|
+
],
|
|
285
|
+
]);
|
|
286
|
+
const emailSetResult = responses.find((entry) => entry[2] === 'c0')?.[1] ?? {};
|
|
287
|
+
const createdEmail = emailSetResult.created?.newEmail;
|
|
288
|
+
if (!createdEmail) {
|
|
289
|
+
const notCreated = emailSetResult.notCreated?.newEmail;
|
|
290
|
+
throw new JmapError(`Email/set create failed${notCreated ? `: ${notCreated.type}${notCreated.description ? ` — ${notCreated.description}` : ''}` : ''}`, { jmapErrorType: notCreated?.type, jmapErrorDescription: notCreated?.description });
|
|
291
|
+
}
|
|
292
|
+
const submissionSetResult = responses.find((entry) => entry[2] === 'c1')?.[1] ?? {};
|
|
293
|
+
const createdSubmission = submissionSetResult.created?.newSubmission;
|
|
294
|
+
if (!createdSubmission) {
|
|
295
|
+
const notCreated = submissionSetResult.notCreated?.newSubmission;
|
|
296
|
+
throw new JmapError(`EmailSubmission/set create failed${notCreated ? `: ${notCreated.type}${notCreated.description ? ` — ${notCreated.description}` : ''}` : ''}`, { jmapErrorType: notCreated?.type, jmapErrorDescription: notCreated?.description });
|
|
297
|
+
}
|
|
298
|
+
return { emailId: createdEmail.id, submissionId: createdSubmission.id };
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Downloads a blob's raw bytes using the session downloadUrl template
|
|
302
|
+
* (RFC 8620 §6.2: {accountId}, {blobId}, {type}, {name}).
|
|
303
|
+
*/
|
|
304
|
+
async downloadBlob(blobId, type = 'application/octet-stream', name = 'blob') {
|
|
305
|
+
const template = this.session?.downloadUrl;
|
|
306
|
+
if (!template) {
|
|
307
|
+
throw new Error('JMAP session provides no downloadUrl — call connect() first.');
|
|
308
|
+
}
|
|
309
|
+
const expanded = template
|
|
310
|
+
.replace('{accountId}', encodeURIComponent(this.accountId))
|
|
311
|
+
.replace('{blobId}', encodeURIComponent(blobId))
|
|
312
|
+
.replace('{type}', encodeURIComponent(type))
|
|
313
|
+
.replace('{name}', encodeURIComponent(name));
|
|
314
|
+
const url = new URL(expanded, this.config.sessionUrl).toString();
|
|
315
|
+
const response = await fetch(url, {
|
|
316
|
+
headers: { authorization: this.getAuthHeader() },
|
|
317
|
+
});
|
|
318
|
+
if (!response.ok) {
|
|
319
|
+
throw await this.createHttpError(response, `Failed to download blob "${blobId}"`);
|
|
320
|
+
}
|
|
321
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
322
|
+
}
|
|
323
|
+
// ======================
|
|
324
|
+
// internal helpers
|
|
325
|
+
// ======================
|
|
326
|
+
getAuthHeader() {
|
|
327
|
+
const auth = this.config.auth;
|
|
328
|
+
if (typeof auth.accessToken === 'string') {
|
|
329
|
+
return `Bearer ${auth.accessToken}`;
|
|
330
|
+
}
|
|
331
|
+
return `Basic ${Buffer.from(`${auth.user}:${auth.pass}`).toString('base64')}`;
|
|
332
|
+
}
|
|
333
|
+
async createHttpError(response, messagePrefix) {
|
|
334
|
+
let problemType;
|
|
335
|
+
let problemDetail;
|
|
336
|
+
try {
|
|
337
|
+
const body = (await response.json());
|
|
338
|
+
if (body && typeof body === 'object') {
|
|
339
|
+
problemType = typeof body.type === 'string' ? body.type : undefined;
|
|
340
|
+
problemDetail = typeof body.detail === 'string' ? body.detail : undefined;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
// non-JSON error body
|
|
345
|
+
}
|
|
346
|
+
return new JmapError(`${messagePrefix}: HTTP ${response.status}${problemDetail ? ` — ${problemDetail}` : ''}${problemType ? ` (${problemType})` : ''}`, { httpStatus: response.status, problemType, problemDetail });
|
|
347
|
+
}
|
|
348
|
+
async resolveMailbox() {
|
|
349
|
+
const responses = await this.request([
|
|
350
|
+
['Mailbox/get', { accountId: this.accountId, ids: null }, 'mb0'],
|
|
351
|
+
]);
|
|
352
|
+
const list = (responses[0][1].list ?? []);
|
|
353
|
+
const wanted = this.config.mailbox ?? 'INBOX';
|
|
354
|
+
const wantedLower = wanted.toLowerCase();
|
|
355
|
+
const byRole = list.find((mb) => (mb.role ?? '').toLowerCase() === wantedLower);
|
|
356
|
+
const byName = list.find((mb) => mb.name.toLowerCase() === wantedLower);
|
|
357
|
+
const mailbox = byRole ?? byName;
|
|
358
|
+
if (!mailbox) {
|
|
359
|
+
throw new Error(`JMAP mailbox "${wanted}" not found (no role or name match).`);
|
|
360
|
+
}
|
|
361
|
+
return mailbox;
|
|
362
|
+
}
|
|
363
|
+
async fetchEmailState() {
|
|
364
|
+
const responses = await this.request([
|
|
365
|
+
['Email/get', { accountId: this.accountId, ids: [] }, 'st0'],
|
|
366
|
+
]);
|
|
367
|
+
return String(responses[0][1].state ?? '');
|
|
368
|
+
}
|
|
369
|
+
async getEmailObjects(ids) {
|
|
370
|
+
const responses = await this.request([
|
|
371
|
+
[
|
|
372
|
+
'Email/get',
|
|
373
|
+
{
|
|
374
|
+
accountId: this.accountId,
|
|
375
|
+
ids,
|
|
376
|
+
properties: EMAIL_PROPERTIES,
|
|
377
|
+
fetchAllBodyValues: true,
|
|
378
|
+
},
|
|
379
|
+
'g0',
|
|
380
|
+
],
|
|
381
|
+
]);
|
|
382
|
+
return (responses[0][1].list ?? []);
|
|
383
|
+
}
|
|
384
|
+
async updateEmail(emailId, patch) {
|
|
385
|
+
const responses = await this.request([
|
|
386
|
+
['Email/set', { accountId: this.accountId, update: { [emailId]: patch } }, 'u0'],
|
|
387
|
+
]);
|
|
388
|
+
const result = responses[0][1];
|
|
389
|
+
const notUpdated = result.notUpdated?.[emailId];
|
|
390
|
+
if (notUpdated) {
|
|
391
|
+
throw new JmapError(`Email/set update of "${emailId}" failed: ${notUpdated.type}${notUpdated.description ? ` — ${notUpdated.description}` : ''}`, { jmapErrorType: notUpdated.type, jmapErrorDescription: notUpdated.description });
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
toMessage(email) {
|
|
395
|
+
const bodyValues = email.bodyValues ?? {};
|
|
396
|
+
const resolveParts = (parts) => parts
|
|
397
|
+
.map((part) => (part.partId != null ? bodyValues[part.partId]?.value : undefined))
|
|
398
|
+
.filter((value) => typeof value === 'string')
|
|
399
|
+
.join('\n');
|
|
400
|
+
const htmlParts = (email.htmlBody ?? []).filter((part) => part.type === 'text/html');
|
|
401
|
+
const attachments = (email.attachments ?? [])
|
|
402
|
+
.filter((part) => typeof part.blobId === 'string')
|
|
403
|
+
.map((part) => ({
|
|
404
|
+
blobId: part.blobId,
|
|
405
|
+
name: part.name ?? undefined,
|
|
406
|
+
type: part.type,
|
|
407
|
+
size: part.size,
|
|
408
|
+
}));
|
|
409
|
+
return {
|
|
410
|
+
id: email.id,
|
|
411
|
+
blobId: email.blobId,
|
|
412
|
+
threadId: email.threadId,
|
|
413
|
+
mailboxIds: email.mailboxIds ?? {},
|
|
414
|
+
keywords: email.keywords ?? {},
|
|
415
|
+
from: email.from ?? [],
|
|
416
|
+
to: email.to ?? [],
|
|
417
|
+
cc: email.cc ?? [],
|
|
418
|
+
bcc: email.bcc ?? [],
|
|
419
|
+
replyTo: email.replyTo ?? [],
|
|
420
|
+
subject: email.subject ?? undefined,
|
|
421
|
+
receivedAt: email.receivedAt,
|
|
422
|
+
textBody: resolveParts(email.textBody ?? []),
|
|
423
|
+
htmlBody: htmlParts.length ? resolveParts(htmlParts) : undefined,
|
|
424
|
+
attachments,
|
|
425
|
+
raw: email,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
emitMessage(message) {
|
|
429
|
+
if (this.seenEmailIds.has(message.id)) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
this.seenEmailIds.add(message.id);
|
|
433
|
+
this.emit('message', message);
|
|
434
|
+
}
|
|
435
|
+
async emitInitialEmails() {
|
|
436
|
+
const messages = await this.queryEmails();
|
|
437
|
+
// queryEmails sorts newest first; emit oldest first for natural ordering
|
|
438
|
+
for (const message of messages.reverse()) {
|
|
439
|
+
this.emitMessage(message);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
startWatching() {
|
|
443
|
+
if (this.stopped) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
const eventSourceUrl = this.session?.eventSourceUrl;
|
|
447
|
+
if (eventSourceUrl) {
|
|
448
|
+
this.startEventStream(eventSourceUrl);
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
this.startPolling();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Watches the JMAP event source (RFC 8620 §7.3) by parsing SSE frames from
|
|
456
|
+
* a fetch body stream. A hand-rolled parser is used instead of the global
|
|
457
|
+
* EventSource because JMAP requires an Authorization header, which the
|
|
458
|
+
* WHATWG EventSource API cannot send.
|
|
459
|
+
*/
|
|
460
|
+
startEventStream(templateUrl) {
|
|
461
|
+
const expanded = templateUrl
|
|
462
|
+
.replace('{types}', '*')
|
|
463
|
+
.replace('{closeafter}', 'no')
|
|
464
|
+
.replace('{ping}', '30');
|
|
465
|
+
const url = new URL(expanded, this.config.sessionUrl).toString();
|
|
466
|
+
// Last stream wins under any interleaving: abort a previous stream before
|
|
467
|
+
// installing the new controller.
|
|
468
|
+
if (this.sseAbortController) {
|
|
469
|
+
this.sseAbortController.abort();
|
|
470
|
+
}
|
|
471
|
+
const abortController = new AbortController();
|
|
472
|
+
this.sseAbortController = abortController;
|
|
473
|
+
void this.runEventStream(url, abortController);
|
|
474
|
+
}
|
|
475
|
+
async runEventStream(url, abortController) {
|
|
476
|
+
try {
|
|
477
|
+
const response = await fetch(url, {
|
|
478
|
+
headers: {
|
|
479
|
+
authorization: this.getAuthHeader(),
|
|
480
|
+
accept: 'text/event-stream',
|
|
481
|
+
},
|
|
482
|
+
signal: abortController.signal,
|
|
483
|
+
});
|
|
484
|
+
if (!response.ok || !response.body) {
|
|
485
|
+
throw new Error(`JMAP event source unavailable (HTTP ${response.status}).`);
|
|
486
|
+
}
|
|
487
|
+
// The stream is live; catch up on anything that changed between the
|
|
488
|
+
// initial state snapshot and the stream being established.
|
|
489
|
+
void this.fetchDeltas();
|
|
490
|
+
const reader = response.body.getReader();
|
|
491
|
+
const decoder = new TextDecoder();
|
|
492
|
+
let buffer = '';
|
|
493
|
+
let eventName = 'message';
|
|
494
|
+
let dataLines = [];
|
|
495
|
+
while (true) {
|
|
496
|
+
const { done, value } = await reader.read();
|
|
497
|
+
if (done) {
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
buffer += decoder.decode(value, { stream: true });
|
|
501
|
+
let newlineIndex;
|
|
502
|
+
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
|
|
503
|
+
const line = buffer.slice(0, newlineIndex).replace(/\r$/, '');
|
|
504
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
505
|
+
if (line === '') {
|
|
506
|
+
if (dataLines.length) {
|
|
507
|
+
this.handleEventStreamEvent(eventName, dataLines.join('\n'));
|
|
508
|
+
}
|
|
509
|
+
eventName = 'message';
|
|
510
|
+
dataLines = [];
|
|
511
|
+
}
|
|
512
|
+
else if (line.startsWith(':')) {
|
|
513
|
+
// comment / keepalive
|
|
514
|
+
}
|
|
515
|
+
else if (line.startsWith('event:')) {
|
|
516
|
+
eventName = line.slice(6).trim();
|
|
517
|
+
}
|
|
518
|
+
else if (line.startsWith('data:')) {
|
|
519
|
+
dataLines.push(line.slice(5).replace(/^ /, ''));
|
|
520
|
+
}
|
|
521
|
+
// id: and retry: fields are irrelevant here
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
// Stream ended server-side: fall back to polling (mirroring the catch
|
|
525
|
+
// guards so a superseded stream's continuation stays inert).
|
|
526
|
+
if (!this.stopped && !abortController.signal.aborted) {
|
|
527
|
+
if (this.sseAbortController === abortController) {
|
|
528
|
+
this.sseAbortController = null;
|
|
529
|
+
}
|
|
530
|
+
this.startPolling();
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
if (this.stopped || abortController.signal.aborted) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
// Event stream unavailable or broken: abort so any held response body
|
|
538
|
+
// (e.g. an unconsumed non-OK response) releases its connection, then
|
|
539
|
+
// fall back to polling.
|
|
540
|
+
abortController.abort();
|
|
541
|
+
if (this.sseAbortController === abortController) {
|
|
542
|
+
this.sseAbortController = null;
|
|
543
|
+
}
|
|
544
|
+
this.startPolling();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
handleEventStreamEvent(eventName, data) {
|
|
548
|
+
let parsed;
|
|
549
|
+
try {
|
|
550
|
+
parsed = JSON.parse(data);
|
|
551
|
+
}
|
|
552
|
+
catch {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (eventName !== 'state' && parsed?.['@type'] !== 'StateChange') {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const changed = parsed?.changed?.[this.accountId];
|
|
559
|
+
const emailState = changed?.Email;
|
|
560
|
+
if (typeof emailState === 'string' && emailState !== this.lastEmailState) {
|
|
561
|
+
void this.fetchDeltas();
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
startPolling() {
|
|
565
|
+
if (this.pollTimer || this.stopped) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
const interval = this.config.pollIntervalMs ?? 30000;
|
|
569
|
+
this.pollTimer = setInterval(() => {
|
|
570
|
+
void this.fetchDeltas();
|
|
571
|
+
}, interval);
|
|
572
|
+
}
|
|
573
|
+
async fetchDeltas() {
|
|
574
|
+
if (this.stopped) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (this.fetchingDeltas) {
|
|
578
|
+
this.deltaQueued = true;
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
this.fetchingDeltas = true;
|
|
582
|
+
try {
|
|
583
|
+
do {
|
|
584
|
+
this.deltaQueued = false;
|
|
585
|
+
await this.fetchDeltasOnce();
|
|
586
|
+
} while (this.deltaQueued && !this.stopped);
|
|
587
|
+
}
|
|
588
|
+
finally {
|
|
589
|
+
this.fetchingDeltas = false;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
async fetchDeltasOnce() {
|
|
593
|
+
try {
|
|
594
|
+
let hasMore = true;
|
|
595
|
+
while (hasMore && !this.stopped) {
|
|
596
|
+
const sinceState = this.lastEmailState;
|
|
597
|
+
const responses = await this.request([
|
|
598
|
+
['Email/changes', { accountId: this.accountId, sinceState }, 'ch0'],
|
|
599
|
+
]);
|
|
600
|
+
const changes = responses[0][1];
|
|
601
|
+
this.lastEmailState = String(changes.newState ?? sinceState);
|
|
602
|
+
hasMore = changes.hasMoreChanges === true && this.lastEmailState !== sinceState;
|
|
603
|
+
const createdIds = changes.created ?? [];
|
|
604
|
+
if (createdIds.length) {
|
|
605
|
+
const emails = await this.getEmailObjects(createdIds);
|
|
606
|
+
for (const email of emails) {
|
|
607
|
+
if (this.mailbox && email.mailboxIds?.[this.mailbox.id]) {
|
|
608
|
+
this.emitMessage(this.toMessage(email));
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
catch (error) {
|
|
615
|
+
if (this.stopped) {
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (error?.jmapErrorType === 'cannotCalculateChanges') {
|
|
619
|
+
try {
|
|
620
|
+
await this.resyncViaQuery();
|
|
621
|
+
}
|
|
622
|
+
catch (resyncError) {
|
|
623
|
+
this.emit('error', resyncError);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
this.emit('error', error);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
/** Full resync fallback when the server cannot calculate changes. */
|
|
632
|
+
async resyncViaQuery() {
|
|
633
|
+
this.lastEmailState = await this.fetchEmailState();
|
|
634
|
+
const messages = await this.queryEmails();
|
|
635
|
+
for (const message of messages.reverse()) {
|
|
636
|
+
this.emitMessage(message);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
teardownWatchers() {
|
|
640
|
+
if (this.sseAbortController) {
|
|
641
|
+
this.sseAbortController.abort();
|
|
642
|
+
this.sseAbortController = null;
|
|
643
|
+
}
|
|
644
|
+
if (this.pollTimer) {
|
|
645
|
+
clearInterval(this.pollTimer);
|
|
646
|
+
this.pollTimer = null;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5qbWFwY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5qbWFwY2xpZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sd0JBQXdCLENBQUM7QUFFbEQsTUFBTSxDQUFDLE1BQU0sb0JBQW9CLEdBQUcsMkJBQTJCLENBQUM7QUFDaEUsTUFBTSxDQUFDLE1BQU0sb0JBQW9CLEdBQUcsMkJBQTJCLENBQUM7QUFDaEUsTUFBTSxDQUFDLE1BQU0sMEJBQTBCLEdBQUcsaUNBQWlDLENBQUM7QUEwSjVFLDJGQUEyRjtBQUMzRixNQUFNLE9BQU8sU0FBVSxTQUFRLEtBQUs7SUFRbEMsWUFBWSxPQUFlLEVBQUUsU0FBNkIsRUFBRTtRQUMxRCxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDZixJQUFJLENBQUMsSUFBSSxHQUFHLFdBQVcsQ0FBQztRQUN4QixNQUFNLENBQUMsTUFBTSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsQ0FBQztJQUM5QixDQUFDO0NBQ0Y7QUFFRCxNQUFNLGdCQUFnQixHQUFHO0lBQ3ZCLElBQUk7SUFDSixRQUFRO0lBQ1IsVUFBVTtJQUNWLFlBQVk7SUFDWixVQUFVO0lBQ1YsTUFBTTtJQUNOLFlBQVk7SUFDWixRQUFRO0lBQ1IsU0FBUztJQUNULE1BQU07SUFDTixJQUFJO0lBQ0osSUFBSTtJQUNKLEtBQUs7SUFDTCxTQUFTO0lBQ1QsZUFBZTtJQUNmLFNBQVM7SUFDVCxZQUFZO0lBQ1osVUFBVTtJQUNWLFVBQVU7SUFDVixhQUFhO0NBQ2QsQ0FBQztBQUVGLE1BQU0sT0FBTyxVQUFXLFNBQVEsT0FBTyxDQUFDLE1BQU0sQ0FBQyxZQUFZO0lBZXpELFlBQVksTUFBeUI7UUFDbkMsS0FBSyxFQUFFLENBQUM7UUFkRixZQUFPLEdBQXdCLElBQUksQ0FBQztRQUNwQyxjQUFTLEdBQVcsRUFBRSxDQUFDO1FBQ3ZCLFdBQU0sR0FBVyxFQUFF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
|