serverless-ircd 0.7.0 → 0.8.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/.github/workflows/ci.yml +3 -3
- package/.gitmodules +1 -1
- package/CHANGELOG.md +273 -29
- package/README.md +155 -55
- package/apps/aws-stack/package.json +1 -1
- package/apps/aws-stack/src/aws-stack.ts +186 -18
- package/apps/aws-stack/tests/stack.test.ts +400 -56
- package/apps/cf-tcp-container/package.json +1 -1
- package/apps/cf-worker/package.json +1 -1
- package/apps/cf-worker/src/worker.ts +4 -4
- package/apps/cf-worker/tests/fixtures/web-dist/webclient/index.html +18 -0
- package/apps/cf-worker/tests/smoke.test.ts +5 -5
- package/apps/cf-worker/wrangler.test.toml +6 -6
- package/apps/cf-worker/wrangler.toml +7 -5
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +8 -0
- package/apps/local-cli/src/main.ts +16 -0
- package/apps/local-cli/src/server.ts +1 -0
- package/apps/local-cli/tests/config-loader.test.ts +14 -0
- package/apps/web/landing/index.html +14 -16
- package/apps/web/package.json +2 -2
- package/apps/web/scripts/build.mjs +23 -16
- package/apps/web/src/build-env.ts +1 -1
- package/apps/web/src/config-schema.ts +6 -6
- package/apps/web/tests/build-smoke.test.ts +14 -14
- package/apps/web/tests/config-schema.test.ts +1 -1
- package/docs/AWS-Deployment.md +21 -8
- package/docs/Cloudflare-Deployment-Guide.md +22 -6
- package/docs/PlanExtensions.md +113 -3
- package/docs/Release-Process.md +23 -13
- package/docs/Services.md +546 -0
- package/docs/WebClientGuide.md +32 -31
- package/package.json +2 -2
- package/packages/aws-adapter/package.json +1 -1
- package/packages/aws-adapter/src/aws-runtime.ts +5 -0
- package/packages/aws-adapter/src/cdk-table-defs.ts +6 -0
- package/packages/aws-adapter/src/config-loader.ts +11 -0
- package/packages/aws-adapter/src/connection-counter.ts +89 -0
- package/packages/aws-adapter/src/dynamo-services-store.ts +649 -0
- package/packages/aws-adapter/src/handlers/connect.ts +55 -51
- package/packages/aws-adapter/src/handlers/default.ts +36 -4
- package/packages/aws-adapter/src/handlers/index.ts +15 -0
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +15 -0
- package/packages/aws-adapter/src/handlers/sweeper.ts +5 -1
- package/packages/aws-adapter/src/index.ts +4 -0
- package/packages/aws-adapter/src/stats.ts +6 -1
- package/packages/aws-adapter/src/tables.ts +34 -4
- package/packages/aws-adapter/tests/aws-harness.ts +3 -0
- package/packages/aws-adapter/tests/config-loader.test.ts +8 -0
- package/packages/aws-adapter/tests/connect.test.ts +158 -32
- package/packages/aws-adapter/tests/connection-counter.test.ts +127 -0
- package/packages/aws-adapter/tests/dynamo-services-store-dynamo.test.ts +183 -0
- package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +568 -0
- package/packages/aws-adapter/tests/handlers.test.ts +105 -3
- package/packages/aws-adapter/tests/tables.test.ts +6 -1
- package/packages/cf-adapter/package.json +1 -1
- package/packages/cf-adapter/src/config-loader.ts +11 -0
- package/packages/cf-adapter/src/connection-do.ts +112 -2
- package/packages/cf-adapter/src/d1-services-store.ts +703 -0
- package/packages/cf-adapter/src/env.ts +8 -0
- package/packages/cf-adapter/src/index.ts +5 -0
- package/packages/cf-adapter/tests/config-loader.test.ts +19 -0
- package/packages/cf-adapter/tests/connection-do-nickserv-d1.test.ts +128 -0
- package/packages/cf-adapter/tests/connection-do.test.ts +150 -2
- package/packages/cf-adapter/tests/d1-services-store.test.ts +582 -0
- package/packages/cf-adapter/tests/serialize.test.ts +1 -0
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/scripts/generate-build-info.mjs +26 -5
- package/packages/irc-core/src/commands/account-auth.ts +172 -0
- package/packages/irc-core/src/commands/chanserv.ts +882 -0
- package/packages/irc-core/src/commands/hostserv.ts +487 -0
- package/packages/irc-core/src/commands/index.ts +12 -0
- package/packages/irc-core/src/commands/join.ts +164 -8
- package/packages/irc-core/src/commands/markread.ts +202 -0
- package/packages/irc-core/src/commands/memoserv.ts +319 -0
- package/packages/irc-core/src/commands/mode.ts +96 -4
- package/packages/irc-core/src/commands/nickserv.ts +390 -0
- package/packages/irc-core/src/commands/oper.ts +18 -1
- package/packages/irc-core/src/commands/operserv.ts +346 -0
- package/packages/irc-core/src/commands/pre-away.ts +3 -1
- package/packages/irc-core/src/commands/privmsg.ts +42 -0
- package/packages/irc-core/src/commands/read-marker.ts +8 -8
- package/packages/irc-core/src/commands/registration.ts +61 -6
- package/packages/irc-core/src/commands/sasl.ts +18 -49
- package/packages/irc-core/src/commands/tagmsg.ts +41 -6
- package/packages/irc-core/src/commands/topic.ts +37 -0
- package/packages/irc-core/src/config.ts +36 -5
- package/packages/irc-core/src/effects.ts +56 -1
- package/packages/irc-core/src/ports.ts +1653 -84
- package/packages/irc-core/src/protocol/numerics.ts +8 -0
- package/packages/irc-core/src/state/channel.ts +21 -1
- package/packages/irc-core/src/state/connection.ts +25 -1
- package/packages/irc-core/src/types.ts +48 -12
- package/packages/irc-core/tests/commands/chanserv.test.ts +1668 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +6 -0
- package/packages/irc-core/tests/commands/hostserv.test.ts +935 -0
- package/packages/irc-core/tests/commands/join.test.ts +393 -1
- package/packages/irc-core/tests/commands/markread.test.ts +361 -0
- package/packages/irc-core/tests/commands/memoserv.test.ts +654 -0
- package/packages/irc-core/tests/commands/mode.test.ts +381 -2
- package/packages/irc-core/tests/commands/nickserv.test.ts +807 -0
- package/packages/irc-core/tests/commands/oper.test.ts +13 -0
- package/packages/irc-core/tests/commands/operserv.test.ts +656 -0
- package/packages/irc-core/tests/commands/privmsg.test.ts +147 -0
- package/packages/irc-core/tests/commands/read-marker.test.ts +28 -28
- package/packages/irc-core/tests/commands/registration.test.ts +788 -14
- package/packages/irc-core/tests/commands/sasl.test.ts +185 -12
- package/packages/irc-core/tests/commands/server-info.test.ts +9 -5
- package/packages/irc-core/tests/commands/tagmsg.test.ts +73 -33
- package/packages/irc-core/tests/commands/topic.test.ts +94 -2
- package/packages/irc-core/tests/commands/unified-account.test.ts +416 -0
- package/packages/irc-core/tests/config.test.ts +49 -5
- package/packages/irc-core/tests/effects.test.ts +19 -0
- package/packages/irc-core/tests/message-store.test.ts +63 -0
- package/packages/irc-core/tests/persistent-services-store.test.ts +582 -0
- package/packages/irc-core/tests/services-store.test.ts +1289 -0
- package/packages/irc-core/tests/state/channel.test.ts +3 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +71 -16
- package/packages/irc-server/src/dispatch.ts +94 -7
- package/packages/irc-server/src/routing.ts +19 -0
- package/packages/irc-server/tests/actor.test.ts +623 -12
- package/packages/irc-server/tests/dispatch.test.ts +270 -2
- package/packages/irc-server/tests/routing.test.ts +6 -0
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/in-memory-harness.ts +29 -3
- package/packages/irc-test-support/src/index.ts +1 -0
- package/packages/irc-test-support/tests/in-memory-harness.test.ts +32 -0
- package/tools/ci-hardening/package.json +1 -1
- package/tools/load-test/package.json +1 -1
- package/tools/tcp-ws-forwarder/package.json +1 -1
- package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +2 -2
- package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +0 -18
- package/packages/irc-core/tests/read-marker-store.test.ts +0 -108
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ platform-agnostic core, and two thin adapters run it on **Cloudflare Workers**
|
|
|
6
6
|
|
|
7
7
|
One TypeScript codebase. Two serverless substrates.
|
|
8
8
|
|
|
9
|
-
> **Status:** **v0.
|
|
9
|
+
> **Status:** **v0.8.0 (preview).** The pure protocol core, the
|
|
10
10
|
> `IrcRuntime` port + in-memory runtime, a runnable local CLI server,
|
|
11
11
|
> the **Cloudflare Workers** adapter, and the **AWS** (API Gateway
|
|
12
12
|
> WebSocket + Lambda + DynamoDB + CDK) adapter are all functional and
|
|
@@ -19,22 +19,33 @@ One TypeScript codebase. Two serverless substrates.
|
|
|
19
19
|
> deferred IRC verbs (`KILL`, `REHASH`, `LUSERS` + `STATS`, `TRACE`,
|
|
20
20
|
> `WALLOPS`, `SETNAME`) all land, and the S2S (`CONNECT` / `SQUIT` /
|
|
21
21
|
> `LINKS`) and obsolete RFC 2812 (`SERVICE` / `SUMMON` / `USERS`) verbs
|
|
22
|
-
> are formally dropped. v0.6.0 landed the **IRCv3 extension sweep
|
|
23
|
-
>
|
|
22
|
+
> are formally dropped. v0.6.0 landed the **IRCv3 extension sweep**
|
|
23
|
+
> — ten caps (`account-notify`, `msgid`,
|
|
24
24
|
> `standard-replies`, `MONITOR`, `labeled-response`, `sts`,
|
|
25
25
|
> `draft/typing`, `draft/multiline`, `draft/read-marker`,
|
|
26
26
|
> `draft/pre-away`), the ISUPPORT tokens for the new caps, and the
|
|
27
27
|
> read-only user mode `S` (TLS connected). **v0.7.0 lands the web
|
|
28
|
-
> client
|
|
28
|
+
> client**: a vendored **Kiwi IRC** SPA served at `/webclient/`
|
|
29
29
|
> and a static landing page at `/` directly by the Cloudflare Worker,
|
|
30
30
|
> with **Cross-Site WebSocket Hijacking (CSWSH) defense** wired into
|
|
31
31
|
> the WS upgrade path (same-origin auto-derive by default, optional
|
|
32
32
|
> `WEB_ORIGINS` allowlist for cross-origin deploys) — the browser
|
|
33
33
|
> opens a native `wss://` straight to the existing IRC-over-WebSocket
|
|
34
|
-
> endpoint.
|
|
34
|
+
> endpoint. **v0.8.0 lands integrated IRC services** — NickServ
|
|
35
|
+
> (`REGISTER` / `IDENTIFY` / `DROP` / `INFO` / `SET ENFORCE` +
|
|
36
|
+
> nick enforcement), ChanServ (`REGISTER` / `DROP` / `SET` /
|
|
37
|
+
> `ACCESS` / `LEVELS` + the `+r` / `+R` / `+M` channel modes),
|
|
38
|
+
> HostServ (`ON` / `OFF` / `REQUEST` + oper `SET` / `APPROVE`),
|
|
39
|
+
> OperServ (`AKILL` / `JUPE` / `RAW`), and MemoServ
|
|
40
|
+
> (`SEND` / `LIST` / `READ` / `DEL`) — backed by a `ServicesStore`
|
|
41
|
+
> port with **persistent D1 (Cloudflare) and DynamoDB (AWS)
|
|
42
|
+
> backends** (write-behind, surviving redeploys). The SASL
|
|
43
|
+
> `AccountStore` and NickServ accounts are **unified** under one
|
|
44
|
+
> scrypt-hashed credential store, `PASS <nick>:<password>` identifies
|
|
45
|
+
> at registration, and `draft/read-marker` gains the timestamp-based
|
|
46
|
+
> `MARKREAD` verb. Remaining 0.x work: the client compatibility sweep
|
|
35
47
|
> (WeeChat / HexChat / IRCCloud / TheLounge), the Playwright
|
|
36
|
-
> browser-driven e2e for the SPA,
|
|
37
|
-
> HostServ / OperServ / MemoServ), and driving the remaining
|
|
48
|
+
> browser-driven e2e for the SPA, and driving the remaining
|
|
38
49
|
> sub-100% packages to full coverage. See `CHANGELOG.md` for the
|
|
39
50
|
> per-release manifests.
|
|
40
51
|
|
|
@@ -43,9 +54,9 @@ One TypeScript codebase. Two serverless substrates.
|
|
|
43
54
|
## Why
|
|
44
55
|
|
|
45
56
|
IRC servers have historically been long-running stateful processes. This
|
|
46
|
-
project
|
|
47
|
-
|
|
48
|
-
|
|
57
|
+
project moves the protocol logic into a set of **pure reducers** so that
|
|
58
|
+
the only platform-specific code is the side-effect layer (transports,
|
|
59
|
+
registries, fanout). The consequences:
|
|
49
60
|
|
|
50
61
|
- Every command handler is a trivial unit test: arrange state, apply a message,
|
|
51
62
|
assert the new state and emitted effects.
|
|
@@ -57,7 +68,7 @@ reducers** and the only platform-specific code is the side-effect plumbing
|
|
|
57
68
|
|
|
58
69
|
## Architecture
|
|
59
70
|
|
|
60
|
-
Hexagonal / ports-and-adapters. The core
|
|
71
|
+
Hexagonal / ports-and-adapters. The core implements the IRC protocol; adapters handle I/O.
|
|
61
72
|
|
|
62
73
|
```
|
|
63
74
|
┌─────────────────────────────────────────────────────────────────┐
|
|
@@ -97,7 +108,7 @@ function. Both feed the same `ConnectionActor` through a `Transport`
|
|
|
97
108
|
seam (`WsTextFrameTransport` vs. `TcpByteStreamTransport`); the
|
|
98
109
|
parser/reducer/dispatch pipeline is identical.
|
|
99
110
|
|
|
100
|
-
###
|
|
111
|
+
### Design: pure reducers + location-of-authority
|
|
101
112
|
|
|
102
113
|
Each command handler is a pure function:
|
|
103
114
|
|
|
@@ -122,6 +133,7 @@ Each reducer runs in whichever authority **owns** the state it mutates:
|
|
|
122
133
|
| PRIVMSG/NOTICE to channel | Channel entity | Fanout (reads roster) |
|
|
123
134
|
| Nick collision check | Registry entity | Nick → Connection map |
|
|
124
135
|
| NAMES / WHO / WHOIS | Reads snapshots | (no mutation) |
|
|
136
|
+
| Services (`PRIVMSG NickServ`/`ChanServ`/…) | Connection entity | `ServicesStore` (account/channel/vhost/memo state) |
|
|
125
137
|
|
|
126
138
|
---
|
|
127
139
|
|
|
@@ -130,7 +142,7 @@ Each reducer runs in whichever authority **owns** the state it mutates:
|
|
|
130
142
|
```
|
|
131
143
|
ServerlessIRCd/
|
|
132
144
|
├── packages/
|
|
133
|
-
│ ├── irc-core/ pure protocol + reducers
|
|
145
|
+
│ ├── irc-core/ pure protocol + reducers
|
|
134
146
|
│ ├── irc-server/ orchestration, IrcRuntime port, dispatch
|
|
135
147
|
│ ├── in-memory-runtime/ reference runtime (used by tests + local CLI)
|
|
136
148
|
│ ├── irc-test-support/ parametrized IRC scenario suite + harness seam
|
|
@@ -140,11 +152,11 @@ ServerlessIRCd/
|
|
|
140
152
|
│ ├── cf-worker/ worker entry, DO migrations, bindings, [assets] + CSWSH gate
|
|
141
153
|
│ ├── cf-tcp-container/ Spectrum + Container origin for irc+tls :6697
|
|
142
154
|
│ ├── aws-stack/ CDK stack (APIGW WS + NLB + Lambda streaming + DynamoDB)
|
|
143
|
-
│ ├── web/ vendored Kiwi IRC SPA (→ /
|
|
155
|
+
│ ├── web/ vendored Kiwi IRC SPA (→ /webclient/) + static landing page (→ /)
|
|
144
156
|
│ └── local-cli/ runnable WS + TCP server using in-memory-runtime
|
|
145
157
|
├── tools/
|
|
146
158
|
│ ├── tcp-ws-forwarder/ local TCP↔ws/wss bridge for stock IRC clients
|
|
147
|
-
│ ├── load-test/ synthetic WebSocket IRC client
|
|
159
|
+
│ ├── load-test/ synthetic WebSocket IRC client pool (10k conns, p50/p95/p99, drop rate)
|
|
148
160
|
│ ├── ci-hardening/ coverage-gate + mutation-config validators
|
|
149
161
|
│ ├── seed-aws-accounts.ts scrypt-hash SASL PLAIN accounts into DynamoDB
|
|
150
162
|
│ └── seed-cf-accounts.ts scrypt-hash SASL PLAIN accounts into Cloudflare D1
|
|
@@ -201,7 +213,7 @@ needs the upstream sources (`git submodule update --init apps/web/upstream`)
|
|
|
201
213
|
and `yarn` (Kiwi ships a `yarn.lock`; `corepack enable` provides it):
|
|
202
214
|
|
|
203
215
|
```bash
|
|
204
|
-
pnpm --filter web build # builds the Kiwi SPA into apps/web/dist/
|
|
216
|
+
pnpm --filter web build # builds the Kiwi SPA into apps/web/dist/webclient/ (/webclient/)
|
|
205
217
|
```
|
|
206
218
|
|
|
207
219
|
Coverage reports are written to `packages/*/coverage/`. CI (`.github/workflows/ci.yml`)
|
|
@@ -223,7 +235,7 @@ that share one runtime, so a TCP client and a WS client can see each other
|
|
|
223
235
|
|
|
224
236
|
- a **WebSocket** listener (the serverless transport) on `--port`, and
|
|
225
237
|
- an **RFC-style TCP** listener on `--tcp-port` (default `<port> + 1`) so
|
|
226
|
-
real IRC clients (WeeChat, HexChat, irssi, …) can
|
|
238
|
+
real IRC clients (WeeChat, HexChat, irssi, …) can connect directly.
|
|
227
239
|
|
|
228
240
|
First build the workspace (the CLI runs from compiled `dist/`):
|
|
229
241
|
|
|
@@ -256,11 +268,55 @@ On startup it logs a JSON line like:
|
|
|
256
268
|
| `--motd-file <p>` | built-in | Read MOTD lines from this file (one per line). |
|
|
257
269
|
| `--server-name <h>`| `irc.localhost` | Server hostname advertised in `001`/`005`. Production MUST override. |
|
|
258
270
|
| `--network-name <n>`| `LocalNet` | Network name advertised in `005 NETWORK=…`. |
|
|
271
|
+
| `--server-password <p>`| | Server-password gate. When set, every connection must supply `PASS <p>` (or SASL-identify) before `001`. Treat as a secret. |
|
|
259
272
|
| `-h, --help` | | Show help and exit. |
|
|
260
273
|
|
|
261
274
|
Pass `--host 0.0.0.0` to expose the server on all interfaces. `SIGINT` /
|
|
262
275
|
`SIGTERM` perform a graceful shutdown (closes active sockets, then exits).
|
|
263
276
|
|
|
277
|
+
### Server password (`SERVER_PASSWORD` / `--server-password`)
|
|
278
|
+
|
|
279
|
+
All three adapters (Cloudflare Worker, AWS Lambda, local CLI) support an
|
|
280
|
+
optional **server-password gate**. When the knob is set, every connection
|
|
281
|
+
must supply the matching `PASS <value>` before `001 RPL_WELCOME` is
|
|
282
|
+
emitted; when unset or empty the gate is disabled (the default).
|
|
283
|
+
|
|
284
|
+
| Adapter | Knob |
|
|
285
|
+
|----------------|-------------------------------------------------------------------------------|
|
|
286
|
+
| Cloudflare | `wrangler secret put SERVER_PASSWORD` (Workers secret; never a `[vars]` entry). |
|
|
287
|
+
| AWS | `SERVER_PASSWORD` Lambda env var (sourced from Secrets Manager / SSM). |
|
|
288
|
+
| local CLI | `--server-password <p>` flag, or `serverPassword` on `StartServerOptions`. |
|
|
289
|
+
|
|
290
|
+
On a mismatched/missing `PASS` the server emits `464 ERR_PASSWDMISMATCH`
|
|
291
|
+
and disconnects with reason `Bad Password`. **SASL short-circuit:** a
|
|
292
|
+
connection that has authenticated an account via SASL
|
|
293
|
+
(`AUTHENTICATE PLAIN` / `EXTERNAL`) is exempt — the reducer's gate
|
|
294
|
+
treats `state.account !== undefined` as already authorised, so a
|
|
295
|
+
deployment with both `SERVER_PASSWORD` and configured SASL accounts
|
|
296
|
+
does not need to hand the shared password to identified users. The
|
|
297
|
+
server-wide gate is a shared deployment secret (never log it); per-user
|
|
298
|
+
credentials still go through the hashed SASL account store
|
|
299
|
+
(`HashedAccountStore` / D1 / DynamoDB), which is unaffected.
|
|
300
|
+
|
|
301
|
+
**PASS-based account login:** in addition to SASL, a client may identify
|
|
302
|
+
to its NickServ account at registration by sending
|
|
303
|
+
`PASS <nick>:<password>` (the same `<nick>:<password>` literal the
|
|
304
|
+
`SASL_ACCOUNTS` seed tooling uses). When an `AccountStore` is configured,
|
|
305
|
+
`NICK alice` + `PASS alice:hunter2` + `USER …` verifies the credentials
|
|
306
|
+
against the account store and, on success, logs the connection in as
|
|
307
|
+
`alice` — emitting `900 RPL_LOGGEDIN` + `903 RPL_SASLSUCCESS` (the same
|
|
308
|
+
"account is set" numerics SASL uses) before `001 RPL_WELCOME`, stamping
|
|
309
|
+
user mode `+r`, and running the same read-marker / away / memo replay as
|
|
310
|
+
a SASL login. The nick left of the `:` must match the `NICK` sent; a bare
|
|
311
|
+
`PASS <value>` with no colon is always treated as a server-password
|
|
312
|
+
candidate, never as account credentials. A wrong password or unknown nick
|
|
313
|
+
is indistinguishable (no `904`/`464` from the auth path) and the
|
|
314
|
+
connection simply proceeds un-identified when no server password is set.
|
|
315
|
+
This composes with the gate above by precedence: an already-identified
|
|
316
|
+
connection (SASL or PASS-auth) satisfies the gate; a `<nick>:<password>`
|
|
317
|
+
that fails verify never matches a bare shared secret, so a server with
|
|
318
|
+
both configured still rejects it with `464`.
|
|
319
|
+
|
|
264
320
|
### Connecting
|
|
265
321
|
|
|
266
322
|
The WebSocket listener speaks WebSocket text frames (one IRC message per
|
|
@@ -283,9 +339,9 @@ given.
|
|
|
283
339
|
The local CLI ships a built-in TCP listener, so no bridge is needed for local
|
|
284
340
|
development. Deployed stacks (Cloudflare Workers and AWS API Gateway) are
|
|
285
341
|
WebSocket-only, so a stock TCP IRC client (WeeChat, HexChat, irssi, …) cannot
|
|
286
|
-
|
|
342
|
+
connect to them directly. `tools/tcp-ws-forwarder` is a local bridge for that case:
|
|
287
343
|
it listens on a TCP port and, for each connection, opens one WebSocket to a
|
|
288
|
-
`ws://` / `wss://` target and
|
|
344
|
+
`ws://` / `wss://` target and forwards IRC lines both directions — reassembling the TCP
|
|
289
345
|
byte stream into one message per WS frame outbound, and normalizing inbound
|
|
290
346
|
frames back to canonical CRLF.
|
|
291
347
|
|
|
@@ -330,9 +386,9 @@ against any line-oriented WebSocket endpoint.
|
|
|
330
386
|
|
|
331
387
|
## Load testing (`tools/load-test`)
|
|
332
388
|
|
|
333
|
-
A synthetic WebSocket IRC client
|
|
389
|
+
A synthetic WebSocket IRC client pool (PLAN §7). It opens N
|
|
334
390
|
connections to a `ws://` / `wss://` target, registers each (NICK/USER),
|
|
335
|
-
joins a channel, and optionally chats. Per-
|
|
391
|
+
joins a channel, and optionally chats. Per-stage **p50 / p95 / p99
|
|
336
392
|
latency** (connect / register / join / message) and the **drop rate**
|
|
337
393
|
are captured, then printed as a markdown report to stdout (and written
|
|
338
394
|
to `--report <path>` when given).
|
|
@@ -379,12 +435,12 @@ report).
|
|
|
379
435
|
|
|
380
436
|
## Web client (`apps/web`)
|
|
381
437
|
|
|
382
|
-
A vendored **Kiwi IRC** SPA served at `/
|
|
438
|
+
A vendored **Kiwi IRC** SPA served at `/webclient/` and a static project
|
|
383
439
|
landing page served at `/`, both baked out of `apps/web/dist/` and
|
|
384
440
|
served directly by the Cloudflare Worker via its `[assets]` binding.
|
|
385
|
-
The browser opens a native `wss://`
|
|
441
|
+
The browser opens a native `wss://` directly to the Worker's
|
|
386
442
|
IRC-over-WebSocket endpoint — no proxy, gateway, or transport
|
|
387
|
-
adaptation in the
|
|
443
|
+
adaptation in the request path. See `docs/WebClientGuide.md` for the
|
|
388
444
|
end-to-end contributor/operator guide.
|
|
389
445
|
|
|
390
446
|
Build the SPA + landing page (needs the submodule + `yarn`, provided by
|
|
@@ -394,8 +450,8 @@ Build the SPA + landing page (needs the submodule + `yarn`, provided by
|
|
|
394
450
|
git submodule update --init apps/web/upstream # one-time per clone
|
|
395
451
|
pnpm --filter web build:staging # or :prod / default
|
|
396
452
|
# → apps/web/dist/index.html (landing page, served at /)
|
|
397
|
-
# → apps/web/dist/
|
|
398
|
-
# → apps/web/dist/
|
|
453
|
+
# → apps/web/dist/webclient/index.html (Kiwi SPA, served at /webclient/)
|
|
454
|
+
# → apps/web/dist/webclient/static/config.json (baked, env-specific)
|
|
399
455
|
```
|
|
400
456
|
|
|
401
457
|
Then run everything locally (Worker serves SPA + landing page + WS on
|
|
@@ -405,7 +461,7 @@ one origin):
|
|
|
405
461
|
pnpm build # workspace packages
|
|
406
462
|
pnpm --filter @serverless-ircd/cf-worker dev # http://localhost:8787
|
|
407
463
|
# / → landing page
|
|
408
|
-
# /
|
|
464
|
+
# /webclient/ → Kiwi SPA (opens ws://localhost:8787/)
|
|
409
465
|
# /health → plaintext liveness
|
|
410
466
|
```
|
|
411
467
|
|
|
@@ -501,49 +557,49 @@ suite passes against every runtime, both adapters are deployed to staging,
|
|
|
501
557
|
and ≥3 reference clients (WeeChat, HexChat, IRCCloud, TheLounge) connect
|
|
502
558
|
cleanly. Per-release manifests live in `CHANGELOG.md`.
|
|
503
559
|
|
|
504
|
-
- **
|
|
505
|
-
- **
|
|
560
|
+
- **Foundation** (monorepo, turbo, vitest, biome, CI). ✅
|
|
561
|
+
- **Pure protocol engine** — reducers, IRCv3 caps, isupport,
|
|
506
562
|
SASL (`PLAIN`), token-bucket flood control, chat history. ✅ (most in
|
|
507
563
|
**v0.1.0**; SASL/flood/chathistory landed in **v0.2.0**).
|
|
508
|
-
-
|
|
564
|
+
- **`IrcRuntime` port** — in-memory runtime, `ConnectionActor`,
|
|
509
565
|
local CLI, parametrized contract suite. ✅ landed in **v0.1.0**.
|
|
510
|
-
- **
|
|
566
|
+
- **Cloudflare adapter** (ConnectionDO / ChannelDO / RegistryDO).
|
|
511
567
|
✅ landed in **v0.1.0** (staging deploy via CI; prod deploy is manual).
|
|
512
|
-
- **
|
|
568
|
+
- **AWS adapter** (APIGW WS + Lambda + DynamoDB + CDK).
|
|
513
569
|
✅ landed in **v0.2.0** (staging deploy via CI; prod deploy is manual).
|
|
514
|
-
- **
|
|
570
|
+
- **Observability, security hardening, config, CI gates.**
|
|
515
571
|
✅ landed across v0.2.0–v0.4.0 (logger port, server-password, cloaking,
|
|
516
572
|
admission limits, coverage + mutation gates, OPER credential auth, the
|
|
517
573
|
Cloudflare config loader, and AWS max-clients admission); the
|
|
518
574
|
required-`serverName` gate closed in **v0.6.0**.
|
|
519
|
-
- **
|
|
575
|
+
- **TLS hardening & raw TCP transport** (`irc+tls :6697`).
|
|
520
576
|
✅ landed in **v0.4.0**: generalized `ConnectionActor` transport seam,
|
|
521
577
|
Cloudflare Spectrum + Container origin, AWS NLB + Lambda streaming,
|
|
522
578
|
mTLS → SASL `EXTERNAL`, transport-parametrized contract suite, ADR-009.
|
|
523
|
-
- **
|
|
579
|
+
- **Load testing & compatibility sweep.** 🔄 partial as of
|
|
524
580
|
**v0.6.0**: the `tools/load-test` 10k-connection harness is in-tree;
|
|
525
581
|
the formal 10k-connection report per platform and the client
|
|
526
582
|
compatibility matrix (WeeChat / HexChat / IRCCloud / TheLounge) are
|
|
527
583
|
still pending.
|
|
528
|
-
- **
|
|
584
|
+
- **PLAN-FIXES remediation.** ✅ landed across v0.3.0–v0.4.0
|
|
529
585
|
(MessageStore wired into all three adapters, AccountStore end-to-end,
|
|
530
586
|
the DynamoDB `Accounts` table decided, CF enumeration RPCs, AWS QUIT
|
|
531
587
|
fanout, `OPER` / `TAGMSG` / `WHOWAS`, RFC 1459 case-mapping, real
|
|
532
588
|
MOTD, config-driven server identity, AWS max-clients admission,
|
|
533
589
|
CF D1 SASL store, and a sweep of dead-code / stale-comment cleanup).
|
|
534
|
-
- **
|
|
590
|
+
- **Runtime & tooling baseline.** ✅ landed in **v0.4.0**
|
|
535
591
|
(Node ≥ 24 / pnpm 11).
|
|
536
|
-
- **
|
|
592
|
+
- **IRCv3 WebSocket extension.** ✅ landed in **v0.4.0**:
|
|
537
593
|
`text.ircv3.net` / `binary.ircv3.net` subprotocol negotiation +
|
|
538
594
|
per-message framing across the CF Worker, AWS APIGW, local CLI, and
|
|
539
595
|
`tcp-ws-forwarder` (510-byte budget, `1009`/`1003` close codes,
|
|
540
596
|
legacy fallback).
|
|
541
|
-
- **
|
|
597
|
+
- **Deferred IRC verbs.** ✅ landed in **v0.5.0**: `KILL`,
|
|
542
598
|
`REHASH`, `LUSERS` + `STATS`, `TRACE`, `WALLOPS`, `SETNAME`, plus
|
|
543
599
|
the formal drop of the S2S (`CONNECT`/`SQUIT`/`LINKS`) and obsolete
|
|
544
600
|
RFC 2812 (`SERVICE`/`SUMMON`/`USERS`) verbs. The protocol surface is
|
|
545
601
|
feature-complete for modern clients.
|
|
546
|
-
- **
|
|
602
|
+
- **IRCv3 extension sweep.** ✅ landed in **v0.6.0**: ten
|
|
547
603
|
negotiated caps (`account-notify`, `msgid`, `standard-replies`,
|
|
548
604
|
`MONITOR`, `labeled-response`, `sts`, `draft/typing`,
|
|
549
605
|
`draft/multiline`, `draft/read-marker`, `draft/pre-away`), the
|
|
@@ -552,13 +608,21 @@ cleanly. Per-release manifests live in `CHANGELOG.md`.
|
|
|
552
608
|
(TLS connected), and the async push fanout seam (Registry hooks →
|
|
553
609
|
`MONITOR` online/offline, `BroadcastWallops`-style caps-gated
|
|
554
610
|
delivery).
|
|
555
|
-
- **
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
`
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
611
|
+
- **IRC services.** ✅ landed in **v0.8.0**: `ServicesStore` port
|
|
612
|
+
+ integrated NickServ (`REGISTER`/`IDENTIFY`/`DROP`/`INFO`/`SET
|
|
613
|
+
ENFORCE` + nick enforcement), ChanServ (`REGISTER`/`DROP`/`SET`/
|
|
614
|
+
`ACCESS`/`LEVELS` + `+r`/`+R`/`+M`), HostServ
|
|
615
|
+
(`ON`/`OFF`/`REQUEST` + oper `SET`/`APPROVE`), OperServ
|
|
616
|
+
(`AKILL`/`JUPE`/`RAW`), MemoServ (`SEND`/`LIST`/`READ`/`DEL`).
|
|
617
|
+
Persistent backends ship alongside — **D1** on Cloudflare,
|
|
618
|
+
**DynamoDB** on AWS — both write-behind so registrations survive
|
|
619
|
+
redeploys. The SASL `AccountStore` and NickServ accounts are
|
|
620
|
+
**unified** under one scrypt-hashed credential store, `PASS
|
|
621
|
+
<nick>:<password>` identifies at registration, and `draft/read-marker`
|
|
622
|
+
/ `draft/pre-away` now persist through the `ServicesStore` (no longer
|
|
623
|
+
in-memory-only). See `docs/Services.md`.
|
|
624
|
+
- **Web client.** 🔄 partial as of **v0.7.0**: a vendored
|
|
625
|
+
Kiwi IRC SPA is served at `/webclient/` and a static landing page at `/`
|
|
562
626
|
by the Cloudflare Worker via an `[assets]` binding, with a Zod-
|
|
563
627
|
validated per-env config matrix and **CSWSH defense** (same-origin
|
|
564
628
|
auto-derive + optional `WEB_ORIGINS` allowlist). The Playwright
|
|
@@ -594,10 +658,34 @@ RFC 2812 verbs `SERVICE`/`SUMMON`/`USERS` are **formally dropped** as of
|
|
|
594
658
|
v0.5.0 — they return `421 ERR_UNKNOWNCOMMAND`, and their reserved
|
|
595
659
|
numerics have been removed. S2S linking is a PLAN non-goal.)
|
|
596
660
|
|
|
597
|
-
**Channel modes:** `o v b i k l t n m s p
|
|
661
|
+
**Channel modes:** `o v b i k l t n m s p`, plus the
|
|
662
|
+
services-derived modes `r` (registered), `R` (block unidentified
|
|
663
|
+
join/message), `M` (moderated-identified) — settable only via ChanServ,
|
|
664
|
+
not via `MODE`.
|
|
598
665
|
**User modes:** `i`, `o` (local only), `w`, `s`, and the read-only `S`
|
|
599
666
|
(TLS connected — set by the transport at registration, surfaced in WHOIS
|
|
600
|
-
via `276 RPL_WHOISSECURE`, not settable via `MODE`)
|
|
667
|
+
via `276 RPL_WHOISSECURE`, not settable via `MODE`) and the read-only
|
|
668
|
+
`r` (registered — set by NickServ `IDENTIFY` / SASL login, surfaced as
|
|
669
|
+
`+r`, not settable via `MODE`).
|
|
670
|
+
|
|
671
|
+
**Integrated IRC services** (v0.8.0): the service nicks `NickServ`,
|
|
672
|
+
`ChanServ`, `HostServ`, `OperServ`, and `MemoServ` are reserved and
|
|
673
|
+
routed to dedicated pure reducers when a `ServicesStore` is bound —
|
|
674
|
+
there is **no separate services process and no S2S link**. Reached via
|
|
675
|
+
`PRIVMSG <Service> :<subcommand>`:
|
|
676
|
+
NickServ (`REGISTER` / `IDENTIFY` / `DROP` / `INFO` / `SET ENFORCE` +
|
|
677
|
+
nick enforcement on the `NICK` path), ChanServ (`REGISTER` / `DROP` /
|
|
678
|
+
`SET` / `INFO` / `ACCESS` / `LEVELS` + auto-op on `JOIN`), HostServ
|
|
679
|
+
(`ON` / `OFF` / `REQUEST` + oper `SET` / `APPROVE` / `LIST`, with
|
|
680
|
+
`CHGHOST` fanout), OperServ (`AKILL` / `JUPE` / `UNJUPE` / `RAW`), and
|
|
681
|
+
MemoServ (`SEND` / `LIST` / `READ` / `DEL` with queue delivery at
|
|
682
|
+
identify). The SASL `AccountStore` and NickServ accounts share one
|
|
683
|
+
scrypt-hashed credential store, so a registered nick is also a SASL
|
|
684
|
+
login and vice versa. Backends: D1 on Cloudflare, DynamoDB on AWS,
|
|
685
|
+
in-memory for the local CLI / tests (all write-behind; registrations
|
|
686
|
+
survive redeploys). When no store is bound, services commands reply
|
|
687
|
+
`501` and the rest of the daemon is unaffected. See
|
|
688
|
+
`docs/Services.md` for the full reference.
|
|
601
689
|
|
|
602
690
|
**IRCv3 extensions (negotiated via `CAP`):** `message-tags` (incl. the
|
|
603
691
|
`TAGMSG` command), `server-time`, `account-tag`, `account-notify`
|
|
@@ -614,7 +702,9 @@ shared between live and `draft/chathistory` replay), `standard-replies`
|
|
|
614
702
|
so compliant clients upgrade from plaintext to TLS and pin the secure
|
|
615
703
|
listener), `draft/chathistory`, `safelist`, `draft/typing` (typing-indicator
|
|
616
704
|
broadcast), `draft/multiline=<n>` (multi-line `BATCH`, default 4096-byte
|
|
617
|
-
budget), `draft/read-marker` (per-account persisted last-read
|
|
705
|
+
budget), `draft/read-marker` (per-account persisted last-read, with the
|
|
706
|
+
timestamp-based `MARKREAD <target> [timestamp]` verb plus the
|
|
707
|
+
`+draft/read-marker` tag fanout), and
|
|
618
708
|
`draft/pre-away` (per-account persisted away reason, replayed at
|
|
619
709
|
identify). Case-insensitive nick/channel comparison uses **RFC 1459**
|
|
620
710
|
case-mapping (advertised via `005 CASEMAPPING=rfc1459`). The new
|
|
@@ -648,11 +738,21 @@ bridges a stock TCP client to a deployed WebSocket endpoint.
|
|
|
648
738
|
required-`serverName` gate; `tools/load-test`; aws-adapter / aws-stack
|
|
649
739
|
coverage unblocks; CF packages moved to istanbul; deploys now
|
|
650
740
|
manual-only), and **v0.7.0** (the web client — vendored Kiwi IRC SPA
|
|
651
|
-
at `/
|
|
652
|
-
binding, per-env Zod-validated config matrix, and CSWSH
|
|
653
|
-
same-origin auto-derive + optional `WEB_ORIGINS`;
|
|
654
|
-
an explicit route; mutation-killing tests for
|
|
655
|
-
batch / numerics).
|
|
741
|
+
at `/webclient/`, static landing page at `/`, Cloudflare Worker
|
|
742
|
+
`[assets]` binding, per-env Zod-validated config matrix, and CSWSH
|
|
743
|
+
defense via same-origin auto-derive + optional `WEB_ORIGINS`;
|
|
744
|
+
`/health` becomes an explicit route; mutation-killing tests for
|
|
745
|
+
`irc-core`'s parser / batch / numerics), and **v0.8.0** (integrated
|
|
746
|
+
IRC services — NickServ / ChanServ / HostServ / OperServ / MemoServ
|
|
747
|
+
backed by a `ServicesStore` port with persistent D1 + DynamoDB
|
|
748
|
+
backends; unified SASL + NickServ scrypt-hashed account store;
|
|
749
|
+
`PASS <nick>:<password>` registration login; the `draft/read-marker`
|
|
750
|
+
`MARKREAD` verb).
|
|
751
|
+
- `docs/Services.md` — operator and contributor reference for the
|
|
752
|
+
integrated IRC services: architecture (integrated vs. pseudo-client),
|
|
753
|
+
the per-service command tables, the `+r`/`+R`/`+M` modes, nick
|
|
754
|
+
enforcement, adapter backends, and an end-to-end registration
|
|
755
|
+
walkthrough.
|
|
656
756
|
- `docs/WebClientGuide.md` — end-to-end contributor/operator doc for the
|
|
657
757
|
web client: build pipeline, per-env config matrix, CSWSH rationale and
|
|
658
758
|
the optional `WEB_ORIGINS` var, local dev, optional Cloudflare Pages
|