@bobfrankston/iflow 1.0.33 → 1.0.37

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.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Bridge transport for IMAP — uses mailxapi.tcp from the native shell.
3
+ * For Android (MAUI) and potentially desktop WebView.
4
+ *
5
+ * The native shell provides:
6
+ * mailxapi.tcp.connect(host, port, tls) → streamId
7
+ * mailxapi.tcp.write(streamId, data)
8
+ * mailxapi.tcp.onData(streamId, callback)
9
+ * mailxapi.tcp.upgradeTLS(streamId, servername)
10
+ * mailxapi.tcp.close(streamId)
11
+ *
12
+ * TLS is handled natively (C# SslStream) — 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 mailxapi.tcp from the native shell.
3
+ * For Android (MAUI) and potentially desktop WebView.
4
+ *
5
+ * The native shell provides:
6
+ * mailxapi.tcp.connect(host, port, tls) → streamId
7
+ * mailxapi.tcp.write(streamId, data)
8
+ * mailxapi.tcp.onData(streamId, callback)
9
+ * mailxapi.tcp.upgradeTLS(streamId, servername)
10
+ * mailxapi.tcp.close(streamId)
11
+ *
12
+ * TLS is handled natively (C# SslStream) — 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 mailxapi.tcp.connect(host, port, tls);
23
+ this._connected = true;
24
+ mailxapi.tcp.onData(this.streamId, (data) => {
25
+ if (this.dataHandler)
26
+ this.dataHandler(data);
27
+ });
28
+ mailxapi.tcp.onClose(this.streamId, (hadError) => {
29
+ this._connected = false;
30
+ if (this.closeHandler)
31
+ this.closeHandler(hadError);
32
+ });
33
+ mailxapi.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 mailxapi.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 mailxapi.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
+ mailxapi.tcp.close(this.streamId);
56
+ this.streamId = null;
57
+ }
58
+ }
59
+ }
60
+ //# sourceMappingURL=bridge-transport.js.map
package/imaplib/gmail.js CHANGED
@@ -6,7 +6,12 @@ import path from 'path';
6
6
  import fs from 'fs';
7
7
  const GMAIL_SERVER = 'imap.gmail.com';
8
8
  const GMAIL_PORT = 993;
9
- const GMAIL_SCOPE = 'https://mail.google.com/';
9
+ // All Google scopes in one token — one consent, one refresh
10
+ const GMAIL_SCOPE = [
11
+ 'https://mail.google.com/',
12
+ 'https://www.googleapis.com/auth/contacts.readonly',
13
+ // 'https://www.googleapis.com/auth/calendar.readonly', // future
14
+ ].join(' ');
10
15
  // Serialize concurrent OAuth attempts per username to avoid port conflicts (EADDRINUSE)
11
16
  const pendingAuth = new Map();
12
17
  // Get the directory where this module is located
@@ -0,0 +1,77 @@
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
+ /** Search messages in a mailbox */
53
+ searchMessages(mailbox: string, criteria: any): Promise<number[]>;
54
+ /** Delete a message by UID */
55
+ deleteMessageByUid(mailbox: string, uid: number): Promise<void>;
56
+ /** Move a message between mailboxes (same server) */
57
+ moveMessage(msg: any, fromMailbox: string, toMailbox: string): Promise<void>;
58
+ /** Add flags to a message */
59
+ addFlags(mailbox: string, uid: number, flags: string[]): Promise<void>;
60
+ /** Remove flags from a message */
61
+ removeFlags(mailbox: string, uid: number, flags: string[]): Promise<void>;
62
+ /** Create a mailbox */
63
+ createmailbox(name: string): Promise<void>;
64
+ /** Append a message to a mailbox */
65
+ appendMessage(mailbox: string, message: string | Buffer, flags?: string[]): Promise<number | null>;
66
+ /** Watch a mailbox for new messages (IDLE) */
67
+ watchMailbox(mailbox: string, onNew: (count: number) => void): Promise<() => Promise<void>>;
68
+ /** Copy a message to another server (cross-account) */
69
+ moveMessageToServer(msg: any, fromMailbox: string, targetClient: CompatImapClient, toMailbox: string): Promise<void>;
70
+ /** Rename a mailbox (via native access) */
71
+ renameMailbox(from: string, to: string): Promise<void>;
72
+ /** Delete a mailbox */
73
+ deleteMailbox(name: string): Promise<void>;
74
+ /** Get flags for a UID — returns string[] for compatibility with old client */
75
+ getFlags(mailbox: string, uid: number): Promise<string[]>;
76
+ }
77
+ //# sourceMappingURL=imap-compat.d.ts.map
@@ -0,0 +1,238 @@
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
+ /** Search messages in a mailbox */
135
+ async searchMessages(mailbox, criteria) {
136
+ await this.ensureConnected();
137
+ await this.native.select(mailbox);
138
+ // Convert object criteria to IMAP search string
139
+ const parts = [];
140
+ if (criteria.from)
141
+ parts.push(`FROM "${criteria.from}"`);
142
+ if (criteria.to)
143
+ parts.push(`TO "${criteria.to}"`);
144
+ if (criteria.subject)
145
+ parts.push(`SUBJECT "${criteria.subject}"`);
146
+ if (criteria.body)
147
+ parts.push(`BODY "${criteria.body}"`);
148
+ if (criteria.since)
149
+ parts.push(`SINCE ${formatDate(criteria.since)}`);
150
+ if (criteria.before)
151
+ parts.push(`BEFORE ${formatDate(criteria.before)}`);
152
+ const searchStr = parts.length > 0 ? parts.join(" ") : "ALL";
153
+ const uids = await this.native.search(searchStr);
154
+ await this.native.closeMailbox();
155
+ return uids;
156
+ }
157
+ /** Delete a message by UID */
158
+ async deleteMessageByUid(mailbox, uid) {
159
+ await this.ensureConnected();
160
+ await this.native.select(mailbox);
161
+ await this.native.deleteMessage(uid);
162
+ await this.native.closeMailbox();
163
+ }
164
+ /** Move a message between mailboxes (same server) */
165
+ async moveMessage(msg, fromMailbox, toMailbox) {
166
+ await this.ensureConnected();
167
+ await this.native.select(fromMailbox);
168
+ await this.native.moveMessage(msg.uid, toMailbox);
169
+ await this.native.closeMailbox();
170
+ }
171
+ /** Add flags to a message */
172
+ async addFlags(mailbox, uid, flags) {
173
+ await this.ensureConnected();
174
+ await this.native.select(mailbox);
175
+ await this.native.addFlags(uid, flags);
176
+ await this.native.closeMailbox();
177
+ }
178
+ /** Remove flags from a message */
179
+ async removeFlags(mailbox, uid, flags) {
180
+ await this.ensureConnected();
181
+ await this.native.select(mailbox);
182
+ await this.native.removeFlags(uid, flags);
183
+ await this.native.closeMailbox();
184
+ }
185
+ /** Create a mailbox */
186
+ async createmailbox(name) {
187
+ await this.ensureConnected();
188
+ await this.native.createMailbox(name);
189
+ }
190
+ /** Append a message to a mailbox */
191
+ async appendMessage(mailbox, message, flags = []) {
192
+ await this.ensureConnected();
193
+ const data = typeof message === "string" ? message : message.toString("utf-8");
194
+ return this.native.appendMessage(mailbox, data, flags);
195
+ }
196
+ /** Watch a mailbox for new messages (IDLE) */
197
+ async watchMailbox(mailbox, onNew) {
198
+ await this.ensureConnected();
199
+ await this.native.select(mailbox);
200
+ return this.native.startIdle(onNew);
201
+ }
202
+ /** Copy a message to another server (cross-account) */
203
+ async moveMessageToServer(msg, fromMailbox, targetClient, toMailbox) {
204
+ // Fetch source, append to target, delete from source
205
+ await this.ensureConnected();
206
+ await this.native.select(fromMailbox);
207
+ const fetched = await this.native.fetchMessage(msg.uid, { source: true });
208
+ if (!fetched || !fetched.source)
209
+ throw new Error(`Message UID ${msg.uid} not found`);
210
+ await targetClient.ensureConnected();
211
+ await targetClient.native.appendMessage(toMailbox, fetched.source, [...fetched.flags]);
212
+ await this.native.deleteMessage(msg.uid);
213
+ await this.native.closeMailbox();
214
+ }
215
+ /** Rename a mailbox (via native access) */
216
+ async renameMailbox(from, to) {
217
+ await this.ensureConnected();
218
+ await this.native.renameMailbox(from, to);
219
+ }
220
+ /** Delete a mailbox */
221
+ async deleteMailbox(name) {
222
+ await this.ensureConnected();
223
+ await this.native.deleteMailbox(name);
224
+ }
225
+ /** Get flags for a UID — returns string[] for compatibility with old client */
226
+ async getFlags(mailbox, uid) {
227
+ await this.ensureConnected();
228
+ await this.native.select(mailbox);
229
+ const msg = await this.native.fetchMessage(uid, { source: false });
230
+ await this.native.closeMailbox();
231
+ return msg?.flags ? [...msg.flags] : [];
232
+ }
233
+ }
234
+ function formatDate(d) {
235
+ const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
236
+ return `${d.getDate()}-${months[d.getMonth()]}-${d.getFullYear()}`;
237
+ }
238
+ //# sourceMappingURL=imap-compat.js.map
@@ -0,0 +1,123 @@
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
+ constructor(config: ImapClientConfig, transportFactory: TransportFactory);
61
+ get connected(): boolean;
62
+ connect(): Promise<void>;
63
+ private readGreeting;
64
+ private authenticate;
65
+ private starttls;
66
+ capability(): Promise<Set<string>>;
67
+ private parseCapabilities;
68
+ logout(): Promise<void>;
69
+ select(mailbox: string): Promise<MailboxInfo>;
70
+ examine(mailbox: string): Promise<MailboxInfo>;
71
+ /** Close the currently selected mailbox */
72
+ closeMailbox(): Promise<void>;
73
+ listFolders(): Promise<NativeFolder[]>;
74
+ getStatus(mailbox: string): Promise<proto.StatusData>;
75
+ createMailbox(mailbox: string): Promise<void>;
76
+ deleteMailbox(mailbox: string): Promise<void>;
77
+ renameMailbox(from: string, to: string): Promise<void>;
78
+ /** Fetch messages by UID range */
79
+ fetchMessages(range: string, options?: {
80
+ source?: boolean;
81
+ headers?: boolean;
82
+ }): Promise<NativeFetchedMessage[]>;
83
+ /** Fetch messages since a UID */
84
+ fetchSinceUid(sinceUid: number, options?: {
85
+ source?: boolean;
86
+ }): Promise<NativeFetchedMessage[]>;
87
+ /** Fetch messages by date range */
88
+ fetchByDate(since: Date, before?: Date, options?: {
89
+ source?: boolean;
90
+ }): Promise<NativeFetchedMessage[]>;
91
+ /** Fetch a single message by UID */
92
+ fetchMessage(uid: number, options?: {
93
+ source?: boolean;
94
+ }): Promise<NativeFetchedMessage | null>;
95
+ /** Get all UIDs in the current mailbox */
96
+ getUids(): Promise<number[]>;
97
+ /** UID SEARCH */
98
+ search(criteria: string): Promise<number[]>;
99
+ /** Set flags on a message */
100
+ addFlags(uid: number, flags: string[]): Promise<void>;
101
+ /** Remove flags from a message */
102
+ removeFlags(uid: number, flags: string[]): Promise<void>;
103
+ /** Copy a message to another mailbox */
104
+ copyMessage(uid: number, destination: string): Promise<void>;
105
+ /** Move a message to another mailbox (MOVE or COPY+DELETE) */
106
+ moveMessage(uid: number, destination: string): Promise<void>;
107
+ /** Delete a message by UID (flag + expunge) */
108
+ deleteMessage(uid: number): Promise<void>;
109
+ /** Expunge deleted messages */
110
+ expunge(): Promise<void>;
111
+ /** Append a message to a mailbox */
112
+ appendMessage(mailbox: string, message: string | Uint8Array, flags?: string[]): Promise<number | null>;
113
+ startIdle(onNewMail: (count: number) => void): Promise<() => Promise<void>>;
114
+ getMessageCount(mailbox: string): Promise<number>;
115
+ private sendCommand;
116
+ private waitForContinuation;
117
+ private waitForTagged;
118
+ private handleData;
119
+ private processBuffer;
120
+ private handleUntaggedResponse;
121
+ private parseFetchResponses;
122
+ }
123
+ //# sourceMappingURL=imap-native.d.ts.map