imapkit 5.0.1 → 5.1.0

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.
@@ -6,6 +6,7 @@ import type { ResolvedCommandOptions } from './command-states.js';
6
6
  import { Control } from './control.js';
7
7
  import type { RestServer } from './rest.js';
8
8
  import type { SessionInfo } from './control.js';
9
+ import type { MemorySocket } from './memory-socket.js';
9
10
  import { ServerScript } from './script.js';
10
11
  import type { DeferredOutput, OutputOperation, ScriptContext, ScriptEvent, ScriptRule } from './script.js';
11
12
  import type { AppendCheck, AppendCheckOptions, AppendDataHandler, AppendMessage, CapabilityCheck, CheckResult, ClosedCheck, CommandCheck, CommandContext, CommandOptions, CommandHandler, ConnectionHandler, ConnectionState, CopyHandler, FetchHandler, IMAPResponse, IMAPServerOptions, ListedMailbox, LiteralFilter, Mailbox, MailboxHandler, MailboxStatus, Message, MessageFilter, MessageHandler, MessageRange, Namespace, Notification, NotifyEvent, NotifyFilter, OutputHandler, ParsedCommand, RangeLimit, Refusal, SearchAccessCheck, SearchHandler, SearchLimit, ServerStorage, StatusHandler, StorageMailbox, StorageMessage, StoreHandler, SubscriptionStandIn, Transport, UrlAccessCheck, UserData } from './types.js';
@@ -158,6 +159,18 @@ declare class IMAPServer extends Stream {
158
159
  };
159
160
  address(): ReturnType<net.Server['address']>;
160
161
  createClient(socket: net.Socket): void;
162
+ /**
163
+ * Opens a connection to the server in memory, without a network socket (no `listen()` needed). This is how the
164
+ * server runs in a browser, see the README "Run in a browser"
165
+ *
166
+ * @param {Object} [options] `{ secure }`: false makes the connection a plaintext one, where STARTTLS turns on the
167
+ * encryption (nothing is encrypted, the session only counts as encrypted). By default the connection counts as
168
+ * one over TLS
169
+ * @return {MemorySocket} the client end, write commands to it and read the responses from its `data` events
170
+ */
171
+ connect(options?: {
172
+ secure?: boolean | undefined;
173
+ }): MemorySocket;
161
174
  registerCapability(keyword: string, handler?: CapabilityCheck | null): void;
162
175
  /**
163
176
  * Sets the handler of a command
@@ -19,6 +19,7 @@ import { resolveQuirks } from './quirks.js';
19
19
  import { validateStorage } from './storage-schema.js';
20
20
  import { createRestServer } from './rest.js';
21
21
  import * as bundledCert from './cert.js';
22
+ import { createSocketPair } from './memory-socket.js';
22
23
  import { ServerScript, sendOutput, handleLine, handleQuiet, literalResponse, releaseDeferred } from './script.js';
23
24
  // longest command line (not counting literals) accepted from a client
24
25
  const MAX_LINE_LENGTH = 1024 * 1024;
@@ -359,6 +360,21 @@ class IMAPServer extends Stream {
359
360
  handler(connection);
360
361
  });
361
362
  }
363
+ /**
364
+ * Opens a connection to the server in memory, without a network socket (no `listen()` needed). This is how the
365
+ * server runs in a browser, see the README "Run in a browser"
366
+ *
367
+ * @param {Object} [options] `{ secure }`: false makes the connection a plaintext one, where STARTTLS turns on the
368
+ * encryption (nothing is encrypted, the session only counts as encrypted). By default the connection counts as
369
+ * one over TLS
370
+ * @return {MemorySocket} the client end, write commands to it and read the responses from its `data` events
371
+ */
372
+ connect(options) {
373
+ const [serverSocket, clientSocket] = createSocketPair();
374
+ serverSocket.encrypted = clientSocket.encrypted = !options || options.secure !== false;
375
+ this.createClient(serverSocket);
376
+ return clientSocket;
377
+ }
362
378
  registerCapability(keyword, handler) {
363
379
  this.capabilities[keyword] =
364
380
  handler ||
@@ -1396,7 +1412,8 @@ class IMAPConnection {
1396
1412
  this._outputQueue = null;
1397
1413
  this._outputTimer = null;
1398
1414
  this._deferredOutput = null;
1399
- this.secureConnection = !!this.options.secureConnection;
1415
+ // a TLS socket is encrypted, and so can be an in-memory connection of server.connect()
1416
+ this.secureConnection = !!this.options.secureConnection || socket.encrypted === true;
1400
1417
  this._remainder = '';
1401
1418
  this._command = '';
1402
1419
  this._literalRemaining = 0;
@@ -1414,7 +1431,7 @@ class IMAPConnection {
1414
1431
  this.messageGlobal = false;
1415
1432
  this._commandQueue = [];
1416
1433
  this._processing = false;
1417
- if (this.options.debug) {
1434
+ if (this.options.debug && typeof this.socket.pipe === 'function') {
1418
1435
  this.socket.pipe(process.stdout);
1419
1436
  }
1420
1437
  this.socket.on('data', this.receive.bind(this));
@@ -1653,7 +1670,8 @@ class IMAPConnection {
1653
1670
  this.watchQuiet(quiet);
1654
1671
  }
1655
1672
  }, Math.max(0, next - (Date.now() - last)));
1656
- this._quietTimer.unref();
1673
+ // browsers have no unref
1674
+ this._quietTimer.unref?.();
1657
1675
  }
1658
1676
  /**
1659
1677
  * Handles data from the socket, through the transport layer if there is one
@@ -2323,6 +2341,16 @@ class IMAPConnection {
2323
2341
  // section 6.2.1). STARTTLS is refused when input is waiting (the noPipelining command option), this only
2324
2342
  // guards against plugins that upgrade the connection otherwise
2325
2343
  this.discardInput();
2344
+ // STARTTLS runs on a live connection
2345
+ const socket = this.socket;
2346
+ if (typeof socket.startTls === 'function') {
2347
+ // a socket without a TLS layer that can count as encrypted (MemorySocket of server.connect())
2348
+ return socket.startTls(() => {
2349
+ this.secureConnection = true;
2350
+ this.upgrading = false;
2351
+ callback();
2352
+ });
2353
+ }
2326
2354
  const secureContext = tls.createSecureContext(this.server.getCredentials());
2327
2355
  const socketOptions = {
2328
2356
  secureContext: secureContext,
@@ -2333,8 +2361,6 @@ class IMAPConnection {
2333
2361
  cb(null, secureContext);
2334
2362
  }
2335
2363
  };
2336
- // STARTTLS runs on a live connection
2337
- const socket = this.socket;
2338
2364
  // the RST of a script rule goes to the TCP socket under the TLS layer, see resetSocket. Node also has it as the
2339
2365
  // _parent of the TLS socket, Bun does not
2340
2366
  this.tcpSocket = socket;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapkit",
3
- "version": "5.0.1",
3
+ "version": "5.1.0",
4
4
  "description": "Scriptable, strictly RFC compliant in-memory IMAP server for testing IMAP clients",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -18,6 +18,10 @@
18
18
  "import": "./dist/esm/index.js",
19
19
  "require": "./dist/cjs/index.js"
20
20
  },
21
+ "./browser": {
22
+ "types": "./dist/esm/index.d.ts",
23
+ "default": "./dist/browser/imapkit.js"
24
+ },
21
25
  "./lib/server": {
22
26
  "import": "./dist/esm/index.js",
23
27
  "require": "./dist/cjs/index.js"
@@ -89,8 +93,11 @@
89
93
  "@eslint/js": "10.0.1",
90
94
  "@types/node": "20.19.43",
91
95
  "@types/smtp-server": "3.5.13",
96
+ "buffer": "6.0.3",
97
+ "esbuild": "0.28.2",
92
98
  "eslint": "10.12.0",
93
99
  "eslint-config-prettier": "10.1.8",
100
+ "events": "3.3.0",
94
101
  "globals": "17.13.0",
95
102
  "imapflow": "2.2.9",
96
103
  "prettier": "3.9.9",