@remit/mailbox-service 0.0.114 → 0.0.115
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/connection-factory.d.ts +8 -34
- package/dist/connection-factory.js +29 -35
- package/dist/imapflow-connection.d.ts +1 -18
- package/dist/imapflow-connection.js +12 -26
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/message-sync.d.ts +4 -5
- package/dist/message-sync.js +8 -8
- package/dist/node-transport.d.ts +2 -0
- package/dist/node-transport.js +8 -0
- package/dist/test-helpers/fake-imap.d.ts +2 -0
- package/dist/test-helpers/fake-imap.js +13 -4
- package/dist/test-helpers/test-store.js +1 -1
- package/dist/types.d.ts +3 -0
- package/package.json +6 -2
|
@@ -3,52 +3,26 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Uses ImapFlow for all IMAP connections.
|
|
5
5
|
*/
|
|
6
|
-
import type { IImapConnection, ImapConnectionConfig, MailCredentials } from "./types.js";
|
|
6
|
+
import type { IImapConnection, ImapConnectionConfig, ImapTransport, MailCredentials } from "./types.js";
|
|
7
7
|
/**
|
|
8
8
|
* Create an IMAP connection
|
|
9
9
|
*/
|
|
10
10
|
export declare const createConnection: (config: ImapConnectionConfig) => IImapConnection;
|
|
11
|
-
/**
|
|
12
|
-
* Create an IMAP connection from account data using password credentials.
|
|
13
|
-
*/
|
|
14
|
-
export declare const createConnectionFromAccount: (account: {
|
|
15
|
-
imapHost: string;
|
|
16
|
-
imapPort: number;
|
|
17
|
-
imapTls: boolean;
|
|
18
|
-
username: string;
|
|
19
|
-
}, password: string) => IImapConnection;
|
|
20
11
|
/**
|
|
21
12
|
* Create an IMAP connection from account data using a MailCredentials union.
|
|
22
|
-
* Use this instead of createConnectionFromAccount for OAuth accounts.
|
|
23
13
|
*/
|
|
24
14
|
export declare const createConnectionWithCredentials: (account: {
|
|
25
15
|
imapHost: string;
|
|
26
16
|
imapPort: number;
|
|
27
17
|
imapTls: boolean;
|
|
28
18
|
username: string;
|
|
29
|
-
}, credentials: MailCredentials) => IImapConnection;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* The factory handles connection lifecycle - callers should not disconnect.
|
|
34
|
-
* Call close() on the factory to disconnect when done.
|
|
35
|
-
*/
|
|
36
|
-
export interface ManagedConnectionFactory {
|
|
37
|
-
/** Get the cached connection (creates on first call) */
|
|
38
|
-
getConnection(): IImapConnection;
|
|
39
|
-
/** Disconnect and cleanup */
|
|
40
|
-
close(): Promise<void>;
|
|
19
|
+
}, credentials: MailCredentials, transport: ImapTransport) => IImapConnection;
|
|
20
|
+
export interface ConnectionScope {
|
|
21
|
+
getConnection: () => Promise<IImapConnection>;
|
|
22
|
+
disconnect: () => Promise<void>;
|
|
41
23
|
}
|
|
42
24
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```typescript
|
|
47
|
-
* const factory = createManagedConnectionFactory(config);
|
|
48
|
-
* const conn = factory.getConnection();
|
|
49
|
-
* await conn.connect();
|
|
50
|
-
* // ... reuse conn via factory.getConnection() ...
|
|
51
|
-
* await factory.close(); // Disconnect when done
|
|
52
|
-
* ```
|
|
25
|
+
* A lazily-connected, cached connection shared across the operations of one
|
|
26
|
+
* unit of work. Call disconnect() when done.
|
|
53
27
|
*/
|
|
54
|
-
export declare const
|
|
28
|
+
export declare const connectionScope: (config: ImapConnectionConfig) => ConnectionScope;
|
|
@@ -3,52 +3,46 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Uses ImapFlow for all IMAP connections.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { ImapFlowConnection } from "./imapflow-connection.js";
|
|
7
7
|
/**
|
|
8
8
|
* Create an IMAP connection
|
|
9
9
|
*/
|
|
10
10
|
export const createConnection = (config) => {
|
|
11
11
|
return new ImapFlowConnection(config);
|
|
12
12
|
};
|
|
13
|
-
/**
|
|
14
|
-
* Create an IMAP connection from account data using password credentials.
|
|
15
|
-
*/
|
|
16
|
-
export const createConnectionFromAccount = (account, password) => {
|
|
17
|
-
return createImapFlowConnectionFromAccount(account, password);
|
|
18
|
-
};
|
|
19
13
|
/**
|
|
20
14
|
* Create an IMAP connection from account data using a MailCredentials union.
|
|
21
|
-
* Use this instead of createConnectionFromAccount for OAuth accounts.
|
|
22
15
|
*/
|
|
23
|
-
export const createConnectionWithCredentials = (account, credentials) => {
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
export const createConnectionWithCredentials = (account, credentials, transport) => new ImapFlowConnection({
|
|
17
|
+
host: account.imapHost,
|
|
18
|
+
port: account.imapPort,
|
|
19
|
+
tls: account.imapTls,
|
|
20
|
+
transport,
|
|
21
|
+
user: account.username,
|
|
22
|
+
credentials,
|
|
23
|
+
});
|
|
26
24
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* ```typescript
|
|
31
|
-
* const factory = createManagedConnectionFactory(config);
|
|
32
|
-
* const conn = factory.getConnection();
|
|
33
|
-
* await conn.connect();
|
|
34
|
-
* // ... reuse conn via factory.getConnection() ...
|
|
35
|
-
* await factory.close(); // Disconnect when done
|
|
36
|
-
* ```
|
|
25
|
+
* A lazily-connected, cached connection shared across the operations of one
|
|
26
|
+
* unit of work. Call disconnect() when done.
|
|
37
27
|
*/
|
|
38
|
-
export const
|
|
28
|
+
export const connectionScope = (config) => {
|
|
39
29
|
let connection = null;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
30
|
+
let connectPromise = null;
|
|
31
|
+
const getConnection = async () => {
|
|
32
|
+
if (connectPromise) {
|
|
33
|
+
return connectPromise;
|
|
34
|
+
}
|
|
35
|
+
const conn = createConnection(config);
|
|
36
|
+
connection = conn;
|
|
37
|
+
connectPromise = conn.connect().then(() => conn);
|
|
38
|
+
return connectPromise;
|
|
39
|
+
};
|
|
40
|
+
const disconnect = async () => {
|
|
41
|
+
if (connection) {
|
|
42
|
+
await connection.disconnect();
|
|
43
|
+
connection = null;
|
|
44
|
+
connectPromise = null;
|
|
45
|
+
}
|
|
53
46
|
};
|
|
47
|
+
return { getConnection, disconnect };
|
|
54
48
|
};
|
|
@@ -62,6 +62,7 @@ export declare class ImapFlowConnection {
|
|
|
62
62
|
private _state;
|
|
63
63
|
private config;
|
|
64
64
|
private currentMailbox;
|
|
65
|
+
private currentMailboxReadOnly;
|
|
65
66
|
constructor(config: ImapConnectionConfig);
|
|
66
67
|
/**
|
|
67
68
|
* Current connection state
|
|
@@ -357,21 +358,3 @@ export declare class ImapFlowConnection {
|
|
|
357
358
|
*/
|
|
358
359
|
private ensureConnected;
|
|
359
360
|
}
|
|
360
|
-
/**
|
|
361
|
-
* Create an ImapFlow connection from account data using password credentials.
|
|
362
|
-
*/
|
|
363
|
-
export declare const createImapFlowConnectionFromAccount: (account: {
|
|
364
|
-
imapHost: string;
|
|
365
|
-
imapPort: number;
|
|
366
|
-
imapTls: boolean;
|
|
367
|
-
username: string;
|
|
368
|
-
}, password: string) => ImapFlowConnection;
|
|
369
|
-
/**
|
|
370
|
-
* Create an ImapFlow connection using a credentials union (password or OAuth access token).
|
|
371
|
-
*/
|
|
372
|
-
export declare const createImapFlowConnectionWithCredentials: (account: {
|
|
373
|
-
imapHost: string;
|
|
374
|
-
imapPort: number;
|
|
375
|
-
imapTls: boolean;
|
|
376
|
-
username: string;
|
|
377
|
-
}, credentials: import("./types.js").MailCredentials) => ImapFlowConnection;
|
|
@@ -149,6 +149,7 @@ export class ImapFlowConnection {
|
|
|
149
149
|
_state = "disconnected";
|
|
150
150
|
config;
|
|
151
151
|
currentMailbox = null;
|
|
152
|
+
currentMailboxReadOnly = true;
|
|
152
153
|
constructor(config) {
|
|
153
154
|
this.config = config;
|
|
154
155
|
}
|
|
@@ -223,6 +224,7 @@ export class ImapFlowConnection {
|
|
|
223
224
|
host: this.config.host,
|
|
224
225
|
port: this.config.port,
|
|
225
226
|
secure: this.config.tls,
|
|
227
|
+
transport: this.config.transport,
|
|
226
228
|
servername: this.config.tlsOptions?.servername,
|
|
227
229
|
auth,
|
|
228
230
|
tls: tlsOptions,
|
|
@@ -356,8 +358,12 @@ export class ImapFlowConnection {
|
|
|
356
358
|
// Idempotency guard: re-selecting the currently-open mailbox is a no-op
|
|
357
359
|
// SELECT on the wire. `mailbox` exposes the live, already-selected box, so
|
|
358
360
|
// we can return its status without paying for another round-trip — this is
|
|
359
|
-
// what lets a batch issue one SELECT and many fetches on the same box.
|
|
360
|
-
|
|
361
|
+
// what lets a batch issue one SELECT and many fetches on the same box. A
|
|
362
|
+
// read-only open does not cover a read-write request: a pooled connection
|
|
363
|
+
// a sync left EXAMINEd must SELECT before a STORE, MOVE or EXPUNGE.
|
|
364
|
+
if (this.currentMailbox === mailboxPath &&
|
|
365
|
+
this.client?.mailbox &&
|
|
366
|
+
(readOnly || !this.currentMailboxReadOnly)) {
|
|
361
367
|
return this.toBoxStatus(this.client.mailbox, readOnly);
|
|
362
368
|
}
|
|
363
369
|
const mailbox = await this.client?.mailboxOpen(mailboxPath, {
|
|
@@ -367,6 +373,7 @@ export class ImapFlowConnection {
|
|
|
367
373
|
throw new Error(`Failed to open mailbox: ${mailboxPath}`);
|
|
368
374
|
}
|
|
369
375
|
this.currentMailbox = mailboxPath;
|
|
376
|
+
this.currentMailboxReadOnly = readOnly;
|
|
370
377
|
return this.toBoxStatus(mailbox, readOnly);
|
|
371
378
|
};
|
|
372
379
|
/**
|
|
@@ -995,6 +1002,9 @@ export class ImapFlowConnection {
|
|
|
995
1002
|
uidValidity: true,
|
|
996
1003
|
highestModseq: true,
|
|
997
1004
|
});
|
|
1005
|
+
if (status === false) {
|
|
1006
|
+
throw new Error(`IMAP STATUS returned no result for ${mailboxPath}`);
|
|
1007
|
+
}
|
|
998
1008
|
const deletedCount = await this.countDeleted(mailboxPath);
|
|
999
1009
|
return {
|
|
1000
1010
|
messages: status.messages ?? 0,
|
|
@@ -1052,30 +1062,6 @@ export class ImapFlowConnection {
|
|
|
1052
1062
|
}
|
|
1053
1063
|
};
|
|
1054
1064
|
}
|
|
1055
|
-
/**
|
|
1056
|
-
* Create an ImapFlow connection from account data using password credentials.
|
|
1057
|
-
*/
|
|
1058
|
-
export const createImapFlowConnectionFromAccount = (account, password) => {
|
|
1059
|
-
return new ImapFlowConnection({
|
|
1060
|
-
host: account.imapHost,
|
|
1061
|
-
port: account.imapPort,
|
|
1062
|
-
tls: account.imapTls,
|
|
1063
|
-
user: account.username,
|
|
1064
|
-
credentials: { kind: "password", password },
|
|
1065
|
-
});
|
|
1066
|
-
};
|
|
1067
|
-
/**
|
|
1068
|
-
* Create an ImapFlow connection using a credentials union (password or OAuth access token).
|
|
1069
|
-
*/
|
|
1070
|
-
export const createImapFlowConnectionWithCredentials = (account, credentials) => {
|
|
1071
|
-
return new ImapFlowConnection({
|
|
1072
|
-
host: account.imapHost,
|
|
1073
|
-
port: account.imapPort,
|
|
1074
|
-
tls: account.imapTls,
|
|
1075
|
-
user: account.username,
|
|
1076
|
-
credentials,
|
|
1077
|
-
});
|
|
1078
|
-
};
|
|
1079
1065
|
/**
|
|
1080
1066
|
* Build the imapflow auth object from mail credentials.
|
|
1081
1067
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export { type BodySyncQueueConfig, type BodySyncQueueLogger, BodySyncQueueServic
|
|
|
11
11
|
export { isMessageBodySyncBroken, type ResolveExhaustedBodySyncDeps, type ResolveExhaustedBodySyncInput, type ResolveExhaustedBodySyncResult, resolveExhaustedBodySyncFailures, } from "./body-sync-terminal.js";
|
|
12
12
|
export { backfillClassifications, type ClassificationBackfillCheckpoint, type ClassificationBackfillCheckpointStore, type ClassificationBackfillDeps, type ClassificationBackfillLogger, type ClassificationBackfillOptions, type ClassificationBackfillProgress, type ClassificationBackfillResult, type ClassificationBackfillTotals, } from "./classification-backfill.js";
|
|
13
13
|
export { classifyParsedMessage, extractPrimaryFromEmail, } from "./classify-message.js";
|
|
14
|
-
export {
|
|
14
|
+
export { type ConnectionScope, connectionScope, createConnection, createConnectionWithCredentials, } from "./connection-factory.js";
|
|
15
15
|
export { type ImapTestConfig, type SmtpTestConfig, type TestResult, testImapConnection, testSmtpConnection, } from "./connection-test.js";
|
|
16
16
|
export { type CreatedFilter, createFilter, type FilterPage, type FilterPatch, listFilters, type NewFilter, resolveFilterUpdate, updateFilter, } from "./filter-operations.js";
|
|
17
17
|
export { extractListId, normalizeListId } from "./filters/list-id.js";
|
|
@@ -27,7 +27,7 @@ export { classifyPlacement, type FolderPlacement, type PlacementAction, type Pla
|
|
|
27
27
|
export { classifyDisplayNameCorrespondence, extractOffDomainLinkDomains, extractSenderMismatch, type SenderMismatchContext, type SenderMismatchSignals, } from "./heuristics/senderMismatch.js";
|
|
28
28
|
export { SOCIAL_DOMAINS } from "./heuristics/socialDomains.js";
|
|
29
29
|
export { TRANSACTIONAL_DOMAINS } from "./heuristics/transactionalDomains.js";
|
|
30
|
-
export {
|
|
30
|
+
export { ImapFlowConnection } from "./imapflow-connection.js";
|
|
31
31
|
export { backfillListIds, type ListIdBackfillCheckpoint, type ListIdBackfillCheckpointStore, type ListIdBackfillDeps, type ListIdBackfillLogger, type ListIdBackfillOptions, type ListIdBackfillProgress, type ListIdBackfillResult, type ListIdBackfillTotals, } from "./list-id-backfill.js";
|
|
32
32
|
export { guardConnectionCursor, guardMailboxCursor, isCursorRebuildNeeded, type MailboxCursorCheck, type MailboxCursorGuardDeps, MailboxCursorPausedError, } from "./mailbox-cursor.js";
|
|
33
33
|
export { type CursorRebuildMatch, type CursorRebuildMatchResult, type CursorRebuildRow, type CursorRebuildSnapshot, matchCursorRebuild, } from "./mailbox-cursor-rebuild.js";
|
|
@@ -59,5 +59,5 @@ export { extractSearchText, extractSnippetFromEmail, generateSnippet, normalizeS
|
|
|
59
59
|
export { MoveNotSettledError, NoJunkMailboxError, type SpamReportConfig, type SpamReportLogger, type SpamReportParams, SpamReportService, } from "./spam-report.js";
|
|
60
60
|
export { type ServerUids, type SyncWarningContext, type SyncWarningRound, SyncWarningService, } from "./sync-warning.js";
|
|
61
61
|
export { buildThreadMessageMoveRevert, buildThreadMessageTrashUpdate, buildThreadMessageUndelete, type ThreadMessageRowState, } from "./thread-message-rows.js";
|
|
62
|
-
export type { FlatMailboxInfo, IImapConnection, ImapBoxStatus, ImapConnectionConfig, ImapConnectionState, ImapNamespace, ImapNamespaces, MailboxSyncResult, MailConnectionErrorKind, MailCredentials, } from "./types.js";
|
|
62
|
+
export type { FlatMailboxInfo, IImapConnection, ImapBoxStatus, ImapConnectionConfig, ImapConnectionState, ImapNamespace, ImapNamespaces, ImapTransport, MailboxSyncResult, MailConnectionErrorKind, MailCredentials, } from "./types.js";
|
|
63
63
|
export { MailConnectionError } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export { isMessageBodySyncBroken, resolveExhaustedBodySyncFailures, } from "./bo
|
|
|
12
12
|
export { backfillClassifications, } from "./classification-backfill.js";
|
|
13
13
|
export { classifyParsedMessage, extractPrimaryFromEmail, } from "./classify-message.js";
|
|
14
14
|
// Connection factory
|
|
15
|
-
export {
|
|
15
|
+
export { connectionScope, createConnection, createConnectionWithCredentials, } from "./connection-factory.js";
|
|
16
16
|
// Connection testing
|
|
17
17
|
export { testImapConnection, testSmtpConnection, } from "./connection-test.js";
|
|
18
18
|
export { createFilter, listFilters, resolveFilterUpdate, updateFilter, } from "./filter-operations.js";
|
|
@@ -30,7 +30,7 @@ export { classifyDisplayNameCorrespondence, extractOffDomainLinkDomains, extract
|
|
|
30
30
|
export { SOCIAL_DOMAINS } from "./heuristics/socialDomains.js";
|
|
31
31
|
export { TRANSACTIONAL_DOMAINS } from "./heuristics/transactionalDomains.js";
|
|
32
32
|
// IMAP connection (ImapFlow-based)
|
|
33
|
-
export {
|
|
33
|
+
export { ImapFlowConnection } from "./imapflow-connection.js";
|
|
34
34
|
export { backfillListIds, } from "./list-id-backfill.js";
|
|
35
35
|
export { guardConnectionCursor, guardMailboxCursor, isCursorRebuildNeeded, MailboxCursorPausedError, } from "./mailbox-cursor.js";
|
|
36
36
|
export { matchCursorRebuild, } from "./mailbox-cursor-rebuild.js";
|
package/dist/message-sync.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { IAddressRepository, IEnvelopeRepository, IMailboxRepository, IMailboxSpecialUseRepository, IMessageFlagPushRepository, IMessageFlagRepository, IMessageRepository, IThreadMessageRepository } from "@remit/data-ports";
|
|
2
2
|
import type { JunkRoleMailboxes } from "@remit/data-ports/folder-role";
|
|
3
|
-
import type { ManagedConnectionFactory } from "./connection-factory.js";
|
|
4
3
|
import type { QuarantineService } from "./quarantine.js";
|
|
5
4
|
import type { SyncWarningService } from "./sync-warning.js";
|
|
6
|
-
import type { ImapAddress } from "./types.js";
|
|
5
|
+
import type { IImapConnection, ImapAddress } from "./types.js";
|
|
7
6
|
/**
|
|
8
7
|
* A From address is only usable when it looks like a real mailbox: both parts
|
|
9
8
|
* present, neither is a known "could not parse" sentinel, and the host carries
|
|
@@ -63,7 +62,7 @@ export declare const parseHeaderDate: (raw: string | undefined, fallbackMs: numb
|
|
|
63
62
|
usedFallback: boolean;
|
|
64
63
|
};
|
|
65
64
|
/**
|
|
66
|
-
* @deprecated
|
|
65
|
+
* @deprecated Pass the IImapConnection to MessageSyncService instead
|
|
67
66
|
*/
|
|
68
67
|
export type ImapConnectionFactory = () => {
|
|
69
68
|
connect(): Promise<void>;
|
|
@@ -101,7 +100,7 @@ export interface SyncMessagesResult {
|
|
|
101
100
|
*/
|
|
102
101
|
export declare const selectUidsToSync: (allUids: number[], lastSyncUid: number, highWaterMarkUid: number) => number[];
|
|
103
102
|
export declare class MessageSyncService {
|
|
104
|
-
private
|
|
103
|
+
private connection;
|
|
105
104
|
private mailboxService;
|
|
106
105
|
/**
|
|
107
106
|
* The account’s folder-role map. Required, not optional: which folder
|
|
@@ -135,7 +134,7 @@ export declare class MessageSyncService {
|
|
|
135
134
|
*/
|
|
136
135
|
private quarantineService?;
|
|
137
136
|
private log;
|
|
138
|
-
constructor(
|
|
137
|
+
constructor(connection: IImapConnection, mailboxService: IMailboxRepository,
|
|
139
138
|
/**
|
|
140
139
|
* The account’s folder-role map. Required, not optional: which folder
|
|
141
140
|
* holds the Junk role decides whether a sighting withholds its sender
|
package/dist/message-sync.js
CHANGED
|
@@ -147,7 +147,7 @@ const messageIdForSighting = (accountId, mailboxId, msg) => deriveMessageIdFromS
|
|
|
147
147
|
fromHost: msg.envelope.from?.[0]?.host,
|
|
148
148
|
});
|
|
149
149
|
export class MessageSyncService {
|
|
150
|
-
|
|
150
|
+
connection;
|
|
151
151
|
mailboxService;
|
|
152
152
|
mailboxSpecialUseService;
|
|
153
153
|
messageService;
|
|
@@ -159,7 +159,7 @@ export class MessageSyncService {
|
|
|
159
159
|
messageFlagService;
|
|
160
160
|
quarantineService;
|
|
161
161
|
log;
|
|
162
|
-
constructor(
|
|
162
|
+
constructor(connection, mailboxService,
|
|
163
163
|
/**
|
|
164
164
|
* The account’s folder-role map. Required, not optional: which folder
|
|
165
165
|
* holds the Junk role decides whether a sighting withholds its sender
|
|
@@ -186,7 +186,7 @@ export class MessageSyncService {
|
|
|
186
186
|
* not re-fetched on every round.
|
|
187
187
|
*/
|
|
188
188
|
quarantineService) {
|
|
189
|
-
this.
|
|
189
|
+
this.connection = connection;
|
|
190
190
|
this.mailboxService = mailboxService;
|
|
191
191
|
this.mailboxSpecialUseService = mailboxSpecialUseService;
|
|
192
192
|
this.messageService = messageService;
|
|
@@ -227,7 +227,7 @@ export class MessageSyncService {
|
|
|
227
227
|
}
|
|
228
228
|
const lastSyncUid = mailbox.lastSyncUid || 0;
|
|
229
229
|
const highWaterMarkUid = mailbox.highWaterMarkUid || 0;
|
|
230
|
-
const connection = this.
|
|
230
|
+
const connection = this.connection;
|
|
231
231
|
const box = await connection.openBox(mailboxPath);
|
|
232
232
|
const status = await connection.getMailboxStatus(mailboxPath);
|
|
233
233
|
// Detection: the served UIDVALIDITY may have changed since it was last
|
|
@@ -453,7 +453,7 @@ export class MessageSyncService {
|
|
|
453
453
|
await this.mailboxService.update(accountId, mailboxId, {
|
|
454
454
|
cursorState: MailboxCursorState.rebuilding,
|
|
455
455
|
});
|
|
456
|
-
const connection = this.
|
|
456
|
+
const connection = this.connection;
|
|
457
457
|
const box = await connection.openBox(mailboxPath);
|
|
458
458
|
// Read before the pass, never after: a message arriving while the
|
|
459
459
|
// rebuild runs is absent from the snapshot below, and a HIGHESTMODSEQ
|
|
@@ -652,7 +652,7 @@ export class MessageSyncService {
|
|
|
652
652
|
const { mailbox, accountId, accountConfigId, cursor, box, status, batchSize, } = params;
|
|
653
653
|
const mailboxId = mailbox.mailboxId;
|
|
654
654
|
const mailboxPath = mailbox.fullPath;
|
|
655
|
-
const connection = this.
|
|
655
|
+
const connection = this.connection;
|
|
656
656
|
// Ask from the last COMPLETE mod-sequence, so a group left part-applied
|
|
657
657
|
// by an earlier round is served again in full; its applied members are
|
|
658
658
|
// then dropped by position, without a lookup.
|
|
@@ -963,7 +963,7 @@ export class MessageSyncService {
|
|
|
963
963
|
* Assumes mailbox is already open from fetchUidsToSync.
|
|
964
964
|
*/
|
|
965
965
|
async fetchMessageBatch(uids) {
|
|
966
|
-
const connection = this.
|
|
966
|
+
const connection = this.connection;
|
|
967
967
|
return await connection.fetchMessages(uids);
|
|
968
968
|
}
|
|
969
969
|
/**
|
|
@@ -1004,7 +1004,7 @@ export class MessageSyncService {
|
|
|
1004
1004
|
*/
|
|
1005
1005
|
async probeDepartures(mailbox, accountId, applicable) {
|
|
1006
1006
|
return confirmDepartures({
|
|
1007
|
-
connection: this.
|
|
1007
|
+
connection: this.connection,
|
|
1008
1008
|
mailboxService: this.mailboxService,
|
|
1009
1009
|
messageService: this.messageService,
|
|
1010
1010
|
log: this.log,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { connect as netConnect } from "node:net";
|
|
2
|
+
import { connect as tlsConnect } from "node:tls";
|
|
3
|
+
export const nodeTransport = () => ({
|
|
4
|
+
connect: ({ host, port, servername, tls, secure }, onConnect) => secure
|
|
5
|
+
? tlsConnect({ host, port, servername, ...tls }, onConnect)
|
|
6
|
+
: netConnect({ host, port }, onConnect),
|
|
7
|
+
upgrade: (socket, { host, port, servername, tls }, onSecure) => tlsConnect({ socket, host, port, servername, ...tls }, onSecure),
|
|
8
|
+
});
|
|
@@ -91,8 +91,10 @@ export declare class FakeImapConnection implements IImapConnection {
|
|
|
91
91
|
readonly state: ImapConnectionState;
|
|
92
92
|
readonly isConnected = true;
|
|
93
93
|
private selected;
|
|
94
|
+
private selectedReadOnly;
|
|
94
95
|
constructor(server: FakeImapServer);
|
|
95
96
|
private open;
|
|
97
|
+
private writable;
|
|
96
98
|
private uidList;
|
|
97
99
|
connect: () => Promise<void>;
|
|
98
100
|
disconnect: () => Promise<void>;
|
|
@@ -216,6 +216,7 @@ export class FakeImapConnection {
|
|
|
216
216
|
state = "authenticated";
|
|
217
217
|
isConnected = true;
|
|
218
218
|
selected = null;
|
|
219
|
+
selectedReadOnly = true;
|
|
219
220
|
constructor(server) {
|
|
220
221
|
this.server = server;
|
|
221
222
|
}
|
|
@@ -224,6 +225,13 @@ export class FakeImapConnection {
|
|
|
224
225
|
throw new Error("No mailbox selected");
|
|
225
226
|
return this.server.folder(this.selected);
|
|
226
227
|
}
|
|
228
|
+
writable() {
|
|
229
|
+
const folder = this.open();
|
|
230
|
+
if (this.selectedReadOnly) {
|
|
231
|
+
throw imapError("READ-ONLY", `Mailbox is read-only: ${this.selected}`);
|
|
232
|
+
}
|
|
233
|
+
return folder;
|
|
234
|
+
}
|
|
227
235
|
uidList = (uids) => uids.join(",");
|
|
228
236
|
connect = async () => { };
|
|
229
237
|
disconnect = async () => { };
|
|
@@ -248,10 +256,11 @@ export class FakeImapConnection {
|
|
|
248
256
|
};
|
|
249
257
|
});
|
|
250
258
|
};
|
|
251
|
-
openBox = async (path, readOnly =
|
|
259
|
+
openBox = async (path, readOnly = true) => {
|
|
252
260
|
this.server.run(readOnly ? "EXAMINE" : "SELECT", path);
|
|
253
261
|
const folder = this.server.folder(path);
|
|
254
262
|
this.selected = path;
|
|
263
|
+
this.selectedReadOnly = readOnly;
|
|
255
264
|
return {
|
|
256
265
|
name: path,
|
|
257
266
|
readOnly,
|
|
@@ -389,7 +398,7 @@ export class FakeImapConnection {
|
|
|
389
398
|
}
|
|
390
399
|
touch = (detail, uids, change) => {
|
|
391
400
|
this.server.run("STORE", `${this.selected} ${this.uidList(uids)} ${detail}`);
|
|
392
|
-
const folder = this.
|
|
401
|
+
const folder = this.writable();
|
|
393
402
|
const modseq = this.server.bumpModseq();
|
|
394
403
|
for (const uid of uids) {
|
|
395
404
|
const stored = folder.messages.get(uid);
|
|
@@ -446,7 +455,7 @@ export class FakeImapConnection {
|
|
|
446
455
|
if (this.server.ignoreNext.delete(verb)) {
|
|
447
456
|
return { destination, uidValidity: target.uidValidity, uidMap };
|
|
448
457
|
}
|
|
449
|
-
const source = this.open();
|
|
458
|
+
const source = verb === "MOVE" ? this.writable() : this.open();
|
|
450
459
|
for (const uid of uids) {
|
|
451
460
|
const stored = source.messages.get(uid);
|
|
452
461
|
if (!stored)
|
|
@@ -468,7 +477,7 @@ export class FakeImapConnection {
|
|
|
468
477
|
copyMessages = async (uids, destination) => this.transfer("COPY", uids, destination);
|
|
469
478
|
deleteMessages = async (uids) => {
|
|
470
479
|
this.server.run("EXPUNGE", `${this.selected} ${this.uidList(uids)}`);
|
|
471
|
-
const folder = this.
|
|
480
|
+
const folder = this.writable();
|
|
472
481
|
let deleted = 0;
|
|
473
482
|
for (const uid of uids) {
|
|
474
483
|
if (folder.messages.delete(uid))
|
|
@@ -146,7 +146,7 @@ export const silentLog = {
|
|
|
146
146
|
};
|
|
147
147
|
export const syncFolder = async (store, account, mailbox, imap) => {
|
|
148
148
|
const connection = imap.connection();
|
|
149
|
-
const service = new MessageSyncService(
|
|
149
|
+
const service = new MessageSyncService(connection, store.mailbox, store.mailboxSpecialUse, store.message, store.envelope, store.address, store.threadMessage, new SyncWarningService(store.syncWarning), silentLog, store.flagPush, store.messageFlag);
|
|
150
150
|
const synced = [];
|
|
151
151
|
for (;;) {
|
|
152
152
|
const result = await service.syncMessages(mailbox.mailboxId, account.accountId, account.accountConfigId);
|
package/dist/types.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Types for the remit-mailbox-service
|
|
3
3
|
*/
|
|
4
4
|
import type { Readable } from "node:stream";
|
|
5
|
+
import type { ImapTransport } from "imapflow";
|
|
6
|
+
export type { ImapTransport };
|
|
5
7
|
/**
|
|
6
8
|
* Discriminated union of mail authentication credentials.
|
|
7
9
|
*
|
|
@@ -40,6 +42,7 @@ export interface ImapConnectionConfig {
|
|
|
40
42
|
user: string;
|
|
41
43
|
credentials: MailCredentials;
|
|
42
44
|
tls: boolean;
|
|
45
|
+
transport: ImapTransport;
|
|
43
46
|
tlsOptions?: {
|
|
44
47
|
rejectUnauthorized?: boolean;
|
|
45
48
|
servername?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/mailbox-service",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.115",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"types": "./dist/imapflow-connection.d.ts",
|
|
22
22
|
"default": "./dist/imapflow-connection.js"
|
|
23
23
|
},
|
|
24
|
+
"./node-transport": {
|
|
25
|
+
"types": "./dist/node-transport.d.ts",
|
|
26
|
+
"default": "./dist/node-transport.js"
|
|
27
|
+
},
|
|
24
28
|
"./account-credentials": {
|
|
25
29
|
"types": "./dist/account-credentials.d.ts",
|
|
26
30
|
"default": "./dist/account-credentials.js"
|
|
@@ -55,7 +59,7 @@
|
|
|
55
59
|
"better-sqlite3": "^12.11.1"
|
|
56
60
|
},
|
|
57
61
|
"dependencies": {
|
|
58
|
-
"imapflow": "^
|
|
62
|
+
"imapflow": "^2.9.1",
|
|
59
63
|
"mailparser": "3.12.2",
|
|
60
64
|
"nodemailer": "^10.0.6",
|
|
61
65
|
"p-map": "^7.0.4",
|