@remit/mailbox-service 0.0.112 → 0.0.113
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/dist/account-credentials.d.ts +83 -0
- package/dist/account-credentials.js +104 -0
- package/dist/attribute-mapper.d.ts +36 -0
- package/dist/attribute-mapper.js +119 -0
- package/dist/body-parse.d.ts +28 -0
- package/{src/body-parse.ts → dist/body-parse.js} +18 -26
- package/dist/body-part-mapper.d.ts +92 -0
- package/dist/body-part-mapper.js +301 -0
- package/dist/body-sync-queue.d.ts +59 -0
- package/dist/body-sync-queue.js +56 -0
- package/dist/body-sync-terminal.d.ts +58 -0
- package/dist/body-sync-terminal.js +95 -0
- package/dist/body-sync.d.ts +351 -0
- package/dist/body-sync.js +1204 -0
- package/dist/calendar-parts.d.ts +23 -0
- package/dist/calendar-parts.js +18 -0
- package/dist/classification-backfill.d.ts +91 -0
- package/dist/classification-backfill.js +180 -0
- package/dist/classify-message.d.ts +31 -0
- package/dist/classify-message.js +89 -0
- package/dist/connection-factory.d.ts +54 -0
- package/dist/connection-factory.js +54 -0
- package/dist/connection-test.d.ts +41 -0
- package/dist/connection-test.js +152 -0
- package/dist/external-move.d.ts +94 -0
- package/dist/external-move.js +173 -0
- package/dist/filter-operations.d.ts +42 -0
- package/dist/filter-operations.js +211 -0
- package/dist/filters/filter-config.d.ts +14 -0
- package/dist/filters/filter-config.js +10 -0
- package/{src/filters/list-id.ts → dist/filters/list-id.d.ts} +2 -16
- package/dist/filters/list-id.js +26 -0
- package/dist/filters/match.d.ts +69 -0
- package/dist/filters/match.js +165 -0
- package/dist/filters/pipeline.d.ts +64 -0
- package/dist/filters/pipeline.js +100 -0
- package/{src/flag-push-terminal.ts → dist/flag-push-terminal.d.ts} +16 -72
- package/dist/flag-push-terminal.js +63 -0
- package/dist/flag-push.d.ts +69 -0
- package/dist/flag-push.js +111 -0
- package/dist/flag-queue.d.ts +175 -0
- package/dist/flag-queue.js +291 -0
- package/dist/folder-operations.d.ts +39 -0
- package/dist/folder-operations.js +112 -0
- package/dist/folder-placement.d.ts +13 -0
- package/dist/folder-placement.js +23 -0
- package/dist/heuristics/authenticationResults.d.ts +10 -0
- package/dist/heuristics/authenticationResults.js +187 -0
- package/dist/heuristics/classifyByHeaders.d.ts +82 -0
- package/dist/heuristics/classifyByHeaders.js +365 -0
- package/dist/heuristics/classifyPlacement.d.ts +67 -0
- package/dist/heuristics/classifyPlacement.js +153 -0
- package/dist/heuristics/machineSenders.d.ts +21 -0
- package/{src/heuristics/machineSenders.ts → dist/heuristics/machineSenders.js} +34 -40
- package/dist/heuristics/senderMismatch.d.ts +81 -0
- package/dist/heuristics/senderMismatch.js +192 -0
- package/dist/heuristics/socialDomains.d.ts +14 -0
- package/{src/heuristics/socialDomains.ts → dist/heuristics/socialDomains.js} +9 -9
- package/dist/heuristics/transactionalDomains.d.ts +18 -0
- package/{src/heuristics/transactionalDomains.ts → dist/heuristics/transactionalDomains.js} +8 -8
- package/dist/imapflow-connection.d.ts +377 -0
- package/dist/imapflow-connection.js +1147 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +65 -0
- package/dist/list-id-backfill.d.ts +83 -0
- package/dist/list-id-backfill.js +132 -0
- package/dist/mailbox-cursor-rebuild.d.ts +74 -0
- package/dist/mailbox-cursor-rebuild.js +71 -0
- package/{src/mailbox-cursor.ts → dist/mailbox-cursor.d.ts} +20 -98
- package/dist/mailbox-cursor.js +102 -0
- package/dist/mailbox-intent.d.ts +41 -0
- package/{src/mailbox-intent.ts → dist/mailbox-intent.js} +38 -64
- package/dist/mailbox-management.d.ts +240 -0
- package/dist/mailbox-management.js +576 -0
- package/dist/mailbox-presence.d.ts +52 -0
- package/{src/mailbox-presence.ts → dist/mailbox-presence.js} +19 -30
- package/dist/mailbox-queue.d.ts +126 -0
- package/dist/mailbox-queue.js +214 -0
- package/dist/mailbox-sync.d.ts +93 -0
- package/dist/mailbox-sync.js +490 -0
- package/dist/mailbox-upstream.d.ts +38 -0
- package/{src/mailbox-upstream.ts → dist/mailbox-upstream.js} +37 -47
- package/dist/message-category.d.ts +51 -0
- package/{src/message-category.ts → dist/message-category.js} +28 -71
- package/dist/message-move.d.ts +285 -0
- package/dist/message-move.js +858 -0
- package/dist/message-parser.d.ts +14 -0
- package/dist/message-parser.js +17 -0
- package/{src/message-presence.ts → dist/message-presence.d.ts} +1 -13
- package/dist/message-presence.js +29 -0
- package/dist/message-read.d.ts +51 -0
- package/dist/message-read.js +31 -0
- package/dist/message-sync.d.ts +345 -0
- package/dist/message-sync.js +1358 -0
- package/dist/mime-walker.d.ts +102 -0
- package/dist/mime-walker.js +198 -0
- package/dist/organize-job.d.ts +20 -0
- package/dist/organize-job.js +37 -0
- package/dist/outbox-attachment-content.d.ts +37 -0
- package/dist/outbox-attachment-content.js +50 -0
- package/dist/outbox-attachment-filename.d.ts +19 -0
- package/dist/outbox-attachment-filename.js +64 -0
- package/dist/outbox-attachment.d.ts +148 -0
- package/dist/outbox-attachment.js +273 -0
- package/dist/outbox-queue.d.ts +73 -0
- package/dist/outbox-queue.js +284 -0
- package/{src/outbox-status.ts → dist/outbox-status.d.ts} +1 -6
- package/dist/outbox-status.js +18 -0
- package/dist/ownership.d.ts +24 -0
- package/dist/ownership.js +83 -0
- package/dist/placement-move-terminal.d.ts +67 -0
- package/dist/placement-move-terminal.js +88 -0
- package/dist/placement-move.d.ts +83 -0
- package/dist/placement-move.js +216 -0
- package/{src/placement-settled.ts → dist/placement-settled.d.ts} +13 -68
- package/dist/placement-settled.js +75 -0
- package/dist/quarantine.d.ts +95 -0
- package/dist/quarantine.js +131 -0
- package/dist/refusal.d.ts +31 -0
- package/dist/refusal.js +3 -0
- package/{src/restore-source-placement.ts → dist/restore-source-placement.d.ts} +29 -97
- package/dist/restore-source-placement.js +79 -0
- package/dist/search-text-backfill.d.ts +15 -0
- package/dist/search-text-backfill.js +33 -0
- package/dist/snippet.d.ts +61 -0
- package/dist/snippet.js +129 -0
- package/dist/spam-report.d.ts +86 -0
- package/dist/spam-report.js +241 -0
- package/dist/sync-warning.d.ts +21 -0
- package/dist/sync-warning.js +26 -0
- package/dist/sync-watermarks.d.ts +138 -0
- package/dist/sync-watermarks.js +205 -0
- package/dist/test-helpers/fake-imap.d.ts +151 -0
- package/dist/test-helpers/fake-imap.js +508 -0
- package/dist/test-helpers/folder-roles.d.ts +34 -0
- package/dist/test-helpers/folder-roles.js +47 -0
- package/dist/test-helpers/mime-tree-generator.d.ts +82 -0
- package/dist/test-helpers/mime-tree-generator.js +329 -0
- package/dist/test-helpers/test-store.d.ts +69 -0
- package/dist/test-helpers/test-store.js +193 -0
- package/dist/text/normalizer.d.ts +20 -0
- package/dist/text/normalizer.js +102 -0
- package/dist/thread-message-rows.d.ts +69 -0
- package/dist/thread-message-rows.js +40 -0
- package/dist/thread-root.d.ts +7 -0
- package/dist/thread-root.js +25 -0
- package/dist/types.d.ts +343 -0
- package/dist/types.js +19 -0
- package/package.json +86 -88
- package/src/account-credentials.ts +0 -210
- package/src/attribute-mapper.test.ts +0 -104
- package/src/attribute-mapper.ts +0 -148
- package/src/body-parse.test.ts +0 -55
- package/src/body-part-mapper.property.test.ts +0 -354
- package/src/body-part-mapper.ts +0 -410
- package/src/body-sync-queue.test.ts +0 -64
- package/src/body-sync-queue.ts +0 -131
- package/src/body-sync-terminal.test.ts +0 -148
- package/src/body-sync-terminal.ts +0 -174
- package/src/body-sync.test.ts +0 -925
- package/src/body-sync.ts +0 -1891
- package/src/calendar-parts.ts +0 -41
- package/src/classification-backfill.test.ts +0 -285
- package/src/classification-backfill.ts +0 -363
- package/src/classify-message.test.ts +0 -142
- package/src/classify-message.ts +0 -116
- package/src/connection-factory.ts +0 -102
- package/src/connection-test.ts +0 -208
- package/src/credentials.test.ts +0 -181
- package/src/external-move.test.ts +0 -262
- package/src/external-move.ts +0 -285
- package/src/filter-operations.test.ts +0 -319
- package/src/filter-operations.ts +0 -377
- package/src/filter-update.test.ts +0 -313
- package/src/filters/filter-config.test.ts +0 -87
- package/src/filters/filter-config.ts +0 -34
- package/src/filters/list-id.test.ts +0 -50
- package/src/filters/match.test.ts +0 -392
- package/src/filters/match.ts +0 -225
- package/src/filters/pipeline.test.ts +0 -199
- package/src/filters/pipeline.ts +0 -170
- package/src/flag-push.test.ts +0 -112
- package/src/flag-push.ts +0 -198
- package/src/flag-queue.test.ts +0 -197
- package/src/flag-queue.ts +0 -509
- package/src/folder-operations.test.ts +0 -273
- package/src/folder-operations.ts +0 -217
- package/src/folder-placement.ts +0 -56
- package/src/forwarded-message.test.ts +0 -122
- package/src/heuristics/authenticationResults.test.ts +0 -189
- package/src/heuristics/authenticationResults.ts +0 -212
- package/src/heuristics/classifyByHeaders.test.ts +0 -889
- package/src/heuristics/classifyByHeaders.ts +0 -467
- package/src/heuristics/classifyPlacement.test.ts +0 -366
- package/src/heuristics/classifyPlacement.ts +0 -211
- package/src/heuristics/classifyRealMail.test.ts +0 -242
- package/src/heuristics/machineSenders.test.ts +0 -101
- package/src/heuristics/senderMismatch.test.ts +0 -346
- package/src/heuristics/senderMismatch.ts +0 -259
- package/src/imapflow-connect-retry.test.ts +0 -97
- package/src/imapflow-connection.test.ts +0 -503
- package/src/imapflow-connection.ts +0 -1476
- package/src/index.ts +0 -397
- package/src/list-id-backfill.test.ts +0 -207
- package/src/list-id-backfill.ts +0 -274
- package/src/mailbox-cursor-rebuild.test.ts +0 -171
- package/src/mailbox-cursor-rebuild.ts +0 -146
- package/src/mailbox-cursor.test.ts +0 -141
- package/src/mailbox-delete-intent.test.ts +0 -119
- package/src/mailbox-management.test.ts +0 -243
- package/src/mailbox-management.ts +0 -838
- package/src/mailbox-presence.test.ts +0 -74
- package/src/mailbox-queue.ts +0 -397
- package/src/mailbox-rename-intent.test.ts +0 -220
- package/src/mailbox-rename-subtree.test.ts +0 -287
- package/src/mailbox-sync.test.ts +0 -286
- package/src/mailbox-sync.ts +0 -724
- package/src/message-move.test.ts +0 -726
- package/src/message-move.ts +0 -1518
- package/src/message-parser.ts +0 -26
- package/src/message-presence.test.ts +0 -50
- package/src/message-read.ts +0 -87
- package/src/message-save-retry.test.ts +0 -90
- package/src/message-sync-flags.test.ts +0 -167
- package/src/message-sync.test.ts +0 -762
- package/src/message-sync.ts +0 -2133
- package/src/mime-walker.test.ts +0 -311
- package/src/mime-walker.ts +0 -306
- package/src/organize-job.ts +0 -93
- package/src/outbox-attachment-content.test.ts +0 -153
- package/src/outbox-attachment-content.ts +0 -104
- package/src/outbox-attachment-filename.test.ts +0 -132
- package/src/outbox-attachment-filename.ts +0 -76
- package/src/outbox-attachment.test.ts +0 -626
- package/src/outbox-attachment.ts +0 -536
- package/src/outbox-failed-recovery.test.ts +0 -299
- package/src/outbox-queue.test.ts +0 -315
- package/src/outbox-queue.ts +0 -525
- package/src/ownership.ts +0 -140
- package/src/placement-move-terminal.ts +0 -145
- package/src/placement-move.ts +0 -363
- package/src/placement-settled.test.ts +0 -147
- package/src/placement.test.ts +0 -404
- package/src/quarantine.test.ts +0 -133
- package/src/quarantine.ts +0 -229
- package/src/refusal.ts +0 -29
- package/src/snippet.test.ts +0 -213
- package/src/snippet.ts +0 -152
- package/src/spam-report.test.ts +0 -329
- package/src/spam-report.ts +0 -357
- package/src/sync-warning.test.ts +0 -213
- package/src/sync-warning.ts +0 -56
- package/src/sync-watermarks.test.ts +0 -409
- package/src/sync-watermarks.ts +0 -319
- package/src/system-flag-wire-format.test.ts +0 -77
- package/src/test-helpers/fake-imap.ts +0 -690
- package/src/test-helpers/folder-roles.ts +0 -66
- package/src/test-helpers/mime-tree-generator.ts +0 -461
- package/src/test-helpers/test-queue.ts +0 -56
- package/src/test-helpers/test-store.ts +0 -299
- package/src/text/normalizer.test.ts +0 -223
- package/src/text/normalizer.ts +0 -140
- package/src/thread-message-rows.ts +0 -77
- package/src/thread-root.test.ts +0 -29
- package/src/thread-root.ts +0 -51
- package/src/types/mailparser-augment.d.ts +0 -23
- package/src/types/natural-porter-stemmer.d.ts +0 -50
- package/src/types/stopword.d.ts +0 -69
- package/src/types.ts +0 -371
- package/tsconfig.json +0 -8
|
@@ -1,690 +0,0 @@
|
|
|
1
|
-
import { Readable } from "node:stream";
|
|
2
|
-
import { simpleParser } from "mailparser";
|
|
3
|
-
import type {
|
|
4
|
-
FlatMailboxInfo,
|
|
5
|
-
IImapConnection,
|
|
6
|
-
ImapAddress,
|
|
7
|
-
ImapBodyStructure,
|
|
8
|
-
ImapBoxStatus,
|
|
9
|
-
ImapConnectionState,
|
|
10
|
-
ImapEnvelopeSnapshot,
|
|
11
|
-
ImapMailboxStatus,
|
|
12
|
-
ImapMessage,
|
|
13
|
-
ImapNamespaces,
|
|
14
|
-
} from "../types.js";
|
|
15
|
-
|
|
16
|
-
interface StoredMessage {
|
|
17
|
-
raw: Buffer;
|
|
18
|
-
flags: Set<string>;
|
|
19
|
-
modseq: bigint;
|
|
20
|
-
internalDate: Date;
|
|
21
|
-
/**
|
|
22
|
-
* What BODYSTRUCTURE answers; read off the top-level headers when unset,
|
|
23
|
-
* and left out of the FETCH row when `null`.
|
|
24
|
-
*/
|
|
25
|
-
bodyStructure?: ImapBodyStructure | null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface Folder {
|
|
29
|
-
uidValidity: number;
|
|
30
|
-
uidNext: number;
|
|
31
|
-
attributes: string[];
|
|
32
|
-
subscribed: boolean;
|
|
33
|
-
messages: Map<number, StoredMessage>;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type FakeImapVerb =
|
|
37
|
-
| "NAMESPACE"
|
|
38
|
-
| "LIST"
|
|
39
|
-
| "STATUS"
|
|
40
|
-
| "SELECT"
|
|
41
|
-
| "EXAMINE"
|
|
42
|
-
| "CLOSE"
|
|
43
|
-
| "SEARCH"
|
|
44
|
-
| "FETCH"
|
|
45
|
-
| "BODY"
|
|
46
|
-
| "STORE"
|
|
47
|
-
| "MOVE"
|
|
48
|
-
| "COPY"
|
|
49
|
-
| "EXPUNGE"
|
|
50
|
-
| "CREATE"
|
|
51
|
-
| "RENAME"
|
|
52
|
-
| "DELETE"
|
|
53
|
-
| "SUBSCRIBE"
|
|
54
|
-
| "UNSUBSCRIBE"
|
|
55
|
-
| "APPEND";
|
|
56
|
-
|
|
57
|
-
/** An error shaped the way ImapFlow reports a tagged NO from the server. */
|
|
58
|
-
export const imapError = (code: string, text: string): Error =>
|
|
59
|
-
Object.assign(new Error(`Command failed: [${code}] ${text}`), {
|
|
60
|
-
serverResponseCode: code,
|
|
61
|
-
responseText: text,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const nonexistent = (path: string): Error =>
|
|
65
|
-
imapError("NONEXISTENT", `Mailbox doesn't exist: ${path}`);
|
|
66
|
-
|
|
67
|
-
export const eml = (headers: Record<string, string>, body = "body"): Buffer =>
|
|
68
|
-
Buffer.from(
|
|
69
|
-
[
|
|
70
|
-
...Object.entries({
|
|
71
|
-
From: "Sender <sender@example.com>",
|
|
72
|
-
To: "me@example.com",
|
|
73
|
-
Subject: "Hello",
|
|
74
|
-
"Content-Type": "text/plain",
|
|
75
|
-
...headers,
|
|
76
|
-
})
|
|
77
|
-
.filter(([, value]) => value !== "")
|
|
78
|
-
.map(([name, value]) => `${name}: ${value}`),
|
|
79
|
-
"",
|
|
80
|
-
body,
|
|
81
|
-
].join("\r\n"),
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const toAddresses = (
|
|
85
|
-
value: { value: Array<{ name?: string; address?: string }> } | undefined,
|
|
86
|
-
): ImapAddress[] =>
|
|
87
|
-
(value?.value ?? [])
|
|
88
|
-
.filter((entry) => entry.address)
|
|
89
|
-
.map((entry) => {
|
|
90
|
-
const [mailbox, host] = (entry.address ?? "").split("@");
|
|
91
|
-
return { name: entry.name || undefined, mailbox, host };
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const single = <T>(value: T | T[] | undefined): T | undefined =>
|
|
95
|
-
Array.isArray(value) ? value[0] : value;
|
|
96
|
-
|
|
97
|
-
const messageIdOf = (raw: Buffer): string =>
|
|
98
|
-
/^Message-ID:\s*(.+?)\s*$/im.exec(raw.toString("utf8"))?.[1] ?? "";
|
|
99
|
-
|
|
100
|
-
const UID_RANGE = /^(\d+)(?::(\d+|\*))?$/;
|
|
101
|
-
|
|
102
|
-
/** Whether `uid` is in an IMAP sequence set such as `3`, `1,4` or `2:*`. */
|
|
103
|
-
const inUidSet = (set: string, uid: number, highest: number): boolean =>
|
|
104
|
-
set.split(",").some((part) => {
|
|
105
|
-
const match = UID_RANGE.exec(part);
|
|
106
|
-
if (!match) throw new Error(`the fake server cannot read UID set ${set}`);
|
|
107
|
-
const from = Number(match[1]);
|
|
108
|
-
const to =
|
|
109
|
-
match[2] === undefined
|
|
110
|
-
? from
|
|
111
|
-
: match[2] === "*"
|
|
112
|
-
? Math.max(highest, from)
|
|
113
|
-
: Number(match[2]);
|
|
114
|
-
return uid >= Math.min(from, to) && uid <= Math.max(from, to);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* An IMAP server held in memory, behind the same connection port the imapflow
|
|
119
|
-
* adapter implements. It refuses what a server refuses: a folder that does not
|
|
120
|
-
* exist is `NONEXISTENT`, a transfer or APPEND to one is `TRYCREATE`, and a
|
|
121
|
-
* SEARCH it does not model throws rather than matching everything. FETCH rows
|
|
122
|
-
* are derived from the raw bytes the way the adapter derives them. `log`
|
|
123
|
-
* records every command that would reach the wire.
|
|
124
|
-
*/
|
|
125
|
-
export class FakeImapServer {
|
|
126
|
-
private readonly folders = new Map<string, Folder>();
|
|
127
|
-
private modseq = 1n;
|
|
128
|
-
private nextUidValidity = 1000;
|
|
129
|
-
readonly log: string[] = [];
|
|
130
|
-
readonly failNext: Partial<Record<FakeImapVerb, Error>> = {};
|
|
131
|
-
readonly ignoreNext = new Set<FakeImapVerb>();
|
|
132
|
-
condstore = true;
|
|
133
|
-
uidplus = true;
|
|
134
|
-
namespacePrefix = "";
|
|
135
|
-
connections = 0;
|
|
136
|
-
onFetchBody: (uid: number) => Promise<void> = async () => {};
|
|
137
|
-
|
|
138
|
-
addFolder(
|
|
139
|
-
path: string,
|
|
140
|
-
options: { attributes?: string[]; uidValidity?: number } = {},
|
|
141
|
-
): void {
|
|
142
|
-
if (this.folders.has(path)) throw new Error(`${path} already exists`);
|
|
143
|
-
this.folders.set(path, {
|
|
144
|
-
uidValidity: options.uidValidity ?? 7,
|
|
145
|
-
uidNext: 1,
|
|
146
|
-
attributes: options.attributes ?? [],
|
|
147
|
-
subscribed: true,
|
|
148
|
-
messages: new Map(),
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
hasFolder(path: string): boolean {
|
|
153
|
-
return this.folders.has(path);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** Another client deleted the folder. */
|
|
157
|
-
removeFolder(path: string): void {
|
|
158
|
-
if (!this.folders.delete(path)) throw nonexistent(path);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
isSubscribed(path: string): boolean {
|
|
162
|
-
return this.folder(path).subscribed;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
folder(path: string): Folder {
|
|
166
|
-
const folder = this.folders.get(path);
|
|
167
|
-
if (!folder) throw nonexistent(path);
|
|
168
|
-
return folder;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
paths(): string[] {
|
|
172
|
-
return [...this.folders.keys()];
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
deliver(
|
|
176
|
-
path: string,
|
|
177
|
-
raw: Buffer,
|
|
178
|
-
options: {
|
|
179
|
-
flags?: string[];
|
|
180
|
-
uid?: number;
|
|
181
|
-
bodyStructure?: ImapBodyStructure | null;
|
|
182
|
-
} = {},
|
|
183
|
-
): number {
|
|
184
|
-
const folder = this.folder(path);
|
|
185
|
-
const uid = options.uid ?? folder.uidNext;
|
|
186
|
-
if (folder.messages.has(uid))
|
|
187
|
-
throw new Error(`uid ${uid} is taken in ${path}`);
|
|
188
|
-
folder.uidNext = Math.max(folder.uidNext, uid + 1);
|
|
189
|
-
folder.messages.set(uid, {
|
|
190
|
-
raw,
|
|
191
|
-
flags: new Set(options.flags ?? []),
|
|
192
|
-
modseq: this.modseq++,
|
|
193
|
-
internalDate: new Date(1_700_000_000_000 + uid * 1000),
|
|
194
|
-
bodyStructure: options.bodyStructure,
|
|
195
|
-
});
|
|
196
|
-
return uid;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/** Another client expunged the message. */
|
|
200
|
-
expunge(path: string, uid: number): void {
|
|
201
|
-
if (!this.folder(path).messages.delete(uid)) {
|
|
202
|
-
throw new Error(`no uid ${uid} in ${path}`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/** Another client expunged everything in the folder. */
|
|
207
|
-
empty(path: string): void {
|
|
208
|
-
this.folder(path).messages.clear();
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/** One STORE from another client: every message it touches shares the new mod-sequence. */
|
|
212
|
-
store(path: string, uids: number[], flags: string[]): void {
|
|
213
|
-
const modseq = this.modseq++;
|
|
214
|
-
for (const uid of uids) {
|
|
215
|
-
const message = this.folder(path).messages.get(uid);
|
|
216
|
-
if (!message) throw new Error(`no uid ${uid} in ${path}`);
|
|
217
|
-
message.flags = new Set(flags);
|
|
218
|
-
message.modseq = modseq;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/** The server renumbered the folder: a new UIDVALIDITY, UIDs from 1. */
|
|
223
|
-
renumber(path: string, uidValidity: number): void {
|
|
224
|
-
const folder = this.folder(path);
|
|
225
|
-
const messages = [...folder.messages.values()];
|
|
226
|
-
folder.uidValidity = uidValidity;
|
|
227
|
-
folder.messages = new Map(messages.map((message, i) => [i + 1, message]));
|
|
228
|
-
folder.uidNext = messages.length + 1;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
flagsOf(path: string, uid: number): string[] {
|
|
232
|
-
return [...(this.folder(path).messages.get(uid)?.flags ?? [])].sort();
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
uids(path: string, messageIdHeader?: string): number[] {
|
|
236
|
-
return [...this.folder(path).messages]
|
|
237
|
-
.filter(
|
|
238
|
-
([, message]) =>
|
|
239
|
-
messageIdHeader === undefined ||
|
|
240
|
-
messageIdOf(message.raw) === messageIdHeader,
|
|
241
|
-
)
|
|
242
|
-
.map(([uid]) => uid);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/** What the server holds in a folder, as another client would read it. */
|
|
246
|
-
messages(path: string): Array<{
|
|
247
|
-
uid: number;
|
|
248
|
-
messageIdHeader: string;
|
|
249
|
-
flags: string[];
|
|
250
|
-
raw: Buffer;
|
|
251
|
-
}> {
|
|
252
|
-
return [...this.folder(path).messages].map(([uid, message]) => ({
|
|
253
|
-
uid,
|
|
254
|
-
messageIdHeader: messageIdOf(message.raw),
|
|
255
|
-
flags: [...message.flags].sort(),
|
|
256
|
-
raw: message.raw,
|
|
257
|
-
}));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/** The server changed UIDVALIDITY without renumbering what it serves. */
|
|
261
|
-
setUidValidity(path: string, uidValidity: number): void {
|
|
262
|
-
this.folder(path).uidValidity = uidValidity;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
uidValidityOf(path: string): number {
|
|
266
|
-
return this.folder(path).uidValidity;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
highestModseq(): bigint {
|
|
270
|
-
return this.modseq - 1n;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
bumpModseq(): bigint {
|
|
274
|
-
return this.modseq++;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
commands(verb: FakeImapVerb): string[] {
|
|
278
|
-
return this.log.filter((line) => line.startsWith(`${verb} `));
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
run(verb: FakeImapVerb, detail: string): void {
|
|
282
|
-
this.log.push(`${verb} ${detail}`);
|
|
283
|
-
const failure = this.failNext[verb];
|
|
284
|
-
if (failure) {
|
|
285
|
-
delete this.failNext[verb];
|
|
286
|
-
throw failure;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
createFolder(path: string): boolean {
|
|
291
|
-
if (this.folders.has(path)) return false;
|
|
292
|
-
this.nextUidValidity += 1;
|
|
293
|
-
this.addFolder(path, { uidValidity: this.nextUidValidity });
|
|
294
|
-
return true;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
renameFolder(oldPath: string, newPath: string): void {
|
|
298
|
-
this.folder(oldPath);
|
|
299
|
-
if (this.folders.has(newPath)) {
|
|
300
|
-
throw imapError("ALREADYEXISTS", `Mailbox already exists: ${newPath}`);
|
|
301
|
-
}
|
|
302
|
-
for (const [path, folder] of [...this.folders]) {
|
|
303
|
-
if (path !== oldPath && !path.startsWith(`${oldPath}/`)) continue;
|
|
304
|
-
this.folders.delete(path);
|
|
305
|
-
this.folders.set(newPath + path.slice(oldPath.length), folder);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
deleteFolder(path: string): void {
|
|
310
|
-
if (path === "INBOX") throw imapError("CANNOT", "Cannot delete INBOX");
|
|
311
|
-
this.folder(path);
|
|
312
|
-
for (const key of [...this.folders.keys()]) {
|
|
313
|
-
if (key === path || key.startsWith(`${path}/`)) this.folders.delete(key);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
connection(): FakeImapConnection {
|
|
318
|
-
this.connections += 1;
|
|
319
|
-
return new FakeImapConnection(this);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
export class FakeImapConnection implements IImapConnection {
|
|
324
|
-
readonly state: ImapConnectionState = "authenticated";
|
|
325
|
-
readonly isConnected = true;
|
|
326
|
-
private selected: string | null = null;
|
|
327
|
-
|
|
328
|
-
constructor(private readonly server: FakeImapServer) {}
|
|
329
|
-
|
|
330
|
-
private open(): Folder {
|
|
331
|
-
if (this.selected === null) throw new Error("No mailbox selected");
|
|
332
|
-
return this.server.folder(this.selected);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
private uidList = (uids: number[]): string => uids.join(",");
|
|
336
|
-
|
|
337
|
-
connect = async (): Promise<void> => {};
|
|
338
|
-
disconnect = async (): Promise<void> => {};
|
|
339
|
-
|
|
340
|
-
getNamespaces = async (): Promise<ImapNamespaces> => {
|
|
341
|
-
this.server.run("NAMESPACE", "");
|
|
342
|
-
return {
|
|
343
|
-
personal: [{ prefix: this.server.namespacePrefix, delimiter: "/" }],
|
|
344
|
-
other: [],
|
|
345
|
-
shared: [],
|
|
346
|
-
};
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
listMailboxes = async (): Promise<FlatMailboxInfo[]> => {
|
|
350
|
-
this.server.run("LIST", "");
|
|
351
|
-
return this.server.paths().map((fullPath) => {
|
|
352
|
-
const separator = fullPath.lastIndexOf("/");
|
|
353
|
-
return {
|
|
354
|
-
fullPath,
|
|
355
|
-
name: separator === -1 ? fullPath : fullPath.slice(separator + 1),
|
|
356
|
-
delimiter: "/",
|
|
357
|
-
attributes: this.server.folder(fullPath).attributes,
|
|
358
|
-
parentPath: separator === -1 ? null : fullPath.slice(0, separator),
|
|
359
|
-
};
|
|
360
|
-
});
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
openBox = async (path: string, readOnly = false): Promise<ImapBoxStatus> => {
|
|
364
|
-
this.server.run(readOnly ? "EXAMINE" : "SELECT", path);
|
|
365
|
-
const folder = this.server.folder(path);
|
|
366
|
-
this.selected = path;
|
|
367
|
-
return {
|
|
368
|
-
name: path,
|
|
369
|
-
readOnly,
|
|
370
|
-
uidvalidity: folder.uidValidity,
|
|
371
|
-
uidnext: folder.uidNext,
|
|
372
|
-
flags: [],
|
|
373
|
-
permFlags: [],
|
|
374
|
-
persistentUIDs: true,
|
|
375
|
-
messages: { total: folder.messages.size, new: 0 },
|
|
376
|
-
newKeywords: false,
|
|
377
|
-
};
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
closeBox = async (): Promise<void> => {
|
|
381
|
-
this.server.run("CLOSE", `${this.selected}`);
|
|
382
|
-
this.selected = null;
|
|
383
|
-
};
|
|
384
|
-
|
|
385
|
-
search = async (criteria: unknown[]): Promise<number[]> => {
|
|
386
|
-
this.server.run("SEARCH", `${this.selected} ${JSON.stringify(criteria)}`);
|
|
387
|
-
const folder = this.open();
|
|
388
|
-
const all = [...folder.messages.keys()];
|
|
389
|
-
const [first] = criteria;
|
|
390
|
-
if (criteria.length === 1 && first === "ALL") return all;
|
|
391
|
-
if (
|
|
392
|
-
criteria.length === 1 &&
|
|
393
|
-
Array.isArray(first) &&
|
|
394
|
-
first.length === 2 &&
|
|
395
|
-
first[0] === "UID"
|
|
396
|
-
) {
|
|
397
|
-
const highest = Math.max(0, ...all);
|
|
398
|
-
return all.filter((uid) => inUidSet(String(first[1]), uid, highest));
|
|
399
|
-
}
|
|
400
|
-
if (
|
|
401
|
-
criteria.length === 1 &&
|
|
402
|
-
Array.isArray(first) &&
|
|
403
|
-
first.length === 3 &&
|
|
404
|
-
first[0] === "HEADER" &&
|
|
405
|
-
String(first[1]).toLowerCase() === "message-id"
|
|
406
|
-
) {
|
|
407
|
-
return all.filter(
|
|
408
|
-
(uid) =>
|
|
409
|
-
messageIdOf(folder.messages.get(uid)?.raw ?? Buffer.alloc(0)) ===
|
|
410
|
-
first[2],
|
|
411
|
-
);
|
|
412
|
-
}
|
|
413
|
-
throw new Error(
|
|
414
|
-
`the fake server does not answer SEARCH ${JSON.stringify(criteria)}`,
|
|
415
|
-
);
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
private toImapMessage = async (
|
|
419
|
-
uid: number,
|
|
420
|
-
stored: StoredMessage,
|
|
421
|
-
): Promise<ImapMessage> => {
|
|
422
|
-
const parsed = await simpleParser(stored.raw);
|
|
423
|
-
const header = (name: string): string => {
|
|
424
|
-
const value = parsed.headers.get(name);
|
|
425
|
-
return typeof value === "string" ? value : "";
|
|
426
|
-
};
|
|
427
|
-
const contentType = single(parsed.headers.get("content-type")) as
|
|
428
|
-
| { value: string; params?: Record<string, string> }
|
|
429
|
-
| undefined;
|
|
430
|
-
const headerStructure: ImapBodyStructure = {
|
|
431
|
-
type: contentType?.value ?? "text/plain",
|
|
432
|
-
parameters: contentType?.params,
|
|
433
|
-
encoding: header("content-transfer-encoding") || "7bit",
|
|
434
|
-
size: stored.raw.length,
|
|
435
|
-
};
|
|
436
|
-
const references = parsed.references;
|
|
437
|
-
return {
|
|
438
|
-
uid,
|
|
439
|
-
seq: uid,
|
|
440
|
-
flags: [...stored.flags],
|
|
441
|
-
internalDate: stored.internalDate,
|
|
442
|
-
size: stored.raw.length,
|
|
443
|
-
envelope: {
|
|
444
|
-
date: parsed.date?.toISOString() ?? "",
|
|
445
|
-
subject: parsed.subject ?? "",
|
|
446
|
-
from: toAddresses(single(parsed.from)),
|
|
447
|
-
sender: toAddresses(single(parsed.from)),
|
|
448
|
-
replyTo: toAddresses(single(parsed.replyTo)),
|
|
449
|
-
to: toAddresses(single(parsed.to)),
|
|
450
|
-
cc: toAddresses(single(parsed.cc)),
|
|
451
|
-
bcc: [],
|
|
452
|
-
inReplyTo: header("in-reply-to"),
|
|
453
|
-
messageId: header("message-id"),
|
|
454
|
-
},
|
|
455
|
-
references:
|
|
456
|
-
references === undefined
|
|
457
|
-
? undefined
|
|
458
|
-
: Array.isArray(references)
|
|
459
|
-
? references
|
|
460
|
-
: [references],
|
|
461
|
-
...(stored.bodyStructure === null
|
|
462
|
-
? {}
|
|
463
|
-
: { bodyStructure: stored.bodyStructure ?? headerStructure }),
|
|
464
|
-
...(this.server.condstore ? { modseq: stored.modseq.toString() } : {}),
|
|
465
|
-
};
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
private fetchRows = async (uids: number[]): Promise<ImapMessage[]> => {
|
|
469
|
-
const folder = this.open();
|
|
470
|
-
const rows: ImapMessage[] = [];
|
|
471
|
-
for (const uid of uids) {
|
|
472
|
-
const stored = folder.messages.get(uid);
|
|
473
|
-
if (stored) rows.push(await this.toImapMessage(uid, stored));
|
|
474
|
-
}
|
|
475
|
-
return rows;
|
|
476
|
-
};
|
|
477
|
-
|
|
478
|
-
fetchMessages = async (uids: number[]): Promise<ImapMessage[]> => {
|
|
479
|
-
this.server.run("FETCH", `${this.selected} ${this.uidList(uids)}`);
|
|
480
|
-
return this.fetchRows(uids);
|
|
481
|
-
};
|
|
482
|
-
|
|
483
|
-
supportsCondstore = (): boolean => this.server.condstore;
|
|
484
|
-
|
|
485
|
-
fetchMessagesChangedSince = async (
|
|
486
|
-
sinceModseq: bigint,
|
|
487
|
-
): Promise<ImapMessage[]> => {
|
|
488
|
-
this.server.run(
|
|
489
|
-
"FETCH",
|
|
490
|
-
`${this.selected} 1:* CHANGEDSINCE ${sinceModseq}`,
|
|
491
|
-
);
|
|
492
|
-
if (!this.server.condstore) {
|
|
493
|
-
throw new Error("CHANGEDSINCE requires CONDSTORE");
|
|
494
|
-
}
|
|
495
|
-
const folder = this.open();
|
|
496
|
-
const rows: ImapMessage[] = [];
|
|
497
|
-
for (const [uid, stored] of folder.messages) {
|
|
498
|
-
if (stored.modseq > sinceModseq) {
|
|
499
|
-
rows.push(await this.toImapMessage(uid, stored));
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
return rows;
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
fetchEnvelopeSnapshots = async (
|
|
506
|
-
uids: number[],
|
|
507
|
-
): Promise<ImapEnvelopeSnapshot[]> => {
|
|
508
|
-
this.server.run("FETCH", `${this.selected} ${this.uidList(uids)} ENVELOPE`);
|
|
509
|
-
const rows = await this.fetchRows(uids);
|
|
510
|
-
return rows.map((row) => ({
|
|
511
|
-
uid: row.uid,
|
|
512
|
-
messageId: row.envelope?.messageId ?? "",
|
|
513
|
-
internalDate: row.internalDate,
|
|
514
|
-
}));
|
|
515
|
-
};
|
|
516
|
-
|
|
517
|
-
fetchMessageBody = async (uid: number): Promise<Buffer | null> => {
|
|
518
|
-
this.server.run("BODY", `${this.selected} ${uid}`);
|
|
519
|
-
await this.server.onFetchBody(uid);
|
|
520
|
-
return this.open().messages.get(uid)?.raw ?? null;
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
async *fetchMessageBodies(
|
|
524
|
-
uids: number[],
|
|
525
|
-
): AsyncGenerator<{ uid: number; source: Readable }> {
|
|
526
|
-
this.server.run("BODY", `${this.selected} ${this.uidList(uids)}`);
|
|
527
|
-
const folder = this.open();
|
|
528
|
-
for (const uid of uids) {
|
|
529
|
-
const stored = folder.messages.get(uid);
|
|
530
|
-
if (stored) yield { uid, source: Readable.from([stored.raw]) };
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
private touch = (
|
|
535
|
-
detail: string,
|
|
536
|
-
uids: number[],
|
|
537
|
-
change: (flags: Set<string>) => void,
|
|
538
|
-
) => {
|
|
539
|
-
this.server.run(
|
|
540
|
-
"STORE",
|
|
541
|
-
`${this.selected} ${this.uidList(uids)} ${detail}`,
|
|
542
|
-
);
|
|
543
|
-
const folder = this.open();
|
|
544
|
-
const modseq = this.server.bumpModseq();
|
|
545
|
-
for (const uid of uids) {
|
|
546
|
-
const stored = folder.messages.get(uid);
|
|
547
|
-
if (!stored) continue;
|
|
548
|
-
change(stored.flags);
|
|
549
|
-
stored.modseq = modseq;
|
|
550
|
-
}
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
addFlags = async (uids: number[], flags: string[]): Promise<void> =>
|
|
554
|
-
this.touch(`+${flags.join(" ")}`, uids, (set) => {
|
|
555
|
-
for (const flag of flags) set.add(flag);
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
removeFlags = async (uids: number[], flags: string[]): Promise<void> =>
|
|
559
|
-
this.touch(`-${flags.join(" ")}`, uids, (set) => {
|
|
560
|
-
for (const flag of flags) set.delete(flag);
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
setFlags = async (uids: number[], flags: string[]): Promise<void> =>
|
|
564
|
-
this.touch(flags.join(" "), uids, (set) => {
|
|
565
|
-
set.clear();
|
|
566
|
-
for (const flag of flags) set.add(flag);
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
createMailbox = async (
|
|
570
|
-
path: string,
|
|
571
|
-
): Promise<{ path: string; created: boolean }> => {
|
|
572
|
-
const resolved = this.server.namespacePrefix + path;
|
|
573
|
-
this.server.run("CREATE", resolved);
|
|
574
|
-
return { path: resolved, created: this.server.createFolder(resolved) };
|
|
575
|
-
};
|
|
576
|
-
|
|
577
|
-
deleteMailbox = async (path: string): Promise<{ path: string }> => {
|
|
578
|
-
this.server.run("DELETE", path);
|
|
579
|
-
this.server.deleteFolder(path);
|
|
580
|
-
return { path };
|
|
581
|
-
};
|
|
582
|
-
|
|
583
|
-
renameMailbox = async (
|
|
584
|
-
oldPath: string,
|
|
585
|
-
requested: string,
|
|
586
|
-
): Promise<{ path: string; newPath: string }> => {
|
|
587
|
-
const newPath = this.server.namespacePrefix + requested;
|
|
588
|
-
this.server.run("RENAME", `${oldPath} ${newPath}`);
|
|
589
|
-
this.server.renameFolder(oldPath, newPath);
|
|
590
|
-
return { path: oldPath, newPath };
|
|
591
|
-
};
|
|
592
|
-
|
|
593
|
-
subscribeMailbox = async (path: string): Promise<void> => {
|
|
594
|
-
this.server.run("SUBSCRIBE", path);
|
|
595
|
-
this.server.folder(path).subscribed = true;
|
|
596
|
-
};
|
|
597
|
-
|
|
598
|
-
unsubscribeMailbox = async (path: string): Promise<void> => {
|
|
599
|
-
this.server.run("UNSUBSCRIBE", path);
|
|
600
|
-
this.server.folder(path).subscribed = false;
|
|
601
|
-
};
|
|
602
|
-
|
|
603
|
-
private transfer = (
|
|
604
|
-
verb: "MOVE" | "COPY",
|
|
605
|
-
uids: number[],
|
|
606
|
-
destination: string,
|
|
607
|
-
) => {
|
|
608
|
-
this.server.run(
|
|
609
|
-
verb,
|
|
610
|
-
`${this.selected} ${this.uidList(uids)} ${destination}`,
|
|
611
|
-
);
|
|
612
|
-
if (!this.server.hasFolder(destination)) {
|
|
613
|
-
throw imapError("TRYCREATE", `Mailbox doesn't exist: ${destination}`);
|
|
614
|
-
}
|
|
615
|
-
const target = this.server.folder(destination);
|
|
616
|
-
const uidMap = new Map<number, number>();
|
|
617
|
-
if (this.server.ignoreNext.delete(verb)) {
|
|
618
|
-
return { destination, uidValidity: target.uidValidity, uidMap };
|
|
619
|
-
}
|
|
620
|
-
const source = this.open();
|
|
621
|
-
for (const uid of uids) {
|
|
622
|
-
const stored = source.messages.get(uid);
|
|
623
|
-
if (!stored) continue;
|
|
624
|
-
const next = target.uidNext++;
|
|
625
|
-
target.messages.set(next, {
|
|
626
|
-
...stored,
|
|
627
|
-
flags: new Set(stored.flags),
|
|
628
|
-
modseq: this.server.bumpModseq(),
|
|
629
|
-
});
|
|
630
|
-
if (verb === "MOVE") source.messages.delete(uid);
|
|
631
|
-
if (this.server.uidplus) uidMap.set(uid, next);
|
|
632
|
-
}
|
|
633
|
-
return { destination, uidValidity: target.uidValidity, uidMap };
|
|
634
|
-
};
|
|
635
|
-
|
|
636
|
-
moveMessages = async (uids: number[], destination: string) =>
|
|
637
|
-
this.transfer("MOVE", uids, destination);
|
|
638
|
-
|
|
639
|
-
copyMessages = async (uids: number[], destination: string) =>
|
|
640
|
-
this.transfer("COPY", uids, destination);
|
|
641
|
-
|
|
642
|
-
deleteMessages = async (uids: number[]): Promise<number> => {
|
|
643
|
-
this.server.run("EXPUNGE", `${this.selected} ${this.uidList(uids)}`);
|
|
644
|
-
const folder = this.open();
|
|
645
|
-
let deleted = 0;
|
|
646
|
-
for (const uid of uids) {
|
|
647
|
-
if (folder.messages.delete(uid)) deleted++;
|
|
648
|
-
}
|
|
649
|
-
return deleted;
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
getMailboxStatus = async (path: string): Promise<ImapMailboxStatus> => {
|
|
653
|
-
this.server.run("STATUS", path);
|
|
654
|
-
const folder = this.server.folder(path);
|
|
655
|
-
const messages = [...folder.messages.values()];
|
|
656
|
-
return {
|
|
657
|
-
messages: messages.length,
|
|
658
|
-
recent: 0,
|
|
659
|
-
unseen: messages.filter((message) => !message.flags.has("\\Seen")).length,
|
|
660
|
-
uidNext: folder.uidNext,
|
|
661
|
-
uidValidity: folder.uidValidity,
|
|
662
|
-
highestModseq: this.server.condstore
|
|
663
|
-
? this.server.highestModseq().toString()
|
|
664
|
-
: "0",
|
|
665
|
-
deletedCount: messages.filter((message) => message.flags.has("\\Deleted"))
|
|
666
|
-
.length,
|
|
667
|
-
};
|
|
668
|
-
};
|
|
669
|
-
|
|
670
|
-
append = async (
|
|
671
|
-
mailbox: string,
|
|
672
|
-
message: string | Buffer,
|
|
673
|
-
flags: string[] = [],
|
|
674
|
-
): Promise<{ destination: string; uidValidity: number; uid: number }> => {
|
|
675
|
-
this.server.run("APPEND", mailbox);
|
|
676
|
-
if (!this.server.hasFolder(mailbox)) {
|
|
677
|
-
throw imapError("TRYCREATE", `Mailbox doesn't exist: ${mailbox}`);
|
|
678
|
-
}
|
|
679
|
-
const uid = this.server.deliver(
|
|
680
|
-
mailbox,
|
|
681
|
-
Buffer.isBuffer(message) ? message : Buffer.from(message),
|
|
682
|
-
{ flags },
|
|
683
|
-
);
|
|
684
|
-
return {
|
|
685
|
-
destination: mailbox,
|
|
686
|
-
uidValidity: this.server.folder(mailbox).uidValidity,
|
|
687
|
-
uid,
|
|
688
|
-
};
|
|
689
|
-
};
|
|
690
|
-
}
|