@gera2ld/imap 0.0.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/README.md +273 -0
- package/dist/client.d.ts +129 -0
- package/dist/connector.d.ts +20 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +642 -0
- package/dist/parser.d.ts +22 -0
- package/dist/parser.test.d.ts +1 -0
- package/dist/types.d.ts +44 -0
- package/dist/util.d.ts +19 -0
- package/dist/util.test.d.ts +1 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# IMAP Client
|
|
2
|
+
|
|
3
|
+
A lightweight IMAP client for Node.js that supports real-time email monitoring using IDLE mode.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
### Basic Example
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { ImapClient } from '@gera2ld/imap';
|
|
11
|
+
|
|
12
|
+
const client = new ImapClient({
|
|
13
|
+
host: 'imap.example.com',
|
|
14
|
+
port: 993,
|
|
15
|
+
secure: true,
|
|
16
|
+
user: 'your-email@example.com',
|
|
17
|
+
password: 'your-password',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Connect and authenticate
|
|
21
|
+
await client.connect();
|
|
22
|
+
|
|
23
|
+
// Select a mailbox
|
|
24
|
+
await client.selectMailbox('INBOX');
|
|
25
|
+
|
|
26
|
+
// Perform operations like fetching messages
|
|
27
|
+
const messages = await client.fetchMessages('1:*', ['UID', 'ENVELOPE']);
|
|
28
|
+
console.log(messages);
|
|
29
|
+
|
|
30
|
+
// Disconnect
|
|
31
|
+
await client.disconnect();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Watching for New Mails and Deletions Using IDLE Mode
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { ImapClient } from '@gera2ld/imap';
|
|
38
|
+
|
|
39
|
+
async function watchEmails() {
|
|
40
|
+
const client = new ImapClient({
|
|
41
|
+
host: 'imap.example.com',
|
|
42
|
+
port: 993,
|
|
43
|
+
secure: true,
|
|
44
|
+
user: 'your-email@example.com',
|
|
45
|
+
password: 'your-password',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Connect and authenticate
|
|
50
|
+
await client.connect();
|
|
51
|
+
|
|
52
|
+
// Select the mailbox to watch (e.g., INBOX)
|
|
53
|
+
await client.selectMailbox('INBOX');
|
|
54
|
+
|
|
55
|
+
// Listen for new emails
|
|
56
|
+
client.on('newmail', (info) => {
|
|
57
|
+
console.log(`New mail received! Total messages in mailbox: ${info.count}`);
|
|
58
|
+
// Handle new email notification
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Listen for message deletions
|
|
62
|
+
client.on('expunge', (info) => {
|
|
63
|
+
console.log(`Message deleted! Sequence number: ${info.seq}`);
|
|
64
|
+
// Handle message deletion
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Listen for other changes
|
|
68
|
+
client.on('fetch', (message) => {
|
|
69
|
+
console.log(`Message ${message.seq} updated with attributes:`, message.attributes);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Start listening for real-time updates using IDLE mode
|
|
73
|
+
await client.startIdle();
|
|
74
|
+
console.log('Now listening for new emails and deletions...');
|
|
75
|
+
|
|
76
|
+
// Keep the program running to continue listening
|
|
77
|
+
// Set up graceful shutdown
|
|
78
|
+
const handleShutdown = async () => {
|
|
79
|
+
console.log('Shutting down gracefully...');
|
|
80
|
+
try {
|
|
81
|
+
// Stop listening for updates
|
|
82
|
+
await client.stopIdle();
|
|
83
|
+
// Disconnect from the server
|
|
84
|
+
await client.disconnect();
|
|
85
|
+
console.log('Disconnected from IMAP server');
|
|
86
|
+
process.exit(0);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error('Error during shutdown:', err);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Listen for termination signals
|
|
94
|
+
process.on('SIGINT', handleShutdown);
|
|
95
|
+
process.on('SIGTERM', handleShutdown);
|
|
96
|
+
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('Error:', error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Run the example
|
|
103
|
+
watchEmails();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Authentication
|
|
107
|
+
|
|
108
|
+
The client supports multiple authentication methods:
|
|
109
|
+
|
|
110
|
+
### Password Authentication
|
|
111
|
+
```typescript
|
|
112
|
+
const client = new ImapClient({
|
|
113
|
+
host: 'imap.example.com',
|
|
114
|
+
port: 993,
|
|
115
|
+
secure: true,
|
|
116
|
+
user: 'your-email@example.com',
|
|
117
|
+
password: 'your-password',
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### XOAUTH2 Authentication
|
|
122
|
+
```typescript
|
|
123
|
+
const client = new ImapClient({
|
|
124
|
+
host: 'imap.example.com',
|
|
125
|
+
port: 993,
|
|
126
|
+
secure: true,
|
|
127
|
+
user: 'your-email@example.com',
|
|
128
|
+
accessToken: 'your-oauth-token',
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Or with a function that returns a token
|
|
132
|
+
const client = new ImapClient({
|
|
133
|
+
host: 'imap.example.com',
|
|
134
|
+
port: 993,
|
|
135
|
+
secure: true,
|
|
136
|
+
user: 'your-email@example.com',
|
|
137
|
+
accessToken: async () => {
|
|
138
|
+
// Return a fresh access token
|
|
139
|
+
return await getFreshAccessToken();
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Available Events
|
|
145
|
+
|
|
146
|
+
- `newmail`: Emitted when new messages arrive in the mailbox
|
|
147
|
+
- `expunge`: Emitted when messages are deleted from the mailbox
|
|
148
|
+
- `fetch`: Emitted when message attributes are updated
|
|
149
|
+
- `recent`: Emitted when the number of recent messages changes
|
|
150
|
+
- `flags`: Emitted when message flags change
|
|
151
|
+
- `list`: Emitted when mailbox list information is received
|
|
152
|
+
- `response`: Emitted for raw IMAP responses
|
|
153
|
+
- `error`: Emitted when an error occurs
|
|
154
|
+
- `close`: Emitted when the connection is closed
|
|
155
|
+
|
|
156
|
+
## API
|
|
157
|
+
|
|
158
|
+
### ImapClient(options)
|
|
159
|
+
|
|
160
|
+
Creates a new IMAP client instance.
|
|
161
|
+
|
|
162
|
+
#### Options
|
|
163
|
+
|
|
164
|
+
- `host` (string): The IMAP server hostname
|
|
165
|
+
- `port` (number): The IMAP server port (typically 143 for non-secure, 993 for secure)
|
|
166
|
+
- `secure` (boolean): Whether to use TLS/SSL connection
|
|
167
|
+
- `user` (string): The username/email address
|
|
168
|
+
- `password` (string, optional): The password for authentication
|
|
169
|
+
- `accessToken` (string | function, optional): Access token for XOAUTH2 authentication
|
|
170
|
+
|
|
171
|
+
### connect()
|
|
172
|
+
|
|
173
|
+
Connects to the IMAP server and performs authentication.
|
|
174
|
+
|
|
175
|
+
### disconnect()
|
|
176
|
+
|
|
177
|
+
Disconnects from the IMAP server and cleans up resources.
|
|
178
|
+
|
|
179
|
+
### selectMailbox(mailbox)
|
|
180
|
+
|
|
181
|
+
Selects a mailbox for subsequent operations.
|
|
182
|
+
|
|
183
|
+
### listMailboxes()
|
|
184
|
+
|
|
185
|
+
Lists all mailboxes on the server.
|
|
186
|
+
|
|
187
|
+
### fetchMessages(sequence, [items], [options])
|
|
188
|
+
|
|
189
|
+
Fetches messages from the currently selected mailbox.
|
|
190
|
+
- `items` (optional): The message data items to fetch (e.g., ['UID', 'ENVELOPE', 'BODY.PEEK[]']). Defaults to ['UID', 'ENVELOPE', 'FLAGS']
|
|
191
|
+
- `options` (optional): Additional options for the fetch operation
|
|
192
|
+
- `uid` (boolean, default: true): Whether to use UID-based fetch
|
|
193
|
+
|
|
194
|
+
### searchMessages(criteria, [options])
|
|
195
|
+
|
|
196
|
+
Searches messages in the currently selected mailbox based on the given criteria.
|
|
197
|
+
- `options` (optional): Additional options for the search operation
|
|
198
|
+
- `uid` (boolean, default: true): Whether to use UID-based search
|
|
199
|
+
|
|
200
|
+
### setFlags(sequence, operation, flags, [options])
|
|
201
|
+
|
|
202
|
+
Sets flags on messages in the currently selected mailbox.
|
|
203
|
+
- `operation`: The flag operation ('+' to add flags, '-' to remove flags, '=' to replace all flags)
|
|
204
|
+
- `flags`: Array of flags to operate on (e.g., ['\\Seen', '\\Flagged'])
|
|
205
|
+
- `options` (optional): Additional options for the operation
|
|
206
|
+
- `uid` (boolean, default: true): Whether to use UID-based operation
|
|
207
|
+
|
|
208
|
+
### markAsSeen(sequence, [options])
|
|
209
|
+
|
|
210
|
+
Marks messages as seen in the currently selected mailbox.
|
|
211
|
+
- `options` (optional): Additional options for the operation
|
|
212
|
+
- `uid` (boolean, default: true): Whether to use UID-based operation
|
|
213
|
+
|
|
214
|
+
### markAsUnseen(sequence, [options])
|
|
215
|
+
|
|
216
|
+
Marks messages as unseen in the currently selected mailbox.
|
|
217
|
+
- `options` (optional): Additional options for the operation
|
|
218
|
+
- `uid` (boolean, default: true): Whether to use UID-based operation
|
|
219
|
+
|
|
220
|
+
### deleteMessages(sequence, [options])
|
|
221
|
+
|
|
222
|
+
Deletes messages in the currently selected mailbox.
|
|
223
|
+
- `options` (optional): Additional options for the operation
|
|
224
|
+
- `uid` (boolean, default: true): Whether to use UID-based operation
|
|
225
|
+
|
|
226
|
+
### moveMessages(sequence, destinationMailbox, [options])
|
|
227
|
+
|
|
228
|
+
Moves messages to a different mailbox.
|
|
229
|
+
- `options` (optional): Additional options for the operation
|
|
230
|
+
- `uid` (boolean, default: true): Whether to use UID-based operation
|
|
231
|
+
|
|
232
|
+
### startIdle()
|
|
233
|
+
|
|
234
|
+
Manually starts IDLE mode for real-time updates.
|
|
235
|
+
|
|
236
|
+
### stopIdle()
|
|
237
|
+
|
|
238
|
+
Manually stops IDLE mode.
|
|
239
|
+
|
|
240
|
+
## Utilities
|
|
241
|
+
|
|
242
|
+
The package also exports utility functions for working with IMAP:
|
|
243
|
+
|
|
244
|
+
### formatDateForImap(date)
|
|
245
|
+
|
|
246
|
+
Formats a Date object to the IMAP date format used in SEARCH commands (e.g., SINCE, ON).
|
|
247
|
+
The format is "DD-MMM-YYYY" (e.g., "01-Jan-2023").
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
import { formatDateForImap } from '@gera2ld/imap';
|
|
251
|
+
|
|
252
|
+
const date = new Date('2023-01-15');
|
|
253
|
+
const imapDate = formatDateForImap(date); // "15-Jan-2023"
|
|
254
|
+
|
|
255
|
+
// Use in search commands
|
|
256
|
+
await client.searchMessages(`SINCE ${imapDate}`);
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### formatDateTimeForImap(date)
|
|
260
|
+
|
|
261
|
+
Formats a Date object to the IMAP datetime format used in SEARCH commands.
|
|
262
|
+
The format is "DD-MMM-YYYY HH:MM:SS +ZZZZ" (e.g., "01-Jan-2023 12:00:00 +0000").
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { formatDateTimeForImap } from '@gera2ld/imap';
|
|
266
|
+
|
|
267
|
+
const date = new Date('2023-01-15T14:30:00Z');
|
|
268
|
+
const imapDateTime = formatDateTimeForImap(date); // "15-Jan-2023 14:30:00 +0000"
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## License
|
|
272
|
+
|
|
273
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { ParsedResponse } from './parser';
|
|
3
|
+
import type { FetchMessage, ImapClientOptions, MailboxInfo } from './types';
|
|
4
|
+
type ImapClientEvents = {
|
|
5
|
+
newmail: [{
|
|
6
|
+
count: number;
|
|
7
|
+
}];
|
|
8
|
+
expunge: [{
|
|
9
|
+
seq: number;
|
|
10
|
+
}];
|
|
11
|
+
recent: [{
|
|
12
|
+
count: number;
|
|
13
|
+
}];
|
|
14
|
+
flags: [{
|
|
15
|
+
flags: any[];
|
|
16
|
+
}];
|
|
17
|
+
response: [ParsedResponse];
|
|
18
|
+
send: [string];
|
|
19
|
+
error: [Error];
|
|
20
|
+
close: [];
|
|
21
|
+
};
|
|
22
|
+
export declare class ImapClient extends EventEmitter<ImapClientEvents> {
|
|
23
|
+
private options;
|
|
24
|
+
private connector;
|
|
25
|
+
private parser;
|
|
26
|
+
private tagCounter;
|
|
27
|
+
private tagPrefix;
|
|
28
|
+
private pendingCommand;
|
|
29
|
+
constructor(options: ImapClientOptions);
|
|
30
|
+
private nextTag;
|
|
31
|
+
/**
|
|
32
|
+
* Register a pending command and return a promise that resolves when the response arrives.
|
|
33
|
+
* Throws if another command is already pending.
|
|
34
|
+
*/
|
|
35
|
+
private registerPendingCommand;
|
|
36
|
+
/**
|
|
37
|
+
* Send a raw command to the server.
|
|
38
|
+
*/
|
|
39
|
+
private sendCommand;
|
|
40
|
+
/**
|
|
41
|
+
* Execute a command and wait for its response.
|
|
42
|
+
* Processes matching untagged responses as they arrive via onResponse, filters out nulls.
|
|
43
|
+
* Non-matching responses are emitted as events.
|
|
44
|
+
*
|
|
45
|
+
* @param command - The IMAP command to send (e.g., 'LIST "" "*"', 'UID SEARCH ALL')
|
|
46
|
+
* @param matchCommand - Optional command name to match in responses (e.g., 'LIST', 'SEARCH', 'FETCH'). Required when onResponse is provided.
|
|
47
|
+
* @param onResponse - Optional function to transform each matching untagged response as it arrives. Return null to skip.
|
|
48
|
+
* @returns Promise with tagged response and array of transformed items
|
|
49
|
+
*/
|
|
50
|
+
private executeCommand;
|
|
51
|
+
/**
|
|
52
|
+
* Handle incoming data lines from the connector.
|
|
53
|
+
*/
|
|
54
|
+
private handleResponse;
|
|
55
|
+
private handleAsyncNotification;
|
|
56
|
+
private parseFetchAttributes;
|
|
57
|
+
/**
|
|
58
|
+
* Connect to the IMAP server and authenticate.
|
|
59
|
+
*/
|
|
60
|
+
connect(): Promise<void>;
|
|
61
|
+
private processResponses;
|
|
62
|
+
/**
|
|
63
|
+
* Authenticate with the server.
|
|
64
|
+
*/
|
|
65
|
+
login(): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* List all mailboxes.
|
|
68
|
+
*/
|
|
69
|
+
listMailboxes(): Promise<MailboxInfo[]>;
|
|
70
|
+
/**
|
|
71
|
+
* Select a mailbox.
|
|
72
|
+
*/
|
|
73
|
+
selectMailbox(mailbox: string): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Search messages.
|
|
76
|
+
*/
|
|
77
|
+
searchMessages(criteria: string, options?: Partial<{
|
|
78
|
+
uid: boolean;
|
|
79
|
+
}>): Promise<number[]>;
|
|
80
|
+
/**
|
|
81
|
+
* Fetch messages.
|
|
82
|
+
*/
|
|
83
|
+
fetchMessages(sequence: string, items?: string[], options?: Partial<{
|
|
84
|
+
uid: boolean;
|
|
85
|
+
}>): Promise<FetchMessage[]>;
|
|
86
|
+
/**
|
|
87
|
+
* Set flags on messages.
|
|
88
|
+
*/
|
|
89
|
+
setFlags(sequence: string, operation: '+' | '-' | '=', flags: string[], options?: Partial<{
|
|
90
|
+
uid: boolean;
|
|
91
|
+
}>): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Mark messages as seen.
|
|
94
|
+
*/
|
|
95
|
+
markAsSeen(sequence: string, options?: Partial<{
|
|
96
|
+
uid: boolean;
|
|
97
|
+
}>): Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* Mark messages as unseen.
|
|
100
|
+
*/
|
|
101
|
+
markAsUnseen(sequence: string, options?: Partial<{
|
|
102
|
+
uid: boolean;
|
|
103
|
+
}>): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Delete messages.
|
|
106
|
+
*/
|
|
107
|
+
deleteMessages(sequence: string, options?: Partial<{
|
|
108
|
+
uid: boolean;
|
|
109
|
+
}>): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* Move messages to another mailbox.
|
|
112
|
+
*/
|
|
113
|
+
moveMessages(sequence: string, destinationMailbox: string, options?: Partial<{
|
|
114
|
+
uid: boolean;
|
|
115
|
+
}>): Promise<void>;
|
|
116
|
+
/**
|
|
117
|
+
* Start IDLE mode.
|
|
118
|
+
*/
|
|
119
|
+
startIdle(): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* Stop IDLE mode.
|
|
122
|
+
*/
|
|
123
|
+
stopIdle(): Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* Disconnect from the server.
|
|
126
|
+
*/
|
|
127
|
+
disconnect(): Promise<void>;
|
|
128
|
+
}
|
|
129
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BufferReader } from '@gera2ld/common';
|
|
2
|
+
export interface ConnectorOptions {
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
secure: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ConnectorCallbacks {
|
|
8
|
+
onError?: (err: Error) => void;
|
|
9
|
+
onClose?: () => void;
|
|
10
|
+
}
|
|
11
|
+
export declare class Connector {
|
|
12
|
+
private options;
|
|
13
|
+
private _socket;
|
|
14
|
+
reader: BufferReader;
|
|
15
|
+
isConnected: boolean;
|
|
16
|
+
constructor(options: ConnectorOptions);
|
|
17
|
+
connect(callbacks?: ConnectorCallbacks): Promise<void>;
|
|
18
|
+
write(data: string): void;
|
|
19
|
+
disconnect(): void;
|
|
20
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
import { EventEmitter as S } from "node:events";
|
|
2
|
+
import * as T from "node:net";
|
|
3
|
+
import * as $ from "node:tls";
|
|
4
|
+
const b = "=", w = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
5
|
+
let g;
|
|
6
|
+
function I() {
|
|
7
|
+
return g ||= O(w), g;
|
|
8
|
+
}
|
|
9
|
+
function O(r) {
|
|
10
|
+
const t = new Array(255);
|
|
11
|
+
for (let e = 0; e < r.length; e += 1)
|
|
12
|
+
t[r.charCodeAt(e)] = e;
|
|
13
|
+
return t;
|
|
14
|
+
}
|
|
15
|
+
function M(r, t) {
|
|
16
|
+
const e = t[r];
|
|
17
|
+
if (e == null) throw new Error("Unable to parse base64 string.");
|
|
18
|
+
return e;
|
|
19
|
+
}
|
|
20
|
+
function D(r, t, e) {
|
|
21
|
+
let s = "", n = 0;
|
|
22
|
+
for (; n < r.length; ) {
|
|
23
|
+
const o = r[n++], i = r[n++], a = r[n++];
|
|
24
|
+
if (s += t[o >> 2], s += t[(o & 3) << 4 | (i || 0) >> 4], i == null ? s += b : s += t[(i & 15) << 2 | (a || 0) >> 6], a == null) {
|
|
25
|
+
s += b;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
s += t[a & 63];
|
|
29
|
+
}
|
|
30
|
+
return s;
|
|
31
|
+
}
|
|
32
|
+
function v(r, t) {
|
|
33
|
+
let e = r.length;
|
|
34
|
+
for (; e > 0 && r[e - 1] === "="; ) e -= 1;
|
|
35
|
+
const s = Math.ceil(e / 4), n = new Uint8Array(3 * s);
|
|
36
|
+
let o = 0, i = 0, a = 0;
|
|
37
|
+
for (i = 0; i < s; i += 1) {
|
|
38
|
+
let l = 0;
|
|
39
|
+
for (a = 0; a < 4; a += 1) {
|
|
40
|
+
o = i * 4 + a;
|
|
41
|
+
const f = r[o];
|
|
42
|
+
if (!f || f === b) break;
|
|
43
|
+
l |= M(r.charCodeAt(o), t) << (3 - a) * 6;
|
|
44
|
+
}
|
|
45
|
+
n[i * 3] = l >> 16, n[i * 3 + 1] = l >> 8 & 255, n[i * 3 + 2] = l & 255;
|
|
46
|
+
}
|
|
47
|
+
if (o < e - 1 || a === 1) throw new Error("Invalid base64 string");
|
|
48
|
+
let c = 3 * s;
|
|
49
|
+
for (; a < 4; a += 1) c -= 1;
|
|
50
|
+
return n.subarray(0, c);
|
|
51
|
+
}
|
|
52
|
+
function U(r) {
|
|
53
|
+
return D(r, w);
|
|
54
|
+
}
|
|
55
|
+
function y(r) {
|
|
56
|
+
return v(r, I());
|
|
57
|
+
}
|
|
58
|
+
function N(r) {
|
|
59
|
+
return new TextEncoder().encode(r);
|
|
60
|
+
}
|
|
61
|
+
function E(r) {
|
|
62
|
+
return new TextDecoder().decode(r);
|
|
63
|
+
}
|
|
64
|
+
function m() {
|
|
65
|
+
const r = {
|
|
66
|
+
status: "pending"
|
|
67
|
+
};
|
|
68
|
+
return r.promise = new Promise((t, e) => {
|
|
69
|
+
r.resolve = t, r.reject = e;
|
|
70
|
+
}), r.promise.then(
|
|
71
|
+
() => {
|
|
72
|
+
r.status = "resolved";
|
|
73
|
+
},
|
|
74
|
+
() => {
|
|
75
|
+
r.status = "rejected";
|
|
76
|
+
}
|
|
77
|
+
), r;
|
|
78
|
+
}
|
|
79
|
+
const F = new Uint8Array([13, 10]);
|
|
80
|
+
class L {
|
|
81
|
+
buffer = new Uint8Array(0);
|
|
82
|
+
requests = [];
|
|
83
|
+
isClosed = !1;
|
|
84
|
+
read(t = 8192) {
|
|
85
|
+
const e = m();
|
|
86
|
+
return this.requests.push({
|
|
87
|
+
deferred: e,
|
|
88
|
+
handle: () => {
|
|
89
|
+
const s = Math.min(t, this.buffer.length), n = this.buffer.subarray(0, s);
|
|
90
|
+
return this.buffer = this.buffer.subarray(s), e.resolve(n), !0;
|
|
91
|
+
}
|
|
92
|
+
}), this.processBuffer(), e.promise;
|
|
93
|
+
}
|
|
94
|
+
readExact(t) {
|
|
95
|
+
const e = m();
|
|
96
|
+
return this.requests.push({
|
|
97
|
+
deferred: e,
|
|
98
|
+
handle: () => {
|
|
99
|
+
if (t > this.buffer.length) return !1;
|
|
100
|
+
const s = this.buffer.subarray(0, t);
|
|
101
|
+
return this.buffer = this.buffer.subarray(t), e.resolve(s), !0;
|
|
102
|
+
}
|
|
103
|
+
}), this.processBuffer(), e.promise;
|
|
104
|
+
}
|
|
105
|
+
readUntil(t = F) {
|
|
106
|
+
typeof t == "string" && (t = N(t));
|
|
107
|
+
const e = m();
|
|
108
|
+
return this.requests.push({
|
|
109
|
+
deferred: e,
|
|
110
|
+
handle: () => {
|
|
111
|
+
const s = P(this.buffer, t);
|
|
112
|
+
if (s < 0) return !1;
|
|
113
|
+
const n = s + t.length, o = this.buffer.subarray(0, n);
|
|
114
|
+
return this.buffer = this.buffer.subarray(n), e.resolve(o), !0;
|
|
115
|
+
}
|
|
116
|
+
}), this.processBuffer(), e.promise;
|
|
117
|
+
}
|
|
118
|
+
feed(t) {
|
|
119
|
+
if (this.isClosed) throw new Error("Buffer is closed");
|
|
120
|
+
this.buffer = B(this.buffer, t), this.processBuffer();
|
|
121
|
+
}
|
|
122
|
+
close() {
|
|
123
|
+
this.isClosed = !0, this.processBuffer();
|
|
124
|
+
}
|
|
125
|
+
processBuffer() {
|
|
126
|
+
for (; this.requests.length; ) {
|
|
127
|
+
const t = this.requests[0];
|
|
128
|
+
if (!t.handle())
|
|
129
|
+
if (this.isClosed)
|
|
130
|
+
t.deferred.reject(new Error("Buffer is closed"));
|
|
131
|
+
else break;
|
|
132
|
+
this.requests.shift();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function B(r, t) {
|
|
137
|
+
const e = new Uint8Array(r.length + t.length);
|
|
138
|
+
return e.set(r, 0), e.set(t, r.length), e;
|
|
139
|
+
}
|
|
140
|
+
function P(r, t) {
|
|
141
|
+
if (!t.length) return 0;
|
|
142
|
+
for (let e = 0; e <= r.length - t.length; e += 1) {
|
|
143
|
+
let s;
|
|
144
|
+
for (s = 0; s < t.length && r[e + s] === t[s]; s += 1)
|
|
145
|
+
;
|
|
146
|
+
if (s === t.length) return e;
|
|
147
|
+
}
|
|
148
|
+
return -1;
|
|
149
|
+
}
|
|
150
|
+
class R {
|
|
151
|
+
options;
|
|
152
|
+
_socket = null;
|
|
153
|
+
reader = new L();
|
|
154
|
+
isConnected = !1;
|
|
155
|
+
constructor(t) {
|
|
156
|
+
this.options = t;
|
|
157
|
+
}
|
|
158
|
+
async connect(t) {
|
|
159
|
+
return new Promise((e, s) => {
|
|
160
|
+
const n = this.options.secure ? $.connect({
|
|
161
|
+
host: this.options.host,
|
|
162
|
+
port: this.options.port,
|
|
163
|
+
rejectUnauthorized: !1
|
|
164
|
+
}) : T.connect({
|
|
165
|
+
host: this.options.host,
|
|
166
|
+
port: this.options.port
|
|
167
|
+
});
|
|
168
|
+
this._socket = n, n.on("data", (o) => {
|
|
169
|
+
this.reader.feed(o);
|
|
170
|
+
}), n.on("connect", () => {
|
|
171
|
+
this.isConnected = !0, e();
|
|
172
|
+
}), n.on("error", (o) => {
|
|
173
|
+
t?.onError?.(o), s(o);
|
|
174
|
+
}), n.on("close", () => {
|
|
175
|
+
this.isConnected = !1, t?.onClose?.();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
write(t) {
|
|
180
|
+
if (!this._socket)
|
|
181
|
+
throw new Error("Not connected");
|
|
182
|
+
this._socket.write(t);
|
|
183
|
+
}
|
|
184
|
+
disconnect() {
|
|
185
|
+
this._socket && (this._socket.end(), this._socket.destroy(), this._socket = null);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
class j {
|
|
189
|
+
decode(t) {
|
|
190
|
+
return new TextDecoder().decode(t);
|
|
191
|
+
}
|
|
192
|
+
async *parse(t) {
|
|
193
|
+
for (; ; ) {
|
|
194
|
+
const e = await t.readUntil();
|
|
195
|
+
if (e.length === 0) break;
|
|
196
|
+
const s = this.decode(e);
|
|
197
|
+
if (s.startsWith("+ "))
|
|
198
|
+
yield {
|
|
199
|
+
symbol: "+",
|
|
200
|
+
text: s.slice(2)
|
|
201
|
+
};
|
|
202
|
+
else {
|
|
203
|
+
const n = s.split(" ");
|
|
204
|
+
let o = "", i, a;
|
|
205
|
+
s.startsWith("* ") ? (o = "*", n.shift(), isNaN(+n[0]) ? a = n.shift() : a = n.splice(1, 1)[0]) : (i = n.shift(), a = n.shift());
|
|
206
|
+
let c = n.join(" ");
|
|
207
|
+
const l = {
|
|
208
|
+
command: a,
|
|
209
|
+
stack: [[]],
|
|
210
|
+
brackets: []
|
|
211
|
+
};
|
|
212
|
+
for (; c; ) {
|
|
213
|
+
this.parseAttributes(l, c);
|
|
214
|
+
const f = l.stack.at(-1), h = f.at(-1), p = typeof h == "string" && h.match(/^\{(\d+)\}\r\n$/);
|
|
215
|
+
if (p) {
|
|
216
|
+
const x = +p[1], A = this.decode(await t.readExact(x));
|
|
217
|
+
f.pop(), f.push(A);
|
|
218
|
+
}
|
|
219
|
+
if (!l.brackets.length) break;
|
|
220
|
+
c = this.decode(await t.readUntil());
|
|
221
|
+
}
|
|
222
|
+
yield o ? {
|
|
223
|
+
symbol: o,
|
|
224
|
+
command: a,
|
|
225
|
+
attributes: l.stack[0]
|
|
226
|
+
} : {
|
|
227
|
+
symbol: "",
|
|
228
|
+
tag: i,
|
|
229
|
+
command: a,
|
|
230
|
+
attributes: l.stack[0]
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
parseAttributes(t, e) {
|
|
236
|
+
let s = !1, n = !0, o = !1, i = "";
|
|
237
|
+
const a = () => {
|
|
238
|
+
if (!i) return;
|
|
239
|
+
let c;
|
|
240
|
+
isNaN(+i) ? i.toLowerCase() === "nil" ? c = null : c = i : c = +i, t.stack.at(-1).push(c), i = "";
|
|
241
|
+
};
|
|
242
|
+
for (let c = 0; c < e.length; c += 1) {
|
|
243
|
+
const l = e[c];
|
|
244
|
+
if (s)
|
|
245
|
+
l === '"' ? (s = !1, a()) : (l === "\\" && (c += 1), i += e[c]);
|
|
246
|
+
else if (l === '"')
|
|
247
|
+
s = !0;
|
|
248
|
+
else if (l === " ")
|
|
249
|
+
a();
|
|
250
|
+
else if ((n ? "([" : "(").includes(l)) {
|
|
251
|
+
t.brackets.push(l);
|
|
252
|
+
const f = [];
|
|
253
|
+
t.stack.at(-1).push(f), t.stack.push(f), l === "[" && (o = !0);
|
|
254
|
+
} else if ((o ? "])" : ")").includes(l)) {
|
|
255
|
+
const f = t.brackets.pop();
|
|
256
|
+
if (!["()", "[]"].includes(f + l))
|
|
257
|
+
throw new Error("Invalid bracket");
|
|
258
|
+
a(), t.stack.pop(), l === "]" && (o = !1);
|
|
259
|
+
} else
|
|
260
|
+
i += e[c];
|
|
261
|
+
n = !1;
|
|
262
|
+
}
|
|
263
|
+
a();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const k = [
|
|
267
|
+
"Jan",
|
|
268
|
+
"Feb",
|
|
269
|
+
"Mar",
|
|
270
|
+
"Apr",
|
|
271
|
+
"May",
|
|
272
|
+
"Jun",
|
|
273
|
+
"Jul",
|
|
274
|
+
"Aug",
|
|
275
|
+
"Sep",
|
|
276
|
+
"Oct",
|
|
277
|
+
"Nov",
|
|
278
|
+
"Dec"
|
|
279
|
+
];
|
|
280
|
+
function H(r) {
|
|
281
|
+
const t = String(r.getDate()).padStart(2, "0"), e = k[r.getMonth()], s = r.getFullYear();
|
|
282
|
+
return `${t}-${e}-${s}`;
|
|
283
|
+
}
|
|
284
|
+
function z(r) {
|
|
285
|
+
const t = String(r.getDate()).padStart(2, "0"), e = k[r.getMonth()], s = r.getFullYear(), n = String(r.getHours()).padStart(2, "0"), o = String(r.getMinutes()).padStart(2, "0"), i = String(r.getSeconds()).padStart(2, "0"), a = -r.getTimezoneOffset(), c = a >= 0 ? "+" : "-", l = Math.abs(a), f = String(Math.floor(l / 60)).padStart(
|
|
286
|
+
2,
|
|
287
|
+
"0"
|
|
288
|
+
), h = String(l % 60).padStart(2, "0"), p = `${c}${f}${h}`;
|
|
289
|
+
return `${t}-${e}-${s} ${n}:${o}:${i} ${p}`;
|
|
290
|
+
}
|
|
291
|
+
function d(r) {
|
|
292
|
+
return !Array.isArray(r) || r.length < 4 ? {} : {
|
|
293
|
+
name: C(r[0] ?? "") || void 0,
|
|
294
|
+
adl: r[1] ?? void 0,
|
|
295
|
+
mailbox: r[2] ?? void 0,
|
|
296
|
+
host: r[3] ?? void 0
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function C(r) {
|
|
300
|
+
return r.split(/\s+/).map((t) => {
|
|
301
|
+
if (t.startsWith("=?") && t.endsWith("?=")) {
|
|
302
|
+
const [e, s, n] = t.slice(2, -2).split("?");
|
|
303
|
+
if (e.toLowerCase() !== "utf-8")
|
|
304
|
+
throw new Error(`Charset is not supported: ${e}`);
|
|
305
|
+
let o;
|
|
306
|
+
if (s === "B")
|
|
307
|
+
o = y(n);
|
|
308
|
+
else {
|
|
309
|
+
const i = [];
|
|
310
|
+
for (let a = 0; a < n.length; a += 1)
|
|
311
|
+
if (n[a] === "=") {
|
|
312
|
+
const c = n.slice(a + 1, a + 3);
|
|
313
|
+
i.push(parseInt(c, 16)), a += 2;
|
|
314
|
+
} else
|
|
315
|
+
i.push(n.charCodeAt(a));
|
|
316
|
+
o = new Uint8Array(i);
|
|
317
|
+
}
|
|
318
|
+
return E(o);
|
|
319
|
+
}
|
|
320
|
+
return t;
|
|
321
|
+
}).join("");
|
|
322
|
+
}
|
|
323
|
+
const u = { uid: !0 };
|
|
324
|
+
function _(r, t) {
|
|
325
|
+
const e = `user=${r}auth=Bearer ${t}`;
|
|
326
|
+
return U(new TextEncoder().encode(e));
|
|
327
|
+
}
|
|
328
|
+
class G extends S {
|
|
329
|
+
options;
|
|
330
|
+
connector;
|
|
331
|
+
parser;
|
|
332
|
+
tagCounter = 0;
|
|
333
|
+
tagPrefix = "A";
|
|
334
|
+
pendingCommand = null;
|
|
335
|
+
constructor(t) {
|
|
336
|
+
super(), this.options = t, this.parser = new j(), this.connector = new R({
|
|
337
|
+
host: t.host,
|
|
338
|
+
port: t.port,
|
|
339
|
+
secure: t.secure
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
nextTag() {
|
|
343
|
+
return this.tagPrefix + this.tagCounter++;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Register a pending command and return a promise that resolves when the response arrives.
|
|
347
|
+
* Throws if another command is already pending.
|
|
348
|
+
*/
|
|
349
|
+
registerPendingCommand(t, e, s) {
|
|
350
|
+
if (this.pendingCommand)
|
|
351
|
+
throw new Error(
|
|
352
|
+
`Cannot execute ${e} command while another operation ("${this.pendingCommand.command}") is in progress.`
|
|
353
|
+
);
|
|
354
|
+
const n = m();
|
|
355
|
+
return this.pendingCommand = {
|
|
356
|
+
tag: t,
|
|
357
|
+
command: e,
|
|
358
|
+
callback: s,
|
|
359
|
+
deferred: n
|
|
360
|
+
}, n.promise.finally(() => {
|
|
361
|
+
this.pendingCommand = null;
|
|
362
|
+
}), n.promise;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Send a raw command to the server.
|
|
366
|
+
*/
|
|
367
|
+
sendCommand(t) {
|
|
368
|
+
this.emit("send", t), this.connector.write(`${t}\r
|
|
369
|
+
`);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Execute a command and wait for its response.
|
|
373
|
+
* Processes matching untagged responses as they arrive via onResponse, filters out nulls.
|
|
374
|
+
* Non-matching responses are emitted as events.
|
|
375
|
+
*
|
|
376
|
+
* @param command - The IMAP command to send (e.g., 'LIST "" "*"', 'UID SEARCH ALL')
|
|
377
|
+
* @param matchCommand - Optional command name to match in responses (e.g., 'LIST', 'SEARCH', 'FETCH'). Required when onResponse is provided.
|
|
378
|
+
* @param onResponse - Optional function to transform each matching untagged response as it arrives. Return null to skip.
|
|
379
|
+
* @returns Promise with tagged response and array of transformed items
|
|
380
|
+
*/
|
|
381
|
+
async executeCommand(t, e) {
|
|
382
|
+
const s = this.nextTag(), n = t.split(" ");
|
|
383
|
+
let o = n.shift();
|
|
384
|
+
if (o === "UID" && (o = n.shift()), !o) throw new Error("Invalid command");
|
|
385
|
+
const i = this.registerPendingCommand(s, o, e);
|
|
386
|
+
this.sendCommand(`${s} ${t}`), await i;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Handle incoming data lines from the connector.
|
|
390
|
+
*/
|
|
391
|
+
handleResponse(t) {
|
|
392
|
+
this.emit("response", t);
|
|
393
|
+
const e = this.pendingCommand, s = () => {
|
|
394
|
+
t.symbol === "*" && this.handleAsyncNotification(t);
|
|
395
|
+
};
|
|
396
|
+
if (e) {
|
|
397
|
+
const n = () => {
|
|
398
|
+
t.symbol === "" && t.tag === e.tag ? t.command === "OK" ? e.deferred.resolve() : e.deferred.reject(new Error(t.command)) : s();
|
|
399
|
+
};
|
|
400
|
+
e.callback ? e.callback({
|
|
401
|
+
response: t,
|
|
402
|
+
pending: e,
|
|
403
|
+
next: s,
|
|
404
|
+
fallback: n
|
|
405
|
+
}) : n();
|
|
406
|
+
} else
|
|
407
|
+
s();
|
|
408
|
+
}
|
|
409
|
+
handleAsyncNotification(t) {
|
|
410
|
+
switch (t.command) {
|
|
411
|
+
case "EXISTS":
|
|
412
|
+
this.emit("newmail", { count: t.attributes[0] });
|
|
413
|
+
break;
|
|
414
|
+
case "EXPUNGE":
|
|
415
|
+
this.emit("expunge", { seq: t.attributes[0] });
|
|
416
|
+
break;
|
|
417
|
+
case "RECENT":
|
|
418
|
+
this.emit("recent", { count: t.attributes[0] });
|
|
419
|
+
break;
|
|
420
|
+
case "FLAGS":
|
|
421
|
+
this.emit("flags", { flags: t.attributes[0] });
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
parseFetchAttributes(t, e) {
|
|
426
|
+
const s = { seq: t };
|
|
427
|
+
if (Array.isArray(e))
|
|
428
|
+
for (let n = 0; n < e.length; n += 2) {
|
|
429
|
+
const o = e[n]?.toUpperCase(), i = e[n + 1];
|
|
430
|
+
switch (o) {
|
|
431
|
+
case "UID":
|
|
432
|
+
s.uid = i;
|
|
433
|
+
break;
|
|
434
|
+
case "FLAGS":
|
|
435
|
+
s.flags = Array.isArray(i) ? i : [i];
|
|
436
|
+
break;
|
|
437
|
+
case "ENVELOPE":
|
|
438
|
+
s.envelope = {
|
|
439
|
+
date: i?.[0],
|
|
440
|
+
subject: C(i?.[1]),
|
|
441
|
+
from: i?.[2]?.map(d) ?? [],
|
|
442
|
+
sender: i?.[3]?.map(d) ?? [],
|
|
443
|
+
replyTo: i?.[4]?.map(d) ?? [],
|
|
444
|
+
to: i?.[5]?.map(d) ?? [],
|
|
445
|
+
cc: i?.[6]?.map(d) ?? [],
|
|
446
|
+
bcc: i?.[7]?.map(d) ?? [],
|
|
447
|
+
inReplyTo: i?.[8],
|
|
448
|
+
messageId: i?.[9]
|
|
449
|
+
};
|
|
450
|
+
break;
|
|
451
|
+
case "INTERNALDATE":
|
|
452
|
+
s.internalDate = i;
|
|
453
|
+
break;
|
|
454
|
+
case "RFC822.SIZE":
|
|
455
|
+
s.size = i;
|
|
456
|
+
break;
|
|
457
|
+
case "BODY[]":
|
|
458
|
+
s.body = i;
|
|
459
|
+
break;
|
|
460
|
+
case "RFC822.TEXT":
|
|
461
|
+
case "BODY[TEXT]":
|
|
462
|
+
s.text = i;
|
|
463
|
+
break;
|
|
464
|
+
case "BODY[HTML]":
|
|
465
|
+
s.html = i;
|
|
466
|
+
break;
|
|
467
|
+
default:
|
|
468
|
+
s.attributes ||= {}, s.attributes[o] = i;
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return s;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Connect to the IMAP server and authenticate.
|
|
476
|
+
*/
|
|
477
|
+
async connect() {
|
|
478
|
+
await this.connector.connect({
|
|
479
|
+
onError: (t) => this.emit("error", t),
|
|
480
|
+
onClose: () => this.emit("close")
|
|
481
|
+
}), this.processResponses(), await this.registerPendingCommand("", "", ({ response: t, pending: e, next: s }) => {
|
|
482
|
+
t.symbol === "*" && t.command === "OK" ? e.deferred.resolve() : s();
|
|
483
|
+
}), await this.login();
|
|
484
|
+
}
|
|
485
|
+
async processResponses() {
|
|
486
|
+
for await (const t of this.parser.parse(this.connector.reader))
|
|
487
|
+
this.handleResponse(t);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Authenticate with the server.
|
|
491
|
+
*/
|
|
492
|
+
async login() {
|
|
493
|
+
if (this.options.accessToken) {
|
|
494
|
+
const t = typeof this.options.accessToken == "function" ? await this.options.accessToken() : this.options.accessToken, e = _(this.options.user, t);
|
|
495
|
+
await this.executeCommand(
|
|
496
|
+
`AUTHENTICATE XOAUTH2 ${e}`,
|
|
497
|
+
({ response: s, pending: n, fallback: o }) => {
|
|
498
|
+
s.symbol === "+" ? n.deferred.reject(
|
|
499
|
+
new Error(E(y(s.text)))
|
|
500
|
+
) : o();
|
|
501
|
+
}
|
|
502
|
+
);
|
|
503
|
+
} else if (this.options.password)
|
|
504
|
+
await this.executeCommand(
|
|
505
|
+
`LOGIN "${this.options.user}" "${this.options.password}"`
|
|
506
|
+
);
|
|
507
|
+
else
|
|
508
|
+
throw new Error("Either password or accessToken must be provided");
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* List all mailboxes.
|
|
512
|
+
*/
|
|
513
|
+
async listMailboxes() {
|
|
514
|
+
const t = [];
|
|
515
|
+
return await this.executeCommand('LIST "" "*"', ({ response: e, fallback: s }) => {
|
|
516
|
+
if (e.symbol === "*" && e.command === "LIST") {
|
|
517
|
+
const n = {
|
|
518
|
+
attributes: e.attributes[0],
|
|
519
|
+
delimiter: e.attributes[1],
|
|
520
|
+
name: e.attributes[2]
|
|
521
|
+
};
|
|
522
|
+
t.push(n);
|
|
523
|
+
} else
|
|
524
|
+
s();
|
|
525
|
+
}), t;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Select a mailbox.
|
|
529
|
+
*/
|
|
530
|
+
async selectMailbox(t) {
|
|
531
|
+
await this.executeCommand(`SELECT "${t}"`);
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Search messages.
|
|
535
|
+
*/
|
|
536
|
+
async searchMessages(t, e) {
|
|
537
|
+
const n = { ...u, ...e }.uid ? "UID " : "";
|
|
538
|
+
let o = [];
|
|
539
|
+
return await this.executeCommand(
|
|
540
|
+
`${n}SEARCH ${t}`,
|
|
541
|
+
({ response: i, fallback: a }) => {
|
|
542
|
+
i.symbol === "*" && i.command === "SEARCH" ? o = [...o, ...i.attributes] : a();
|
|
543
|
+
}
|
|
544
|
+
), o;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Fetch messages.
|
|
548
|
+
*/
|
|
549
|
+
async fetchMessages(t, e = ["UID", "ENVELOPE", "FLAGS"], s) {
|
|
550
|
+
const i = `${{ ...u, ...s }.uid ? "UID " : ""}FETCH ${t} (${e.join(" ")})`, a = [];
|
|
551
|
+
return await this.executeCommand(i, ({ response: c, fallback: l }) => {
|
|
552
|
+
if (c.symbol === "*" && c.command === "FETCH") {
|
|
553
|
+
const f = c.attributes[0], h = c.attributes[1];
|
|
554
|
+
a.push(this.parseFetchAttributes(f, h));
|
|
555
|
+
} else
|
|
556
|
+
l();
|
|
557
|
+
}), a;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Set flags on messages.
|
|
561
|
+
*/
|
|
562
|
+
async setFlags(t, e, s, n) {
|
|
563
|
+
const i = { ...u, ...n }.uid ? "UID " : "", a = s.join(" ");
|
|
564
|
+
await this.executeCommand(
|
|
565
|
+
`${i}STORE ${t} ${e}FLAGS (${a})`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Mark messages as seen.
|
|
570
|
+
*/
|
|
571
|
+
async markAsSeen(t, e) {
|
|
572
|
+
const s = { ...u, ...e };
|
|
573
|
+
await this.setFlags(t, "+", ["\\Seen"], s);
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Mark messages as unseen.
|
|
577
|
+
*/
|
|
578
|
+
async markAsUnseen(t, e) {
|
|
579
|
+
const s = { ...u, ...e };
|
|
580
|
+
await this.setFlags(t, "-", ["\\Seen"], s);
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Delete messages.
|
|
584
|
+
*/
|
|
585
|
+
async deleteMessages(t, e) {
|
|
586
|
+
const s = { ...u, ...e };
|
|
587
|
+
await this.setFlags(t, "+", ["\\Deleted"], s), await this.executeCommand("EXPUNGE");
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Move messages to another mailbox.
|
|
591
|
+
*/
|
|
592
|
+
async moveMessages(t, e, s) {
|
|
593
|
+
const o = { ...u, ...s }.uid ? "UID " : "";
|
|
594
|
+
await this.executeCommand(
|
|
595
|
+
`${o}MOVE ${t} "${e}"`
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Start IDLE mode.
|
|
600
|
+
*/
|
|
601
|
+
async startIdle() {
|
|
602
|
+
const t = m();
|
|
603
|
+
let e = !1;
|
|
604
|
+
return this.executeCommand("IDLE", ({ response: s, pending: n, fallback: o }) => {
|
|
605
|
+
if (e)
|
|
606
|
+
o();
|
|
607
|
+
else if (s.symbol === "+")
|
|
608
|
+
e = !0, t.resolve();
|
|
609
|
+
else {
|
|
610
|
+
const i = new Error(s.command);
|
|
611
|
+
t.reject(i), n.deferred.resolve(t.promise);
|
|
612
|
+
}
|
|
613
|
+
}), t.promise;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Stop IDLE mode.
|
|
617
|
+
*/
|
|
618
|
+
async stopIdle() {
|
|
619
|
+
if (this.pendingCommand?.command !== "IDLE")
|
|
620
|
+
throw new Error("IDLE mode is not active");
|
|
621
|
+
return this.sendCommand("DONE"), this.pendingCommand.deferred.promise;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Disconnect from the server.
|
|
625
|
+
*/
|
|
626
|
+
async disconnect() {
|
|
627
|
+
if (this.pendingCommand?.command === "IDLE")
|
|
628
|
+
try {
|
|
629
|
+
await this.stopIdle();
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
this.connector.disconnect();
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
export {
|
|
636
|
+
G as ImapClient,
|
|
637
|
+
j as ImapParser,
|
|
638
|
+
C as decodeRfc2047,
|
|
639
|
+
H as formatDateForImap,
|
|
640
|
+
z as formatDateTimeForImap,
|
|
641
|
+
d as parseAddress
|
|
642
|
+
};
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { BufferReader } from '@gera2ld/common';
|
|
2
|
+
export interface TaggedResponse {
|
|
3
|
+
symbol: '';
|
|
4
|
+
tag: string;
|
|
5
|
+
command: string;
|
|
6
|
+
attributes: any[];
|
|
7
|
+
}
|
|
8
|
+
export interface UntaggedResponse {
|
|
9
|
+
symbol: '*';
|
|
10
|
+
command: string;
|
|
11
|
+
attributes: any[];
|
|
12
|
+
}
|
|
13
|
+
export interface ContinuationResponse {
|
|
14
|
+
symbol: '+';
|
|
15
|
+
text: string;
|
|
16
|
+
}
|
|
17
|
+
export type ParsedResponse = TaggedResponse | UntaggedResponse | ContinuationResponse;
|
|
18
|
+
export declare class ImapParser {
|
|
19
|
+
private decode;
|
|
20
|
+
parse(reader: BufferReader): AsyncGenerator<ParsedResponse, void, unknown>;
|
|
21
|
+
private parseAttributes;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface ImapClientOptions {
|
|
2
|
+
host: string;
|
|
3
|
+
port: number;
|
|
4
|
+
secure: boolean;
|
|
5
|
+
user: string;
|
|
6
|
+
password?: string;
|
|
7
|
+
accessToken?: string | (() => Promise<string>);
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface Address {
|
|
11
|
+
name?: string;
|
|
12
|
+
adl?: string;
|
|
13
|
+
mailbox?: string;
|
|
14
|
+
host?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface MessageEnvelope {
|
|
17
|
+
date?: string;
|
|
18
|
+
subject?: string;
|
|
19
|
+
from?: Address[];
|
|
20
|
+
sender?: Address[];
|
|
21
|
+
replyTo?: Address[];
|
|
22
|
+
to?: Address[];
|
|
23
|
+
cc?: Address[];
|
|
24
|
+
bcc?: Address[];
|
|
25
|
+
inReplyTo?: string;
|
|
26
|
+
messageId?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface FetchMessage {
|
|
29
|
+
seq: number;
|
|
30
|
+
attributes?: Record<string, any>;
|
|
31
|
+
uid?: number;
|
|
32
|
+
flags?: string[];
|
|
33
|
+
envelope?: MessageEnvelope;
|
|
34
|
+
body?: any;
|
|
35
|
+
text?: string;
|
|
36
|
+
html?: string;
|
|
37
|
+
internalDate?: string;
|
|
38
|
+
size?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface MailboxInfo {
|
|
41
|
+
attributes: string[];
|
|
42
|
+
delimiter: string;
|
|
43
|
+
name: string;
|
|
44
|
+
}
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Address } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Formats a Date object to the IMAP date format used in SEARCH commands (e.g., SINCE, ON).
|
|
4
|
+
* The format is "DD-MMM-YYYY" (e.g., "01-Jan-2023").
|
|
5
|
+
*
|
|
6
|
+
* @param date The date to format
|
|
7
|
+
* @returns The formatted date string in IMAP format
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatDateForImap(date: Date): string;
|
|
10
|
+
/**
|
|
11
|
+
* Formats a Date object to the IMAP datetime format used in SEARCH commands.
|
|
12
|
+
* The format is "DD-MMM-YYYY HH:MM:SS +ZZZZ" (e.g., "01-Jan-2023 12:00:00 +0000").
|
|
13
|
+
*
|
|
14
|
+
* @param date The date to format
|
|
15
|
+
* @returns The formatted datetime string in IMAP format
|
|
16
|
+
*/
|
|
17
|
+
export declare function formatDateTimeForImap(date: Date): string;
|
|
18
|
+
export declare function parseAddress(addressComponent: (string | null)[]): Address;
|
|
19
|
+
export declare function decodeRfc2047(str: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gera2ld/imap",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"import": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts"
|
|
9
|
+
},
|
|
10
|
+
"./utils": {
|
|
11
|
+
"import": "./dist/utils.js",
|
|
12
|
+
"types": "./dist/utils.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public",
|
|
20
|
+
"registry": "https://registry.npmjs.org/"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@gera2ld/common": "^0.0.1"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"clean": "del-cli dist tsconfig.tsbuildinfo",
|
|
27
|
+
"build:types": "tsc",
|
|
28
|
+
"build:js": "vite build",
|
|
29
|
+
"build": "pnpm clean && pnpm /^build:/"
|
|
30
|
+
}
|
|
31
|
+
}
|