@bobfrankston/iflow-direct 0.1.1
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/bridge-transport.d.ts +30 -0
- package/bridge-transport.js +60 -0
- package/credentials.json +9 -0
- package/gmail.d.ts +39 -0
- package/gmail.js +66 -0
- package/imap-compat.d.ts +83 -0
- package/imap-compat.js +254 -0
- package/imap-native.d.ts +135 -0
- package/imap-native.js +733 -0
- package/imap-protocol.d.ts +133 -0
- package/imap-protocol.js +391 -0
- package/index.d.ts +18 -0
- package/index.js +18 -0
- package/package.json +42 -0
- package/transport.d.ts +25 -0
- package/transport.js +6 -0
- package/types.d.ts +14 -0
- package/types.js +5 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge transport for IMAP — uses msgapi.tcp from the native shell.
|
|
3
|
+
* Works in any msgapi host: msga (MAUI), msger (Rust/wry), msgview (Electron).
|
|
4
|
+
*
|
|
5
|
+
* The native shell provides:
|
|
6
|
+
* msgapi.tcp.connect(host, port, tls) → streamId
|
|
7
|
+
* msgapi.tcp.write(streamId, data)
|
|
8
|
+
* msgapi.tcp.onData(streamId, callback)
|
|
9
|
+
* msgapi.tcp.upgradeTLS(streamId, servername)
|
|
10
|
+
* msgapi.tcp.close(streamId)
|
|
11
|
+
*
|
|
12
|
+
* TLS is handled natively — JS never sees raw TLS handshake.
|
|
13
|
+
*/
|
|
14
|
+
import type { ImapTransport } from "./transport.js";
|
|
15
|
+
export declare class BridgeTransport implements ImapTransport {
|
|
16
|
+
private streamId;
|
|
17
|
+
private dataHandler;
|
|
18
|
+
private closeHandler;
|
|
19
|
+
private errorHandler;
|
|
20
|
+
private _connected;
|
|
21
|
+
get connected(): boolean;
|
|
22
|
+
connect(host: string, port: number, tls: boolean, _servername?: string): Promise<void>;
|
|
23
|
+
upgradeTLS(servername?: string): Promise<void>;
|
|
24
|
+
write(data: string | Uint8Array): Promise<void>;
|
|
25
|
+
onData(handler: (data: string) => void): void;
|
|
26
|
+
onClose(handler: (hadError: boolean) => void): void;
|
|
27
|
+
onError(handler: (err: Error) => void): void;
|
|
28
|
+
close(): void;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=bridge-transport.d.ts.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge transport for IMAP — uses msgapi.tcp from the native shell.
|
|
3
|
+
* Works in any msgapi host: msga (MAUI), msger (Rust/wry), msgview (Electron).
|
|
4
|
+
*
|
|
5
|
+
* The native shell provides:
|
|
6
|
+
* msgapi.tcp.connect(host, port, tls) → streamId
|
|
7
|
+
* msgapi.tcp.write(streamId, data)
|
|
8
|
+
* msgapi.tcp.onData(streamId, callback)
|
|
9
|
+
* msgapi.tcp.upgradeTLS(streamId, servername)
|
|
10
|
+
* msgapi.tcp.close(streamId)
|
|
11
|
+
*
|
|
12
|
+
* TLS is handled natively — JS never sees raw TLS handshake.
|
|
13
|
+
*/
|
|
14
|
+
export class BridgeTransport {
|
|
15
|
+
streamId = null;
|
|
16
|
+
dataHandler = null;
|
|
17
|
+
closeHandler = null;
|
|
18
|
+
errorHandler = null;
|
|
19
|
+
_connected = false;
|
|
20
|
+
get connected() { return this._connected; }
|
|
21
|
+
async connect(host, port, tls, _servername) {
|
|
22
|
+
this.streamId = await msgapi.tcp.connect(host, port, tls);
|
|
23
|
+
this._connected = true;
|
|
24
|
+
msgapi.tcp.onData(this.streamId, (data) => {
|
|
25
|
+
if (this.dataHandler)
|
|
26
|
+
this.dataHandler(data);
|
|
27
|
+
});
|
|
28
|
+
msgapi.tcp.onClose(this.streamId, (hadError) => {
|
|
29
|
+
this._connected = false;
|
|
30
|
+
if (this.closeHandler)
|
|
31
|
+
this.closeHandler(hadError);
|
|
32
|
+
});
|
|
33
|
+
msgapi.tcp.onError(this.streamId, (message) => {
|
|
34
|
+
if (this.errorHandler)
|
|
35
|
+
this.errorHandler(new Error(message));
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async upgradeTLS(servername) {
|
|
39
|
+
if (this.streamId == null)
|
|
40
|
+
throw new Error("Not connected");
|
|
41
|
+
await msgapi.tcp.upgradeTLS(this.streamId, servername || "");
|
|
42
|
+
}
|
|
43
|
+
async write(data) {
|
|
44
|
+
if (this.streamId == null)
|
|
45
|
+
throw new Error("Not connected");
|
|
46
|
+
const str = typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
47
|
+
await msgapi.tcp.write(this.streamId, str);
|
|
48
|
+
}
|
|
49
|
+
onData(handler) { this.dataHandler = handler; }
|
|
50
|
+
onClose(handler) { this.closeHandler = handler; }
|
|
51
|
+
onError(handler) { this.errorHandler = handler; }
|
|
52
|
+
close() {
|
|
53
|
+
this._connected = false;
|
|
54
|
+
if (this.streamId != null) {
|
|
55
|
+
msgapi.tcp.close(this.streamId);
|
|
56
|
+
this.streamId = null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=bridge-transport.js.map
|
package/credentials.json
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"installed": {
|
|
3
|
+
"client_id": "884213380682-hcso64dcqmk4p98vsc7br2e6gvn7iv2u.apps.googleusercontent.com",
|
|
4
|
+
"client_secret": "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",
|
|
5
|
+
"token_uri": "https://oauth2.googleapis.com/token",
|
|
6
|
+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
|
7
|
+
"redirect_uris": ["http://localhost:9326"]
|
|
8
|
+
}
|
|
9
|
+
}
|
package/gmail.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gmail-specific IMAP support — Node-free version.
|
|
3
|
+
* OAuth token acquisition is the caller's responsibility.
|
|
4
|
+
* The caller provides a tokenProvider function that returns an access token.
|
|
5
|
+
*/
|
|
6
|
+
import type { ImapClientConfig, TokenProvider } from "./types.js";
|
|
7
|
+
export interface GmailOAuthConfig {
|
|
8
|
+
username: string;
|
|
9
|
+
tokenProvider: TokenProvider;
|
|
10
|
+
verbose?: boolean;
|
|
11
|
+
rejectUnauthorized?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Check if email address is Gmail
|
|
15
|
+
*/
|
|
16
|
+
export declare function isGmailUser(username: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Check if a server is Gmail
|
|
19
|
+
*/
|
|
20
|
+
export declare function isGmailServer(server: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Create Gmail IMAP config with caller-supplied token provider.
|
|
23
|
+
* The tokenProvider handles OAuth — this function just wires it up.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createGmailConfig(config: GmailOAuthConfig): ImapClientConfig;
|
|
26
|
+
/**
|
|
27
|
+
* Auto-configure IMAP client — detects Gmail and uses OAuth, otherwise uses password.
|
|
28
|
+
* For Gmail, caller must provide a tokenProvider.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createAutoImapConfig(config: {
|
|
31
|
+
server: string;
|
|
32
|
+
port: number;
|
|
33
|
+
username: string;
|
|
34
|
+
password?: string;
|
|
35
|
+
tokenProvider?: TokenProvider;
|
|
36
|
+
verbose?: boolean;
|
|
37
|
+
rejectUnauthorized?: boolean;
|
|
38
|
+
}): ImapClientConfig;
|
|
39
|
+
//# sourceMappingURL=gmail.d.ts.map
|
package/gmail.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gmail-specific IMAP support — Node-free version.
|
|
3
|
+
* OAuth token acquisition is the caller's responsibility.
|
|
4
|
+
* The caller provides a tokenProvider function that returns an access token.
|
|
5
|
+
*/
|
|
6
|
+
const GMAIL_SERVER = "imap.gmail.com";
|
|
7
|
+
const GMAIL_PORT = 993;
|
|
8
|
+
/**
|
|
9
|
+
* Check if email address is Gmail
|
|
10
|
+
*/
|
|
11
|
+
export function isGmailUser(username) {
|
|
12
|
+
return username.toLowerCase().endsWith("@gmail.com");
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Check if a server is Gmail
|
|
16
|
+
*/
|
|
17
|
+
export function isGmailServer(server) {
|
|
18
|
+
return server.toLowerCase() === GMAIL_SERVER ||
|
|
19
|
+
server.toLowerCase().endsWith(".gmail.com");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create Gmail IMAP config with caller-supplied token provider.
|
|
23
|
+
* The tokenProvider handles OAuth — this function just wires it up.
|
|
24
|
+
*/
|
|
25
|
+
export function createGmailConfig(config) {
|
|
26
|
+
if (!isGmailUser(config.username)) {
|
|
27
|
+
throw new Error(`Gmail OAuth only supports @gmail.com. Got: ${config.username}`);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
server: GMAIL_SERVER,
|
|
31
|
+
port: GMAIL_PORT,
|
|
32
|
+
username: config.username,
|
|
33
|
+
tokenProvider: config.tokenProvider,
|
|
34
|
+
verbose: config.verbose,
|
|
35
|
+
rejectUnauthorized: config.rejectUnauthorized,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Auto-configure IMAP client — detects Gmail and uses OAuth, otherwise uses password.
|
|
40
|
+
* For Gmail, caller must provide a tokenProvider.
|
|
41
|
+
*/
|
|
42
|
+
export function createAutoImapConfig(config) {
|
|
43
|
+
const isGmail = isGmailUser(config.username) || isGmailServer(config.server);
|
|
44
|
+
if (isGmail) {
|
|
45
|
+
if (!config.tokenProvider) {
|
|
46
|
+
throw new Error("Gmail requires a tokenProvider for OAuth. Pass a function that returns an access token.");
|
|
47
|
+
}
|
|
48
|
+
return createGmailConfig({
|
|
49
|
+
username: config.username,
|
|
50
|
+
tokenProvider: config.tokenProvider,
|
|
51
|
+
verbose: config.verbose,
|
|
52
|
+
rejectUnauthorized: config.rejectUnauthorized,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
return {
|
|
57
|
+
server: config.server,
|
|
58
|
+
port: config.port,
|
|
59
|
+
username: config.username,
|
|
60
|
+
password: config.password,
|
|
61
|
+
verbose: config.verbose,
|
|
62
|
+
rejectUnauthorized: config.rejectUnauthorized,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=gmail.js.map
|
package/imap-compat.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility wrapper — makes NativeImapClient look like the old ImapClient API.
|
|
3
|
+
* This allows mailx-imap to switch to the native client without rewriting all call sites.
|
|
4
|
+
* Each method opens the mailbox, does the operation, and returns. No persistent state.
|
|
5
|
+
*/
|
|
6
|
+
import { type NativeFolder } from "./imap-native.js";
|
|
7
|
+
import type { ImapClientConfig } from "./types.js";
|
|
8
|
+
import type { TransportFactory } from "./transport.js";
|
|
9
|
+
/** Special folder detection result */
|
|
10
|
+
export interface SpecialFolders {
|
|
11
|
+
inbox?: string;
|
|
12
|
+
sent?: string;
|
|
13
|
+
trash?: string;
|
|
14
|
+
drafts?: string;
|
|
15
|
+
spam?: string;
|
|
16
|
+
junk?: string;
|
|
17
|
+
archive?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Compatibility IMAP client — wraps NativeImapClient with the old ImapClient API.
|
|
21
|
+
* Each method creates its own select/operation/close cycle.
|
|
22
|
+
*/
|
|
23
|
+
export declare class CompatImapClient {
|
|
24
|
+
private native;
|
|
25
|
+
private _connected;
|
|
26
|
+
constructor(config: ImapClientConfig, transportFactory: TransportFactory);
|
|
27
|
+
/** Connect and authenticate */
|
|
28
|
+
connect(): Promise<void>;
|
|
29
|
+
/** Ensure connected (lazy connect) */
|
|
30
|
+
private ensureConnected;
|
|
31
|
+
logout(): Promise<void>;
|
|
32
|
+
/** Get folder list */
|
|
33
|
+
getFolderList(): Promise<NativeFolder[]>;
|
|
34
|
+
/** Detect special folders from folder list */
|
|
35
|
+
getSpecialFolders(folders: NativeFolder[]): SpecialFolders;
|
|
36
|
+
/** Fetch messages since a UID in a mailbox */
|
|
37
|
+
fetchMessagesSinceUid(mailbox: string, sinceUid: number, options?: {
|
|
38
|
+
source?: boolean;
|
|
39
|
+
}): Promise<any[]>;
|
|
40
|
+
/** Fetch messages by date range */
|
|
41
|
+
fetchMessageByDate(mailbox: string, start: Date, end?: Date, options?: {
|
|
42
|
+
source?: boolean;
|
|
43
|
+
}): Promise<any[]>;
|
|
44
|
+
/** Fetch a single message by UID */
|
|
45
|
+
fetchMessageByUid(mailbox: string, uid: number, options?: {
|
|
46
|
+
source?: boolean;
|
|
47
|
+
}): Promise<any | null>;
|
|
48
|
+
/** Get message count via STATUS (does not require SELECT) */
|
|
49
|
+
getMessagesCount(mailbox: string): Promise<number>;
|
|
50
|
+
/** Get all UIDs in a mailbox */
|
|
51
|
+
getUids(mailbox: string): Promise<number[]>;
|
|
52
|
+
/** Fetch messages by UID range string (e.g. "100,200,300" or "100:200") */
|
|
53
|
+
fetchMessages(mailbox: string, uidRange: string, options?: {
|
|
54
|
+
source?: boolean;
|
|
55
|
+
}): Promise<any[]>;
|
|
56
|
+
/** Search messages in a mailbox */
|
|
57
|
+
searchMessages(mailbox: string, criteria: any): Promise<number[]>;
|
|
58
|
+
/** Search by header value — returns matching UIDs */
|
|
59
|
+
searchByHeader(mailbox: string, headerName: string, headerValue: string): Promise<number[]>;
|
|
60
|
+
/** Delete a message by UID */
|
|
61
|
+
deleteMessageByUid(mailbox: string, uid: number): Promise<void>;
|
|
62
|
+
/** Move a message between mailboxes (same server) */
|
|
63
|
+
moveMessage(msg: any, fromMailbox: string, toMailbox: string): Promise<void>;
|
|
64
|
+
/** Add flags to a message */
|
|
65
|
+
addFlags(mailbox: string, uid: number, flags: string[]): Promise<void>;
|
|
66
|
+
/** Remove flags from a message */
|
|
67
|
+
removeFlags(mailbox: string, uid: number, flags: string[]): Promise<void>;
|
|
68
|
+
/** Create a mailbox */
|
|
69
|
+
createmailbox(name: string): Promise<void>;
|
|
70
|
+
/** Append a message to a mailbox */
|
|
71
|
+
appendMessage(mailbox: string, message: string | Uint8Array, flags?: string[]): Promise<number | null>;
|
|
72
|
+
/** Watch a mailbox for new messages (IDLE) */
|
|
73
|
+
watchMailbox(mailbox: string, onNew: (count: number) => void): Promise<() => Promise<void>>;
|
|
74
|
+
/** Copy a message to another server (cross-account) */
|
|
75
|
+
moveMessageToServer(msg: any, fromMailbox: string, targetClient: CompatImapClient, toMailbox: string): Promise<void>;
|
|
76
|
+
/** Rename a mailbox (via native access) */
|
|
77
|
+
renameMailbox(from: string, to: string): Promise<void>;
|
|
78
|
+
/** Delete a mailbox */
|
|
79
|
+
deleteMailbox(name: string): Promise<void>;
|
|
80
|
+
/** Get flags for a UID — returns string[] for compatibility with old client */
|
|
81
|
+
getFlags(mailbox: string, uid: number): Promise<string[]>;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=imap-compat.d.ts.map
|
package/imap-compat.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility wrapper — makes NativeImapClient look like the old ImapClient API.
|
|
3
|
+
* This allows mailx-imap to switch to the native client without rewriting all call sites.
|
|
4
|
+
* Each method opens the mailbox, does the operation, and returns. No persistent state.
|
|
5
|
+
*/
|
|
6
|
+
import { NativeImapClient } from "./imap-native.js";
|
|
7
|
+
/** Convert NativeFetchedMessage to the old FetchedMessage shape used by mailx-imap */
|
|
8
|
+
function toCompatMessage(msg) {
|
|
9
|
+
return {
|
|
10
|
+
uid: msg.uid,
|
|
11
|
+
seq: msg.seq,
|
|
12
|
+
flags: msg.flags,
|
|
13
|
+
seen: msg.seen,
|
|
14
|
+
flagged: msg.flagged,
|
|
15
|
+
answered: msg.answered,
|
|
16
|
+
draft: msg.draft,
|
|
17
|
+
date: msg.date,
|
|
18
|
+
subject: msg.subject,
|
|
19
|
+
messageId: msg.messageId,
|
|
20
|
+
from: msg.from,
|
|
21
|
+
to: msg.to,
|
|
22
|
+
cc: msg.cc,
|
|
23
|
+
bcc: msg.bcc,
|
|
24
|
+
sender: msg.sender,
|
|
25
|
+
replyTo: msg.replyTo,
|
|
26
|
+
inReplyTo: msg.inReplyTo,
|
|
27
|
+
size: msg.size,
|
|
28
|
+
source: msg.source,
|
|
29
|
+
headers: msg.headers,
|
|
30
|
+
// Compatibility: old FetchedMessage had envelope sub-object, but mailx-imap accesses directly
|
|
31
|
+
envelope: {
|
|
32
|
+
date: msg.date,
|
|
33
|
+
subject: msg.subject,
|
|
34
|
+
messageId: msg.messageId,
|
|
35
|
+
from: msg.from,
|
|
36
|
+
to: msg.to,
|
|
37
|
+
cc: msg.cc,
|
|
38
|
+
bcc: msg.bcc,
|
|
39
|
+
sender: msg.sender,
|
|
40
|
+
replyTo: msg.replyTo,
|
|
41
|
+
inReplyTo: msg.inReplyTo,
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Compatibility IMAP client — wraps NativeImapClient with the old ImapClient API.
|
|
47
|
+
* Each method creates its own select/operation/close cycle.
|
|
48
|
+
*/
|
|
49
|
+
export class CompatImapClient {
|
|
50
|
+
native;
|
|
51
|
+
_connected = false;
|
|
52
|
+
constructor(config, transportFactory) {
|
|
53
|
+
this.native = new NativeImapClient(config, transportFactory);
|
|
54
|
+
}
|
|
55
|
+
/** Connect and authenticate */
|
|
56
|
+
async connect() {
|
|
57
|
+
await this.native.connect();
|
|
58
|
+
this._connected = true;
|
|
59
|
+
}
|
|
60
|
+
/** Ensure connected (lazy connect) */
|
|
61
|
+
async ensureConnected() {
|
|
62
|
+
if (!this._connected)
|
|
63
|
+
await this.connect();
|
|
64
|
+
}
|
|
65
|
+
async logout() {
|
|
66
|
+
this._connected = false;
|
|
67
|
+
await this.native.logout();
|
|
68
|
+
}
|
|
69
|
+
/** Get folder list */
|
|
70
|
+
async getFolderList() {
|
|
71
|
+
await this.ensureConnected();
|
|
72
|
+
return this.native.listFolders();
|
|
73
|
+
}
|
|
74
|
+
/** Detect special folders from folder list */
|
|
75
|
+
getSpecialFolders(folders) {
|
|
76
|
+
const result = {};
|
|
77
|
+
for (const f of folders) {
|
|
78
|
+
const lower = f.path.toLowerCase();
|
|
79
|
+
const flags = f.flags.map(fl => fl.toLowerCase());
|
|
80
|
+
if (flags.includes("\\inbox") || lower === "inbox")
|
|
81
|
+
result.inbox = f.path;
|
|
82
|
+
else if (flags.includes("\\sent") || lower === "sent" || lower.includes("sent"))
|
|
83
|
+
result.sent = f.path;
|
|
84
|
+
else if (flags.includes("\\trash") || lower === "trash" || lower.includes("trash"))
|
|
85
|
+
result.trash = f.path;
|
|
86
|
+
else if (flags.includes("\\drafts") || lower === "drafts" || lower.includes("draft"))
|
|
87
|
+
result.drafts = f.path;
|
|
88
|
+
else if (flags.includes("\\junk") || flags.includes("\\spam") || lower === "spam" || lower === "junk") {
|
|
89
|
+
result.junk = f.path;
|
|
90
|
+
result.spam = f.path;
|
|
91
|
+
}
|
|
92
|
+
else if (flags.includes("\\archive") || lower === "archive")
|
|
93
|
+
result.archive = f.path;
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
/** Fetch messages since a UID in a mailbox */
|
|
98
|
+
async fetchMessagesSinceUid(mailbox, sinceUid, options) {
|
|
99
|
+
await this.ensureConnected();
|
|
100
|
+
await this.native.select(mailbox);
|
|
101
|
+
const msgs = await this.native.fetchSinceUid(sinceUid, options);
|
|
102
|
+
await this.native.closeMailbox();
|
|
103
|
+
return msgs.map(toCompatMessage);
|
|
104
|
+
}
|
|
105
|
+
/** Fetch messages by date range */
|
|
106
|
+
async fetchMessageByDate(mailbox, start, end, options) {
|
|
107
|
+
await this.ensureConnected();
|
|
108
|
+
await this.native.select(mailbox);
|
|
109
|
+
const msgs = await this.native.fetchByDate(start, end, options);
|
|
110
|
+
await this.native.closeMailbox();
|
|
111
|
+
return msgs.map(toCompatMessage);
|
|
112
|
+
}
|
|
113
|
+
/** Fetch a single message by UID */
|
|
114
|
+
async fetchMessageByUid(mailbox, uid, options) {
|
|
115
|
+
await this.ensureConnected();
|
|
116
|
+
await this.native.select(mailbox);
|
|
117
|
+
const msg = await this.native.fetchMessage(uid, options);
|
|
118
|
+
await this.native.closeMailbox();
|
|
119
|
+
return msg ? toCompatMessage(msg) : null;
|
|
120
|
+
}
|
|
121
|
+
/** Get message count via STATUS (does not require SELECT) */
|
|
122
|
+
async getMessagesCount(mailbox) {
|
|
123
|
+
await this.ensureConnected();
|
|
124
|
+
return this.native.getMessageCount(mailbox);
|
|
125
|
+
}
|
|
126
|
+
/** Get all UIDs in a mailbox */
|
|
127
|
+
async getUids(mailbox) {
|
|
128
|
+
await this.ensureConnected();
|
|
129
|
+
await this.native.select(mailbox);
|
|
130
|
+
const uids = await this.native.getUids();
|
|
131
|
+
await this.native.closeMailbox();
|
|
132
|
+
return uids;
|
|
133
|
+
}
|
|
134
|
+
/** Fetch messages by UID range string (e.g. "100,200,300" or "100:200") */
|
|
135
|
+
async fetchMessages(mailbox, uidRange, options) {
|
|
136
|
+
await this.ensureConnected();
|
|
137
|
+
await this.native.select(mailbox);
|
|
138
|
+
const msgs = await this.native.fetchMessages(uidRange, options);
|
|
139
|
+
await this.native.closeMailbox();
|
|
140
|
+
return msgs.map(toCompatMessage);
|
|
141
|
+
}
|
|
142
|
+
/** Search messages in a mailbox */
|
|
143
|
+
async searchMessages(mailbox, criteria) {
|
|
144
|
+
await this.ensureConnected();
|
|
145
|
+
await this.native.select(mailbox);
|
|
146
|
+
// Convert object criteria to IMAP search string
|
|
147
|
+
const parts = [];
|
|
148
|
+
if (criteria.from)
|
|
149
|
+
parts.push(`FROM "${criteria.from}"`);
|
|
150
|
+
if (criteria.to)
|
|
151
|
+
parts.push(`TO "${criteria.to}"`);
|
|
152
|
+
if (criteria.subject)
|
|
153
|
+
parts.push(`SUBJECT "${criteria.subject}"`);
|
|
154
|
+
if (criteria.body)
|
|
155
|
+
parts.push(`BODY "${criteria.body}"`);
|
|
156
|
+
if (criteria.since)
|
|
157
|
+
parts.push(`SINCE ${formatDate(criteria.since)}`);
|
|
158
|
+
if (criteria.before)
|
|
159
|
+
parts.push(`BEFORE ${formatDate(criteria.before)}`);
|
|
160
|
+
const searchStr = parts.length > 0 ? parts.join(" ") : "ALL";
|
|
161
|
+
const uids = await this.native.search(searchStr);
|
|
162
|
+
await this.native.closeMailbox();
|
|
163
|
+
return uids;
|
|
164
|
+
}
|
|
165
|
+
/** Search by header value — returns matching UIDs */
|
|
166
|
+
async searchByHeader(mailbox, headerName, headerValue) {
|
|
167
|
+
await this.ensureConnected();
|
|
168
|
+
await this.native.select(mailbox);
|
|
169
|
+
const uids = await this.native.search(`HEADER ${headerName} "${headerValue}"`);
|
|
170
|
+
await this.native.closeMailbox();
|
|
171
|
+
return uids;
|
|
172
|
+
}
|
|
173
|
+
/** Delete a message by UID */
|
|
174
|
+
async deleteMessageByUid(mailbox, uid) {
|
|
175
|
+
await this.ensureConnected();
|
|
176
|
+
await this.native.select(mailbox);
|
|
177
|
+
await this.native.deleteMessage(uid);
|
|
178
|
+
await this.native.closeMailbox();
|
|
179
|
+
}
|
|
180
|
+
/** Move a message between mailboxes (same server) */
|
|
181
|
+
async moveMessage(msg, fromMailbox, toMailbox) {
|
|
182
|
+
await this.ensureConnected();
|
|
183
|
+
await this.native.select(fromMailbox);
|
|
184
|
+
await this.native.moveMessage(msg.uid, toMailbox);
|
|
185
|
+
await this.native.closeMailbox();
|
|
186
|
+
}
|
|
187
|
+
/** Add flags to a message */
|
|
188
|
+
async addFlags(mailbox, uid, flags) {
|
|
189
|
+
await this.ensureConnected();
|
|
190
|
+
await this.native.select(mailbox);
|
|
191
|
+
await this.native.addFlags(uid, flags);
|
|
192
|
+
await this.native.closeMailbox();
|
|
193
|
+
}
|
|
194
|
+
/** Remove flags from a message */
|
|
195
|
+
async removeFlags(mailbox, uid, flags) {
|
|
196
|
+
await this.ensureConnected();
|
|
197
|
+
await this.native.select(mailbox);
|
|
198
|
+
await this.native.removeFlags(uid, flags);
|
|
199
|
+
await this.native.closeMailbox();
|
|
200
|
+
}
|
|
201
|
+
/** Create a mailbox */
|
|
202
|
+
async createmailbox(name) {
|
|
203
|
+
await this.ensureConnected();
|
|
204
|
+
await this.native.createMailbox(name);
|
|
205
|
+
}
|
|
206
|
+
/** Append a message to a mailbox */
|
|
207
|
+
async appendMessage(mailbox, message, flags = []) {
|
|
208
|
+
await this.ensureConnected();
|
|
209
|
+
const data = typeof message === "string" ? message : new TextDecoder().decode(message);
|
|
210
|
+
return this.native.appendMessage(mailbox, data, flags);
|
|
211
|
+
}
|
|
212
|
+
/** Watch a mailbox for new messages (IDLE) */
|
|
213
|
+
async watchMailbox(mailbox, onNew) {
|
|
214
|
+
await this.ensureConnected();
|
|
215
|
+
await this.native.select(mailbox);
|
|
216
|
+
return this.native.startIdle(onNew);
|
|
217
|
+
}
|
|
218
|
+
/** Copy a message to another server (cross-account) */
|
|
219
|
+
async moveMessageToServer(msg, fromMailbox, targetClient, toMailbox) {
|
|
220
|
+
// Fetch source, append to target, delete from source
|
|
221
|
+
await this.ensureConnected();
|
|
222
|
+
await this.native.select(fromMailbox);
|
|
223
|
+
const fetched = await this.native.fetchMessage(msg.uid, { source: true });
|
|
224
|
+
if (!fetched || !fetched.source)
|
|
225
|
+
throw new Error(`Message UID ${msg.uid} not found`);
|
|
226
|
+
await targetClient.ensureConnected();
|
|
227
|
+
await targetClient.native.appendMessage(toMailbox, fetched.source, [...fetched.flags]);
|
|
228
|
+
await this.native.deleteMessage(msg.uid);
|
|
229
|
+
await this.native.closeMailbox();
|
|
230
|
+
}
|
|
231
|
+
/** Rename a mailbox (via native access) */
|
|
232
|
+
async renameMailbox(from, to) {
|
|
233
|
+
await this.ensureConnected();
|
|
234
|
+
await this.native.renameMailbox(from, to);
|
|
235
|
+
}
|
|
236
|
+
/** Delete a mailbox */
|
|
237
|
+
async deleteMailbox(name) {
|
|
238
|
+
await this.ensureConnected();
|
|
239
|
+
await this.native.deleteMailbox(name);
|
|
240
|
+
}
|
|
241
|
+
/** Get flags for a UID — returns string[] for compatibility with old client */
|
|
242
|
+
async getFlags(mailbox, uid) {
|
|
243
|
+
await this.ensureConnected();
|
|
244
|
+
await this.native.select(mailbox);
|
|
245
|
+
const msg = await this.native.fetchMessage(uid, { source: false });
|
|
246
|
+
await this.native.closeMailbox();
|
|
247
|
+
return msg?.flags ? [...msg.flags] : [];
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function formatDate(d) {
|
|
251
|
+
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
252
|
+
return `${d.getDate()}-${months[d.getMonth()]}-${d.getFullYear()}`;
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=imap-compat.js.map
|
package/imap-native.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native IMAP client — transport-agnostic.
|
|
3
|
+
* Uses ImapTransport for I/O, imap-protocol for parsing.
|
|
4
|
+
* Works with NodeTransport (desktop) or BridgeTransport (Android).
|
|
5
|
+
*
|
|
6
|
+
* This is a NEW client alongside the existing ImapClient (which wraps imapflow).
|
|
7
|
+
* Existing callers are not affected.
|
|
8
|
+
*/
|
|
9
|
+
import type { TransportFactory } from "./transport.js";
|
|
10
|
+
import * as proto from "./imap-protocol.js";
|
|
11
|
+
import type { ImapClientConfig } from "./types.js";
|
|
12
|
+
export interface NativeFetchedMessage {
|
|
13
|
+
seq: number;
|
|
14
|
+
uid: number;
|
|
15
|
+
flags: Set<string>;
|
|
16
|
+
date: Date | null;
|
|
17
|
+
subject: string;
|
|
18
|
+
messageId: string;
|
|
19
|
+
from: proto.AddressData[];
|
|
20
|
+
to: proto.AddressData[];
|
|
21
|
+
cc: proto.AddressData[];
|
|
22
|
+
bcc: proto.AddressData[];
|
|
23
|
+
sender: proto.AddressData[];
|
|
24
|
+
replyTo: proto.AddressData[];
|
|
25
|
+
inReplyTo: string;
|
|
26
|
+
size: number;
|
|
27
|
+
source: string;
|
|
28
|
+
headers: string;
|
|
29
|
+
seen: boolean;
|
|
30
|
+
flagged: boolean;
|
|
31
|
+
answered: boolean;
|
|
32
|
+
draft: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface NativeFolder {
|
|
35
|
+
path: string;
|
|
36
|
+
delimiter: string;
|
|
37
|
+
flags: string[];
|
|
38
|
+
}
|
|
39
|
+
export interface MailboxInfo {
|
|
40
|
+
exists: number;
|
|
41
|
+
recent: number;
|
|
42
|
+
uidNext: number;
|
|
43
|
+
uidValidity: number;
|
|
44
|
+
flags: string[];
|
|
45
|
+
permanentFlags: string[];
|
|
46
|
+
}
|
|
47
|
+
export declare class NativeImapClient {
|
|
48
|
+
private transport;
|
|
49
|
+
private transportFactory;
|
|
50
|
+
private config;
|
|
51
|
+
private buffer;
|
|
52
|
+
private pendingCommand;
|
|
53
|
+
private capabilities;
|
|
54
|
+
private _connected;
|
|
55
|
+
private idleTag;
|
|
56
|
+
private idleCallback;
|
|
57
|
+
private verbose;
|
|
58
|
+
private selectedMailbox;
|
|
59
|
+
private mailboxInfo;
|
|
60
|
+
private greetingResolve;
|
|
61
|
+
/** Callback for waitForContinuation — set when waiting for "+" response */
|
|
62
|
+
private continuationResolve;
|
|
63
|
+
constructor(config: ImapClientConfig, transportFactory: TransportFactory);
|
|
64
|
+
get connected(): boolean;
|
|
65
|
+
connect(): Promise<void>;
|
|
66
|
+
private readGreeting;
|
|
67
|
+
private authenticate;
|
|
68
|
+
private starttls;
|
|
69
|
+
capability(): Promise<Set<string>>;
|
|
70
|
+
private parseCapabilities;
|
|
71
|
+
logout(): Promise<void>;
|
|
72
|
+
select(mailbox: string): Promise<MailboxInfo>;
|
|
73
|
+
examine(mailbox: string): Promise<MailboxInfo>;
|
|
74
|
+
/** Close the currently selected mailbox */
|
|
75
|
+
closeMailbox(): Promise<void>;
|
|
76
|
+
listFolders(): Promise<NativeFolder[]>;
|
|
77
|
+
getStatus(mailbox: string): Promise<proto.StatusData>;
|
|
78
|
+
createMailbox(mailbox: string): Promise<void>;
|
|
79
|
+
deleteMailbox(mailbox: string): Promise<void>;
|
|
80
|
+
renameMailbox(from: string, to: string): Promise<void>;
|
|
81
|
+
/** Fetch messages by UID range */
|
|
82
|
+
fetchMessages(range: string, options?: {
|
|
83
|
+
source?: boolean;
|
|
84
|
+
headers?: boolean;
|
|
85
|
+
}): Promise<NativeFetchedMessage[]>;
|
|
86
|
+
/** Fetch messages since a UID */
|
|
87
|
+
fetchSinceUid(sinceUid: number, options?: {
|
|
88
|
+
source?: boolean;
|
|
89
|
+
}, onChunk?: (msgs: NativeFetchedMessage[]) => void): Promise<NativeFetchedMessage[]>;
|
|
90
|
+
/** Fetch messages by date range. Optional onChunk callback receives each batch as it arrives. */
|
|
91
|
+
fetchByDate(since: Date, before?: Date, options?: {
|
|
92
|
+
source?: boolean;
|
|
93
|
+
}, onChunk?: (msgs: NativeFetchedMessage[]) => void): Promise<NativeFetchedMessage[]>;
|
|
94
|
+
/** Fetch a single message by UID */
|
|
95
|
+
fetchMessage(uid: number, options?: {
|
|
96
|
+
source?: boolean;
|
|
97
|
+
}): Promise<NativeFetchedMessage | null>;
|
|
98
|
+
/** Get all UIDs in the current mailbox */
|
|
99
|
+
getUids(): Promise<number[]>;
|
|
100
|
+
/** UID SEARCH */
|
|
101
|
+
search(criteria: string): Promise<number[]>;
|
|
102
|
+
/** Set flags on a message */
|
|
103
|
+
addFlags(uid: number, flags: string[]): Promise<void>;
|
|
104
|
+
/** Remove flags from a message */
|
|
105
|
+
removeFlags(uid: number, flags: string[]): Promise<void>;
|
|
106
|
+
/** Copy a message to another mailbox */
|
|
107
|
+
copyMessage(uid: number, destination: string): Promise<void>;
|
|
108
|
+
/** Move a message to another mailbox (MOVE or COPY+DELETE) */
|
|
109
|
+
moveMessage(uid: number, destination: string): Promise<void>;
|
|
110
|
+
/** Delete a message by UID (flag + expunge) */
|
|
111
|
+
deleteMessage(uid: number): Promise<void>;
|
|
112
|
+
/** Expunge deleted messages */
|
|
113
|
+
expunge(): Promise<void>;
|
|
114
|
+
/** Append a message to a mailbox */
|
|
115
|
+
appendMessage(mailbox: string, message: string | Uint8Array, flags?: string[]): Promise<number | null>;
|
|
116
|
+
startIdle(onNewMail: (count: number) => void): Promise<() => Promise<void>>;
|
|
117
|
+
getMessageCount(mailbox: string): Promise<number>;
|
|
118
|
+
/** Inactivity timeout — how long to wait with NO data before declaring the connection dead.
|
|
119
|
+
* This is NOT a wall-clock timeout. Timer resets every time data arrives from the server.
|
|
120
|
+
* A large FETCH returning data continuously will never timeout. */
|
|
121
|
+
private inactivityTimeout;
|
|
122
|
+
/** Fetch chunk sizes — start small for quick first paint, ramp up for throughput */
|
|
123
|
+
private static INITIAL_CHUNK_SIZE;
|
|
124
|
+
private static MAX_CHUNK_SIZE;
|
|
125
|
+
/** Active command timer — reset by handleData on every data arrival */
|
|
126
|
+
private commandTimer;
|
|
127
|
+
private sendCommand;
|
|
128
|
+
private waitForContinuation;
|
|
129
|
+
private waitForTagged;
|
|
130
|
+
private handleData;
|
|
131
|
+
private processBuffer;
|
|
132
|
+
private handleUntaggedResponse;
|
|
133
|
+
private parseFetchResponses;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=imap-native.d.ts.map
|