imapflow 2.4.2 → 2.5.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.
@@ -3,7 +3,6 @@
3
3
  */
4
4
  import tls from 'node:tls';
5
5
  import net from 'node:net';
6
- import crypto from 'node:crypto';
7
6
  import zlib from 'node:zlib';
8
7
  import { EventEmitter } from 'node:events';
9
8
  import { PassThrough } from 'node:stream';
@@ -16,8 +15,10 @@ import { ConnectionDeadline } from './connection-deadline.js';
16
15
  import { downloadMessage, downloadMessageParts } from './download.js';
17
16
  import { AuthenticationFailure, createImapError } from './errors.js';
18
17
  import imapCommands from './imap-commands.js';
19
- import { comparePaths, updateCapabilities, getFolderTree, formatMessageResponse, mergeFetchRows, packMessageRange, normalizePath, expandRange, getColorFlags, hasCapability, isRev2Active, logConnectionError, unrefTimer, clearTimer, parseUintValue, getStringList, getTextValues, emitSafe, buildConnectionError, guardedPromise, guardedReject, MAX_UINT32_DIGITS } from './tools.js';
18
+ import { comparePaths, updateCapabilities, getFolderTree, formatMessageResponse, mergeFetchRows, packMessageRange, normalizePath, expandRange, getColorFlags, hasCapability, isRev2Active, logConnectionError, unrefTimer, clearTimer, parseUintValue, getStringList, getTextValues, emitSafe, buildConnectionError, guardedPromise, guardedReject, isIpLiteral, MAX_UINT32_DIGITS } from './tools.js';
20
19
  export { AuthenticationFailure, ImapFlowErrorCode } from './errors.js';
20
+ export { createSocketsTransport } from './sockets-transport.js';
21
+ export { createStreamsTransport } from './streams-transport.js';
21
22
  const GREETING_TIMEOUT = 16 * 1000;
22
23
  // Status responses that can carry an ALERT response code
23
24
  const ALERT_RESPONSES = new Set(['OK', 'NO', 'BAD', 'BYE', 'PREAUTH']);
@@ -193,7 +194,7 @@ export class ImapFlow extends EventEmitter {
193
194
  // which is POP3 - a client created without an explicit port could never connect.
194
195
  this.port = Number(this.options.port) || (this.secureConnection ? 993 : 143);
195
196
  this.host = this.options.host || 'localhost';
196
- this.servername = this.options.servername ? this.options.servername : !net.isIP(this.host) ? this.host : false;
197
+ this.servername = this.options.servername ? this.options.servername : !isIpLiteral(this.host) ? this.host : false;
197
198
  if (typeof this.options.secure === 'undefined' && this.port === 993) {
198
199
  // if secure option is not set but port is 993, then default to secure
199
200
  this.secureConnection = true;
@@ -320,7 +321,7 @@ export class ImapFlow extends EventEmitter {
320
321
  }
321
322
  /** @internal */
322
323
  getRandomId() {
323
- let rid = BigInt('0x' + crypto.randomBytes(13).toString('hex')).toString(36);
324
+ let rid = BigInt('0x' + Buffer.from(globalThis.crypto.getRandomValues(new Uint8Array(13))).toString('hex')).toString(36);
324
325
  if (rid.length < 20) {
325
326
  rid = '0'.repeat(20 - rid.length) + rid;
326
327
  }
@@ -740,7 +741,7 @@ export class ImapFlow extends EventEmitter {
740
741
  */
741
742
  rejectUnparsedCompletion(payload, parserError) {
742
743
  if (!this.currentRequest || !this.currentRequest.sent) {
743
- return;
744
+ return false;
744
745
  }
745
746
  // Prefer the tag the parser had already extracted before it failed - it went
746
747
  // through the same leading-NUL workaround as every parsed response. Fall back
@@ -752,11 +753,45 @@ export class ImapFlow extends EventEmitter {
752
753
  tag = match && match[1];
753
754
  }
754
755
  if (!tag || tag !== this.currentRequest.tag) {
755
- return;
756
+ return false;
756
757
  }
757
758
  let err = new Error('Failed to parse the server response for this command');
758
759
  err.code = parserError.code || 'ParserError';
759
760
  err.parserError = parserError;
761
+ this.failInFlight(err);
762
+ return true;
763
+ }
764
+ /**
765
+ * Fails the in-flight command when the server answered it with a NO or BAD status line that
766
+ * has no tag at all (Namecheap Private Email: `BAD "Must issue a STARTTLS command first"`).
767
+ * Only one command is ever on the wire, and leaving it pending would stall the connection
768
+ * until the socket timeout. Tags are uppercase hex, so a command can carry the tag BAD:
769
+ * callers check for the command's own tag first.
770
+ *
771
+ * @param payload - Raw bytes of the line.
772
+ * @returns `true` when the line failed the in-flight command
773
+ * @internal
774
+ */
775
+ rejectTaglessCompletion(payload) {
776
+ if (!this.currentRequest || !this.currentRequest.sent) {
777
+ return false;
778
+ }
779
+ let match = payload.toString('latin1', 0, 1024).match(/^\0*(BAD|NO)(?:\s+([^]*))?$/i);
780
+ if (!match) {
781
+ return false;
782
+ }
783
+ let err = createImapError('Command failed', 'MissingTag', {
784
+ command: this.currentRequest.command,
785
+ responseStatus: match[1].toUpperCase(),
786
+ responseText: (match[2] || '').trim().replace(/^"(.*)"$/, '$1')
787
+ });
788
+ this.log.warn({ msg: 'Server sent a status response without a tag', err, cid: this.id });
789
+ this.failInFlight(err);
790
+ return true;
791
+ }
792
+ // Rejects the in-flight command and moves on to the next queued one
793
+ /** @internal */
794
+ failInFlight(err) {
760
795
  this.rejectCurrentRequest(err);
761
796
  this.trySend().catch(sendErr => logConnectionError(this, 'Failed to dispatch command', sendErr));
762
797
  }
@@ -806,7 +841,9 @@ export class ImapFlow extends EventEmitter {
806
841
  // timeout fires. The tag is recovered from the raw bytes (a tag is
807
842
  // ASTRING-CHAR only, so it survives whatever made the rest unparseable) and
808
843
  // the command is failed with the parser error instead of hanging.
809
- this.rejectUnparsedCompletion(data.payload, err);
844
+ if (!this.rejectUnparsedCompletion(data.payload, err)) {
845
+ this.rejectTaglessCompletion(data.payload);
846
+ }
810
847
  return true;
811
848
  }
812
849
  if (parsed.tag && !['*', '+'].includes(parsed.tag) && parsed.command) {
@@ -911,7 +948,8 @@ export class ImapFlow extends EventEmitter {
911
948
  // actually written to the socket (invariant 2), so the three cases below are kept apart:
912
949
  // the active command completes, a command that has not been written yet is proof of
913
950
  // desynchronization (queued behind another command, or current but not yet on the wire),
914
- // and an entirely unknown tag is recorded but tolerated.
951
+ // and an entirely unknown tag is recorded but tolerated, unless the line is a NO or BAD
952
+ // that came without a tag (see rejectTaglessCompletion()).
915
953
  if (parsed.tag && !['*', '+'].includes(parsed.tag)) {
916
954
  if (this.currentRequest && this.currentRequest.tag === parsed.tag && this.currentRequest.sent) {
917
955
  let request = this.takeCurrentRequest();
@@ -949,7 +987,7 @@ export class ImapFlow extends EventEmitter {
949
987
  this.failProtocol(err);
950
988
  return false;
951
989
  }
952
- else {
990
+ else if (!this.rejectTaglessCompletion(data.payload)) {
953
991
  this.countUnknownTag(parsed.tag);
954
992
  }
955
993
  }
@@ -1107,6 +1145,10 @@ export class ImapFlow extends EventEmitter {
1107
1145
  if (typeof socket.setTimeout === 'function') {
1108
1146
  socket.setTimeout(this.socketTimeout);
1109
1147
  }
1148
+ else {
1149
+ // a transport socket from plain JavaScript can lack it, which disables the watchdog
1150
+ this.log.warn({ msg: 'The socket has no setTimeout(), socketTimeout is not enforced', cid: this.id });
1151
+ }
1110
1152
  }
1111
1153
  /** @internal */
1112
1154
  setSocketHandlers() {
@@ -1225,10 +1267,14 @@ export class ImapFlow extends EventEmitter {
1225
1267
  /** @internal */
1226
1268
  async startSession() {
1227
1269
  await this.run('CAPABILITY');
1270
+ await this.upgradeToSTARTTLS();
1271
+ // ID goes after STARTTLS: the client info should not travel in cleartext, and some
1272
+ // servers refuse every command but STARTTLS before the upgrade with an untagged BAD
1273
+ // the client can not tie to ID, which then waits for the socket timeout (Namecheap
1274
+ // Private Email). The capability list was refreshed by the upgrade.
1228
1275
  if (this.capabilities.has('ID')) {
1229
1276
  this.idRequested = await this.run('ID', this.clientInfo);
1230
1277
  }
1231
- await this.upgradeToSTARTTLS();
1232
1278
  await this.authenticate();
1233
1279
  if ((!this.idRequested || Object.keys(this.idRequested).length < 2) && this.capabilities.has('ID')) {
1234
1280
  // re-request ID after LOGIN
@@ -1288,6 +1334,12 @@ export class ImapFlow extends EventEmitter {
1288
1334
  }
1289
1335
  /** @internal */
1290
1336
  async compress() {
1337
+ if (typeof zlib.createDeflateRaw !== 'function') {
1338
+ // The runtime has no streaming DEFLATE (celld implements only the synchronous zlib
1339
+ // functions), so the session stays uncompressed rather than failing after COMPRESS
1340
+ this.log.debug({ msg: 'Streaming DEFLATE is not available, skipping COMPRESS', cid: this.id });
1341
+ return;
1342
+ }
1291
1343
  if (!(await this.run('COMPRESS'))) {
1292
1344
  return; // was not able to negotiate compression
1293
1345
  }
@@ -1408,10 +1460,10 @@ export class ImapFlow extends EventEmitter {
1408
1460
  });
1409
1461
  }
1410
1462
  /** @internal */
1411
- _failSTARTTLS() {
1463
+ _failSTARTTLS(reason = 'Server does not support STARTTLS') {
1412
1464
  if (this.options.doSTARTTLS === true) {
1413
1465
  // STARTTLS configured as requirement
1414
- let err = new Error('Server does not support STARTTLS');
1466
+ let err = new Error(reason);
1415
1467
  err.tlsFailed = true;
1416
1468
  throw err;
1417
1469
  }
@@ -1419,7 +1471,7 @@ export class ImapFlow extends EventEmitter {
1419
1471
  // may be a downgrade attack: say so rather than fall back silently (see the doSTARTTLS
1420
1472
  // option for the policy).
1421
1473
  this.log.warn({
1422
- msg: 'Server does not support STARTTLS, continuing over an unencrypted connection',
1474
+ msg: reason + ', continuing over an unencrypted connection',
1423
1475
  host: this.host,
1424
1476
  port: this.port,
1425
1477
  cid: this.id
@@ -1446,7 +1498,13 @@ export class ImapFlow extends EventEmitter {
1446
1498
  if (!this.capabilities.has('STARTTLS')) {
1447
1499
  return this._failSTARTTLS();
1448
1500
  }
1501
+ if (this.options.transport && !this.options.transport.upgrade) {
1502
+ return this._failSTARTTLS('The transport can not start TLS');
1503
+ }
1449
1504
  this.expectCapabilityUpdate = true;
1505
+ if (this.options.transport?.prepareUpgrade && this.socket) {
1506
+ this.options.transport.prepareUpgrade(this.socket);
1507
+ }
1450
1508
  let canUpgrade = await this.run('STARTTLS');
1451
1509
  if (!canUpgrade) {
1452
1510
  return this._failSTARTTLS();
@@ -1548,7 +1606,7 @@ export class ImapFlow extends EventEmitter {
1548
1606
  this.upgrading = true;
1549
1607
  let tlsSocket;
1550
1608
  try {
1551
- tlsSocket = tls.connect(opts, () => {
1609
+ let onSecure = () => {
1552
1610
  try {
1553
1611
  /* c8 ignore start */ // race: connection closed during the TLS handshake window
1554
1612
  if (this.isClosed) {
@@ -1600,7 +1658,11 @@ export class ImapFlow extends EventEmitter {
1600
1658
  catch (ex) {
1601
1659
  this.emitError(ex);
1602
1660
  }
1603
- });
1661
+ };
1662
+ // a transport without upgrade() never gets here, see the STARTTLS check above
1663
+ tlsSocket = this.options.transport
1664
+ ? this.options.transport.upgrade(socketPlain, this.transportOptions(), onSecure)
1665
+ : tls.connect(opts, onSecure);
1604
1666
  }
1605
1667
  catch (err) {
1606
1668
  // tls.connect() refused the upgrade before any handshake (an option the runtime
@@ -1947,6 +2009,10 @@ export class ImapFlow extends EventEmitter {
1947
2009
  tlsServername() {
1948
2010
  return this.servername ? { servername: this.servername } : {};
1949
2011
  }
2012
+ /** @internal */
2013
+ transportOptions() {
2014
+ return Object.assign({ host: this.host, port: this.port, tls: this.options.tls }, this.tlsServername());
2015
+ }
1950
2016
  // Normalizes a message range from various input formats into an IMAP-compatible
1951
2017
  // sequence string (e.g., "1:5,7,10:*"). Handles: numbers, "*", {all:true},
1952
2018
  // {uid:value}, search query objects (resolved via SEARCH), and arrays of numbers.
@@ -2073,6 +2139,10 @@ export class ImapFlow extends EventEmitter {
2073
2139
  // these methods take an optional second argument, so make sure that some random IMAP tag is not used as the second argument
2074
2140
  this.untaggedHandlers.FETCH = untagged => this.untaggedFetch(untagged);
2075
2141
  this.untaggedHandlers.VANISHED = untagged => this.untaggedVanished(untagged);
2142
+ let transport = this.options.transport;
2143
+ if (transport && this.options.proxy) {
2144
+ throw createImapError('The proxy option can not be combined with a custom transport', 'UnsupportedTransportOption');
2145
+ }
2076
2146
  let socket = false;
2077
2147
  if (this.options.proxy) {
2078
2148
  try {
@@ -2189,6 +2259,19 @@ export class ImapFlow extends EventEmitter {
2189
2259
  setImmediate(onConnect);
2190
2260
  }
2191
2261
  }
2262
+ else if (transport) {
2263
+ try {
2264
+ this.socket = transport.connect({ ...this.transportOptions(), secure: this.secureConnection }, onConnect);
2265
+ }
2266
+ catch (err) {
2267
+ // refused up front: the connection deadline must not stay armed
2268
+ clearTimer(this.connectTimeout);
2269
+ this.closeAfter();
2270
+ this.log.error({ err, cid: this.id });
2271
+ reject(err);
2272
+ return;
2273
+ }
2274
+ }
2192
2275
  else {
2193
2276
  this.socket = connector.connect(opts, onConnect);
2194
2277
  }
@@ -1,3 +1,3 @@
1
1
  export declare const name = "imapflow";
2
- export declare const version = "2.4.2";
2
+ export declare const version = "2.5.0";
3
3
  export declare const homepage = "https://imapflow.com/";
@@ -1,4 +1,4 @@
1
1
  // Generated by scripts/build.js from package.json. Do not edit by hand.
2
2
  export const name = "imapflow";
3
- export const version = "2.4.2";
3
+ export const version = "2.5.0";
4
4
  export const homepage = "https://imapflow.com/";
@@ -0,0 +1,44 @@
1
+ import { type StreamsReader, type StreamsWriter } from './streams-transport.js';
2
+ import type { ImapTransport } from './types.js';
3
+ /** The part of a `cloudflare:sockets` Socket the transport uses */
4
+ export interface SocketsApiSocket {
5
+ readable: {
6
+ getReader(): StreamsReader;
7
+ };
8
+ writable: {
9
+ getWriter(): StreamsWriter;
10
+ };
11
+ opened: Promise<{
12
+ remoteAddress?: string | undefined;
13
+ localAddress?: string | undefined;
14
+ } | undefined>;
15
+ closed: Promise<void>;
16
+ close(): Promise<void>;
17
+ startTls(): SocketsApiSocket;
18
+ }
19
+ /** The `connect()` function of the `cloudflare:sockets` API */
20
+ export type SocketsConnect = (address: {
21
+ hostname: string;
22
+ port: number;
23
+ }, options?: {
24
+ secureTransport?: 'off' | 'on' | 'starttls' | undefined;
25
+ allowHalfOpen?: boolean | undefined;
26
+ } | undefined) => SocketsApiSocket;
27
+ /**
28
+ * Builds a transport for the `transport` option from the `connect()` of the `cloudflare:sockets`
29
+ * API, for celld and other runtimes without `node:net` and `node:tls`:
30
+ *
31
+ * ```js
32
+ * import { connect } from 'cloudflare:sockets';
33
+ * const client = new ImapFlow({ host, port: 993, secure: true, auth, transport: createSocketsTransport(connect) });
34
+ * ```
35
+ *
36
+ * The runtime verifies the server certificate against its own root store, so the transport
37
+ * refuses `tls.rejectUnauthorized: false`, a custom `tls.ca`, client certificates and a
38
+ * `servername` that differs from `host`. The socket carries no cipher details, so `client.tls`
39
+ * stays `false` on a secure connection.
40
+ *
41
+ * @param connect - The `connect` export of `cloudflare:sockets`
42
+ * @returns A transport for the `transport` option
43
+ */
44
+ export declare function createSocketsTransport(connect: SocketsConnect): ImapTransport;
@@ -0,0 +1,86 @@
1
+ // The `transport` option over the `connect()` of the `cloudflare:sockets` API, for runtimes that
2
+ // have it but no usable `node:net` or `node:tls` (celld, Cloudflare Workers). A thin adapter of
3
+ // the API to createStreamsTransport().
4
+ //
5
+ // The runtime verifies every TLS certificate against its own root store and takes the SNI name
6
+ // from the hostname it connects to, so TLS options that would change either are refused rather
7
+ // than silently ignored.
8
+ import { createImapError } from './errors.js';
9
+ import { createStreamsTransport } from './streams-transport.js';
10
+ // TLS options the runtime can not honor: it always verifies the certificate against its own
11
+ // roots, and has no client certificates
12
+ const UNSUPPORTED_TLS_OPTIONS = ['ca', 'cert', 'key', 'pfx', 'passphrase', 'checkServerIdentity'];
13
+ function refuse(option) {
14
+ throw createImapError(`The ${option} option is not supported by this transport`, 'UnsupportedTransportOption');
15
+ }
16
+ function assertOptions(options) {
17
+ const tlsOptions = options.tls || {};
18
+ if (tlsOptions.rejectUnauthorized === false) {
19
+ refuse('tls.rejectUnauthorized=false');
20
+ }
21
+ if (options.servername && options.servername !== options.host) {
22
+ refuse('servername');
23
+ }
24
+ const unsupported = UNSUPPORTED_TLS_OPTIONS.find(key => tlsOptions[key] !== undefined);
25
+ if (unsupported) {
26
+ refuse('tls.' + unsupported);
27
+ }
28
+ }
29
+ // "1.2.3.4:993" or "[::1]:993"
30
+ function splitAddress(address) {
31
+ const match = typeof address === 'string' && address.match(/^\[?(.*?)\]?:(\d+)$/);
32
+ return match ? { address: match[1], port: Number(match[2]) } : {};
33
+ }
34
+ function wrap(raw) {
35
+ return {
36
+ raw,
37
+ opened: raw.opened.then(info => {
38
+ const remote = splitAddress(info?.remoteAddress);
39
+ const local = splitAddress(info?.localAddress);
40
+ return {
41
+ readable: raw.readable,
42
+ writable: raw.writable,
43
+ remoteAddress: remote.address,
44
+ remotePort: remote.port,
45
+ localAddress: local.address,
46
+ localPort: local.port
47
+ };
48
+ }),
49
+ closed: raw.closed,
50
+ close: () => raw.close()
51
+ };
52
+ }
53
+ /**
54
+ * Builds a transport for the `transport` option from the `connect()` of the `cloudflare:sockets`
55
+ * API, for celld and other runtimes without `node:net` and `node:tls`:
56
+ *
57
+ * ```js
58
+ * import { connect } from 'cloudflare:sockets';
59
+ * const client = new ImapFlow({ host, port: 993, secure: true, auth, transport: createSocketsTransport(connect) });
60
+ * ```
61
+ *
62
+ * The runtime verifies the server certificate against its own root store, so the transport
63
+ * refuses `tls.rejectUnauthorized: false`, a custom `tls.ca`, client certificates and a
64
+ * `servername` that differs from `host`. The socket carries no cipher details, so `client.tls`
65
+ * stays `false` on a secure connection.
66
+ *
67
+ * @param connect - The `connect` export of `cloudflare:sockets`
68
+ * @returns A transport for the `transport` option
69
+ */
70
+ export function createSocketsTransport(connect) {
71
+ return createStreamsTransport({
72
+ open(options) {
73
+ // TLS options matter only for a TLS connection, a cleartext one gets them checked if
74
+ // it is upgraded
75
+ if (options.secure) {
76
+ assertOptions(options);
77
+ }
78
+ // Cleartext sockets are opened in starttls mode, which costs nothing until an upgrade
79
+ return wrap(connect({ hostname: options.host, port: options.port }, { secureTransport: options.secure ? 'on' : 'starttls' }));
80
+ },
81
+ upgrade(socket, options) {
82
+ assertOptions(options);
83
+ return wrap(socket.raw.startTls());
84
+ }
85
+ });
86
+ }
@@ -0,0 +1,84 @@
1
+ import type { ImapTransport, ImapTransportConnectOptions, ImapTransportOptions } from './types.js';
2
+ /** The reader of {@link StreamsSocketInfo.readable} */
3
+ export interface StreamsReader {
4
+ read(): Promise<{
5
+ done: boolean;
6
+ value?: Uint8Array | undefined;
7
+ }>;
8
+ releaseLock(): void;
9
+ }
10
+ /** The writer of {@link StreamsSocketInfo.writable} */
11
+ export interface StreamsWriter {
12
+ write(chunk: Uint8Array): Promise<void>;
13
+ releaseLock(): void;
14
+ }
15
+ /** What a {@link StreamsSocket} resolves with once it is open */
16
+ export interface StreamsSocketInfo {
17
+ /** Bytes from the server, a ReadableStream of Uint8Array */
18
+ readable: {
19
+ getReader(): StreamsReader;
20
+ };
21
+ /** Bytes to the server, a WritableStream of Uint8Array */
22
+ writable: {
23
+ getWriter(): StreamsWriter;
24
+ };
25
+ remoteAddress?: string | undefined;
26
+ remotePort?: number | undefined;
27
+ localAddress?: string | undefined;
28
+ localPort?: number | undefined;
29
+ /** Details of a TLS connection, reported as `client.tls`. Left out when the API has none */
30
+ tls?: StreamsTlsInfo | undefined;
31
+ }
32
+ /** What a socket API knows about a TLS connection */
33
+ export interface StreamsTlsInfo {
34
+ /** Whether the server certificate was verified */
35
+ authorized: boolean;
36
+ /** Cipher name, e.g. `TLS_AES_256_GCM_SHA384` */
37
+ name?: string | undefined;
38
+ standardName?: string | undefined;
39
+ /** Protocol version, e.g. `TLSv1.3` */
40
+ version?: string | undefined;
41
+ }
42
+ /** A connection of a web streams socket API, as {@link StreamsTransportApi} opens it */
43
+ export interface StreamsSocket {
44
+ /** Resolves once the connection (and the TLS handshake, if any) is established, rejects if it fails */
45
+ opened: Promise<StreamsSocketInfo>;
46
+ /** Rejects if the connection fails after it was opened */
47
+ closed?: Promise<unknown> | undefined;
48
+ /** Closes the connection */
49
+ close(): unknown;
50
+ }
51
+ /** The socket API that {@link createStreamsTransport} drives */
52
+ export interface StreamsTransportApi<S extends StreamsSocket = StreamsSocket> {
53
+ /**
54
+ * Opens a connection, cleartext or TLS as `options.secure` says. Throws to refuse it, for
55
+ * example with an `UnsupportedTransportOption` error for TLS options the API can not honor.
56
+ */
57
+ open(options: ImapTransportConnectOptions): S;
58
+ /**
59
+ * Starts TLS on an open cleartext connection after STARTTLS and returns the TLS connection,
60
+ * the old one stays open as its transport. Throws to refuse the upgrade. Leave it out when
61
+ * the API can not upgrade a connection: ImapFlow then does not try STARTTLS.
62
+ */
63
+ upgrade?: ((socket: S, options: ImapTransportOptions) => S) | undefined;
64
+ }
65
+ /**
66
+ * Builds a transport for the `transport` option over a socket API built on web streams. A new
67
+ * API needs only `open()`, and `upgrade()` if it can start TLS on an open connection:
68
+ *
69
+ * ```js
70
+ * const transport = createStreamsTransport({
71
+ * open: ({ host, port, secure }) => {
72
+ * const socket = openSomeSocket(host, port, secure);
73
+ * return { opened: socket.ready.then(() => ({ readable: socket.readable, writable: socket.writable })), close: () => socket.close() };
74
+ * }
75
+ * });
76
+ * ```
77
+ *
78
+ * The transport stops reading once the server accepted a STARTTLS, so the API never has to start
79
+ * TLS with a read in flight, and releases the stream locks before `upgrade()` is called.
80
+ *
81
+ * @param api - Opens (and upgrades) connections
82
+ * @returns A transport for the `transport` option
83
+ */
84
+ export declare function createStreamsTransport<S extends StreamsSocket>(api: StreamsTransportApi<S>): ImapTransport;