imapkit 5.0.1 → 5.2.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.
- package/README.md +23 -3
- package/bin/help.txt +5 -3
- package/bin/imapkit.js +8 -0
- package/dist/browser/imapkit.js +105 -0
- package/dist/cjs/control.js +1 -1
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/memory-socket.d.ts +69 -0
- package/dist/cjs/memory-socket.js +187 -0
- package/dist/cjs/plugins/idle.js +2 -2
- package/dist/cjs/rest.js +15 -5
- package/dist/cjs/server.d.ts +37 -0
- package/dist/cjs/server.js +85 -7
- package/dist/cjs/types.d.ts +6 -0
- package/dist/esm/control.js +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/memory-socket.d.ts +69 -0
- package/dist/esm/memory-socket.js +182 -0
- package/dist/esm/plugins/idle.js +2 -2
- package/dist/esm/rest.js +15 -5
- package/dist/esm/server.d.ts +37 -0
- package/dist/esm/server.js +85 -7
- package/dist/esm/types.d.ts +6 -0
- package/package.json +13 -6
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ imapkit -p 1143
|
|
|
27
27
|
|
|
28
28
|
Point your IMAP client to `localhost:1143` and log in with user name `testuser` and password `testpass`. Without `-p` the server listens on port 143 (993 with `--secure`), which usually needs root privileges.
|
|
29
29
|
|
|
30
|
-
`imapkit --smtpPort=1025` also starts an SMTP server that appends every message it receives to INBOX. SMTP needs the optional [smtp-server](https://www.npmjs.com/package/smtp-server) package, which is not installed with ImapKit: `npm install -g smtp-server` (or `npm install smtp-server` next to a local install).
|
|
30
|
+
`imapkit -p 1143 --tls-port=1993` also listens for implicit TLS (IMAPS) connections on port 1993, both ports serve the same mailboxes. `imapkit --smtpPort=1025` also starts an SMTP server that appends every message it receives to INBOX. SMTP needs the optional [smtp-server](https://www.npmjs.com/package/smtp-server) package, which is not installed with ImapKit: `npm install -g smtp-server` (or `npm install smtp-server` next to a local install).
|
|
31
31
|
|
|
32
32
|
Run `imapkit --help` to see all command line options, the environment variables (`IMAPKIT_PORT`, `IMAPKIT_PLUGINS`, ...) and sample configuration data. For example, `imapkit -p 1143 --plugin=IDLE,MOVE,CONDSTORE --storage=storage.json` loads three plugins and the mailboxes of `storage.json`.
|
|
33
33
|
|
|
@@ -59,6 +59,24 @@ ImapKit needs Node.js 20 or newer. It also runs on the latest [Bun](https://bun.
|
|
|
59
59
|
|
|
60
60
|
See [complete.js](https://github.com/postalsys/imapkit/blob/master/examples/complete.js) for an example, [control-api.js](https://github.com/postalsys/imapkit/blob/master/examples/control-api.js) for a client test that changes the server state with the control API, and [rest-api.py](https://github.com/postalsys/imapkit/blob/master/examples/rest-api.py) for the REST API from Python.
|
|
61
61
|
|
|
62
|
+
### Run in a browser
|
|
63
|
+
|
|
64
|
+
`imapkit/browser` is a single ES module bundle for browsers, for interactive examples and in-page demos. There are no sockets in a browser, so `server.connect()` opens a connection in memory and returns the client end: write commands to it and read the responses from its `data` events (Uint8Array chunks, or strings after `setEncoding()`). `server.connect()` works in Node.js too and needs no `listen()`.
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
import imapkit from 'imapkit/browser';
|
|
68
|
+
|
|
69
|
+
const server = imapkit({ plugins: ['IDLE'] });
|
|
70
|
+
const client = server.connect(); // counts as a TLS connection, { secure: false } for plaintext
|
|
71
|
+
client.setEncoding('utf8');
|
|
72
|
+
client.on('data', text => console.log(text));
|
|
73
|
+
client.write('A1 LOGIN testuser testpass\r\n');
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The client end has `write(data, [callback])`, `end()`, `destroy()`, `pause()`, `resume()` and the `data`, `end`, `close` and `error` events. Each write reaches the server as one chunk on a later event loop turn. By default the connection counts as encrypted, as if it ran over TLS. With `{ secure: false }` it starts as plaintext, and STARTTLS only marks it as encrypted (`client.encrypted` turns true), nothing is encrypted. A `close: 'reset'` script rule gives the client an `ECONNRESET` error. The bundle has every plugin but COMPRESS, which throws when it is loaded; `listen()`, the SMTP listener and the REST API need Node.js and throw too.
|
|
77
|
+
|
|
78
|
+
[Try it live](https://imapkit.com/live) on imapkit.com runs this bundle in a web terminal, [examples/browser.html](https://github.com/postalsys/imapkit/blob/master/examples/browser.html) is a minimal page: run `npm run build`, serve the repository root over HTTP (`npx http-server -c-1 .`) and open `/examples/browser.html`.
|
|
79
|
+
|
|
62
80
|
## Scope
|
|
63
81
|
|
|
64
82
|
ImapKit is a single user, multiple connection IMAP server. Changes made over IMAP live only in the memory of that server instance, so start a new server for every test.
|
|
@@ -426,7 +444,7 @@ Plugins add operations for their own data, they exist only when the plugin is lo
|
|
|
426
444
|
|
|
427
445
|
A plugin of your own adds operations with `server.control.register(name, fn, routes)`, the routes go to the REST API.
|
|
428
446
|
|
|
429
|
-
`server.start(port, host)` resolves with the port, `server.stop()` closes the server and every session. `listen()` and `close()` still work. With the `smtp` option (`imapkit({ smtp: { port, host } })`) `start()` also starts an SMTP server that appends every message it receives to INBOX, `server.smtpServer` is that server. It needs the optional `smtp-server` package (`npm install smtp-server`), `start()` rejects with an error that says so when it is missing.
|
|
447
|
+
`server.start(port, host)` resolves with the port, `server.stop()` closes the server and every session. `listen()` and `close()` still work. With the `smtp` option (`imapkit({ smtp: { port, host } })`) `start()` also starts an SMTP server that appends every message it receives to INBOX, `server.smtpServer` is that server. It needs the optional `smtp-server` package (`npm install smtp-server`), `start()` rejects with an error that says so when it is missing. With the `tls` option (`imapkit({ tls: { port, host } })`) `start()` also listens for implicit TLS connections, next to the plaintext port, so one server answers on 143 and 993 alike; `server.tlsServer` is that listener.
|
|
430
448
|
|
|
431
449
|
### Events
|
|
432
450
|
|
|
@@ -435,8 +453,10 @@ Tests can wait for events instead of polling:
|
|
|
435
453
|
- `session`: `{ type, session }`, `type` is `open`, `login`, `select` (also when the same mailbox is selected again), `unselect` (CLOSE, UNSELECT, a failed SELECT, BYE), `logout` (UNAUTHENTICATE), `waiting` (a command waits for client input, with `command`, e.g. `IDLE` after its continuation) or `close`, `session` is the session as `sessions()` describes it.
|
|
436
454
|
- `command`: `{ session, tag, command, status, user }` when the tagged response of a command goes out, `status` is `OK`, `NO` or `BAD`. A line that is refused before it runs (it does not parse, or it is pipelined ambiguously) and a command that a script rule answers have no `command` event.
|
|
437
455
|
- `mailbox`: `{ type, path, oldPath, mailbox, origin }` for CREATE, DELETE, RENAME, SUBSCRIBE and UNSUBSCRIBE, `origin` is null for the control API.
|
|
456
|
+
- `append`: `(mailbox, messages, origin)` when a message is added, by APPEND, COPY, MOVE, REPLACE, SMTP or the control API, before the sessions are told.
|
|
438
457
|
- `expunge`: `(mailbox, messages, origin)` before the sessions are told, and `flags`: `(mailbox, messages, origin)` for flag changes of the control API.
|
|
439
458
|
- `acl`: `(mailbox, previousAcl)` when the ACL of a mailbox changes (ACL plugin), `script`: `{ rule, event, session, tag, command }` when a script rule fires, `reset`: after `control.reset()`.
|
|
459
|
+
- `trace`: `{ session, direction, data }` for the protocol traffic of a session, `direction` is `in` or `out` and `data` a Buffer. It is the IMAP layer above TLS and COMPRESS, so a log shows plaintext commands and responses. Nothing is traced while no one listens.
|
|
440
460
|
|
|
441
461
|
```javascript
|
|
442
462
|
const idling = new Promise(resolve => server.on('session', event => event.type === 'waiting' && event.command === 'IDLE' && resolve(event)));
|
|
@@ -460,7 +480,7 @@ The REST API controls the whole server, including the mail of every user, so it
|
|
|
460
480
|
|
|
461
481
|
Requests and responses are JSON, and every POST needs `Content-Type: application/json`, also without a body (HTTP 415 otherwise). Errors are `{ "error": { "code", "message" } }` with HTTP status 404 for `NONEXISTENT`, 409 for `ALREADYEXISTS` and the response codes of a failed mailbox operation (`CANNOT`, `HASCHILDREN` ...), 400 for `INVALID`, 401 for a missing or wrong token, 404 `NOTFOUND` and 405 `METHOD` for an unknown route or method, 413 `TOOBIG` for a body over 64 MiB (and `addMessage` checks refused by APPENDLIMIT), 500 for a server error. A mailbox is its storage name (modified UTF-7) in the URL, URL encoded with a `/` in the name as `%2F` (`/v1/mailboxes/Work%2FProjects`). Message sources are base64 in responses (`"encoding": "base64"`), a request sends `raw` as text or as base64 with `"encoding": "base64"`. `GET /v1/openapi.json` describes every endpoint.
|
|
462
482
|
|
|
463
|
-
`GET /v1/events` streams the [events](#events) as [Server-Sent Events](https://html.spec.whatwg.org/multipage/server-sent-events.html) (`event: <type>` with JSON `data`), so a test in another language can wait for "the client selected INBOX" or "a script rule fired" without polling. `?types=session,command` chooses the types (`session`, `command`, `mailbox`, `expunge`, `flags`, `acl`, `script`, `reset`), mailboxes are paths, messages UIDs and sessions numbers in the data:
|
|
483
|
+
`GET /v1/events` streams the [events](#events) as [Server-Sent Events](https://html.spec.whatwg.org/multipage/server-sent-events.html) (`event: <type>` with JSON `data`), so a test in another language can wait for "the client selected INBOX" or "a script rule fired" without polling. `?types=session,command` chooses the types (`session`, `command`, `mailbox`, `append`, `expunge`, `flags`, `acl`, `script`, `reset`, and `trace`, which is only sent when asked for, with base64 `data`), mailboxes are paths, messages UIDs and sessions numbers in the data:
|
|
464
484
|
|
|
465
485
|
```bash
|
|
466
486
|
curl -N 'http://127.0.0.1:8143/v1/events?types=session'
|
package/bin/help.txt
CHANGED
|
@@ -22,6 +22,8 @@ Usage: imapkit [OPTS]
|
|
|
22
22
|
"Scripted faults" below)
|
|
23
23
|
--plugin=<plugin> Enables a plugin for the server. See below for
|
|
24
24
|
available plugins.
|
|
25
|
+
--tls-port=<port> Also listens for implicit TLS (IMAPS) connections
|
|
26
|
+
on this port, next to the plaintext port
|
|
25
27
|
--smtpPort=<port> Port number for incoming SMTP server. If not set
|
|
26
28
|
SMTP server is not started. --smtp-port works too
|
|
27
29
|
--rest-port=<port> Starts the REST API (the control API over HTTP,
|
|
@@ -38,9 +40,9 @@ Usage: imapkit [OPTS]
|
|
|
38
40
|
|
|
39
41
|
The options can also be set with environment variables: IMAPKIT_PORT,
|
|
40
42
|
IMAPKIT_SECURE, IMAPKIT_DEBUG, IMAPKIT_STORAGE, IMAPKIT_CONFIG,
|
|
41
|
-
IMAPKIT_SCRIPT, IMAPKIT_PLUGINS (comma separated),
|
|
42
|
-
IMAPKIT_REST_PORT, IMAPKIT_REST_HOST, IMAPKIT_REST_TOKEN,
|
|
43
|
-
and IMAPKIT_SCRIPT_SEED.
|
|
43
|
+
IMAPKIT_SCRIPT, IMAPKIT_PLUGINS (comma separated), IMAPKIT_TLS_PORT,
|
|
44
|
+
IMAPKIT_SMTPPORT, IMAPKIT_REST_PORT, IMAPKIT_REST_HOST, IMAPKIT_REST_TOKEN,
|
|
45
|
+
IMAPKIT_QUIRKS and IMAPKIT_SCRIPT_SEED.
|
|
44
46
|
|
|
45
47
|
NB! If port or smtpPort values are below 1024 you most probably need to
|
|
46
48
|
use sudo or run the command with administrator rights.
|
package/bin/imapkit.js
CHANGED
|
@@ -23,6 +23,7 @@ const { values: argv } = parseArgs({
|
|
|
23
23
|
plugin: { type: 'string', multiple: true },
|
|
24
24
|
smtpPort: { type: 'string' },
|
|
25
25
|
'smtp-port': { type: 'string' },
|
|
26
|
+
'tls-port': { type: 'string' },
|
|
26
27
|
'rest-port': { type: 'string' },
|
|
27
28
|
'rest-host': { type: 'string' },
|
|
28
29
|
'rest-token': { type: 'string' },
|
|
@@ -48,6 +49,7 @@ const configLocation = argv.config || process.env.IMAPKIT_CONFIG;
|
|
|
48
49
|
const storageLocation = argv.storage || process.env.IMAPKIT_STORAGE;
|
|
49
50
|
const scriptLocation = argv.script || process.env.IMAPKIT_SCRIPT;
|
|
50
51
|
const smtpPort = argv.smtpPort || argv['smtp-port'] || process.env.IMAPKIT_SMTPPORT;
|
|
52
|
+
const tlsPort = argv['tls-port'] || process.env.IMAPKIT_TLS_PORT;
|
|
51
53
|
const restPort = argv['rest-port'] || process.env.IMAPKIT_REST_PORT;
|
|
52
54
|
const restHost = argv['rest-host'] || process.env.IMAPKIT_REST_HOST;
|
|
53
55
|
const restToken = argv['rest-token'] || process.env.IMAPKIT_REST_TOKEN;
|
|
@@ -115,6 +117,9 @@ if (argv.help) {
|
|
|
115
117
|
if (scriptSeed !== undefined) {
|
|
116
118
|
config.scriptSeed = Number(scriptSeed);
|
|
117
119
|
}
|
|
120
|
+
if (tlsPort) {
|
|
121
|
+
config.tls = { port: Number(tlsPort) };
|
|
122
|
+
}
|
|
118
123
|
if (restPort) {
|
|
119
124
|
config.rest = { port: Number(restPort), host: restHost, token: restToken };
|
|
120
125
|
}
|
|
@@ -135,6 +140,9 @@ if (argv.help) {
|
|
|
135
140
|
listening => {
|
|
136
141
|
// the actual port, also for -p 0
|
|
137
142
|
console.log('ImapKit successfully%s listening on port %s', isSecure ? ' and securely' : '', listening);
|
|
143
|
+
if (server.tlsServer) {
|
|
144
|
+
console.log('ImapKit listening for TLS connections on port %s', server.tlsServer.address().port);
|
|
145
|
+
}
|
|
138
146
|
if (server.restServer) {
|
|
139
147
|
console.log('REST API listening on %s:%s', (config.rest && config.rest.host) || '127.0.0.1', server.restServer.address().port);
|
|
140
148
|
}
|