serverless-ircd 0.4.0 → 0.6.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 +34 -2
- package/.github/workflows/deploy-aws.yml +1 -2
- package/.github/workflows/deploy-cf-tcp.yml +1 -5
- package/.github/workflows/deploy-cf.yml +1 -2
- package/CHANGELOG.md +435 -0
- package/README.md +206 -27
- package/apps/aws-stack/README.md +37 -3
- package/apps/aws-stack/package.json +1 -1
- package/apps/aws-stack/src/aws-stack.ts +106 -13
- package/apps/aws-stack/tests/coverage-command.test.ts +155 -0
- package/apps/aws-stack/tests/stack.test.ts +49 -3
- package/apps/aws-stack/tests/synth-no-bundle.test.ts +129 -0
- package/apps/aws-stack/tests/vitest.setup.ts +18 -0
- package/apps/aws-stack/vitest.config.ts +3 -0
- package/apps/cf-tcp-container/package.json +1 -1
- package/apps/cf-tcp-container/src/config-loader.ts +6 -7
- package/apps/cf-tcp-container/src/container-server.ts +3 -0
- package/apps/cf-tcp-container/tests/config-loader.test.ts +10 -3
- package/apps/cf-tcp-container/wrangler.toml +1 -1
- package/apps/cf-worker/package.json +2 -1
- package/apps/cf-worker/vitest.config.ts +1 -1
- package/apps/cf-worker/wrangler.toml +5 -3
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +6 -6
- package/apps/local-cli/src/main.ts +20 -0
- package/apps/local-cli/src/server.ts +138 -28
- package/apps/local-cli/tests/e2e.test.ts +113 -29
- package/apps/local-cli/tests/tcp.test.ts +71 -19
- package/apps/local-cli/tests/ws-subprotocol.test.ts +262 -0
- package/biome.json +1 -0
- package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
- package/docs/ADR-002-location-of-authority.md +82 -0
- package/docs/ADR-003-durable-object-sharding.md +110 -0
- package/docs/ADR-004-dynamodb-schema.md +96 -0
- package/docs/ADR-005-wss-only-transport-v1.md +83 -0
- package/docs/ADR-006-sasl-mechanism-scope.md +96 -0
- package/docs/ADR-007-deterministic-ports.md +82 -0
- package/docs/ADR-008-monorepo-tooling.md +60 -0
- package/docs/ADR-009-dual-transport-wss-and-irc-tls.md +138 -0
- package/docs/ADR-Index.md +15 -0
- package/docs/AWS-Adapter-Architecture.md +496 -0
- package/docs/AWS-Deployment.md +1275 -0
- package/docs/AWS-TCP-Deployment.md +410 -0
- package/docs/Cloudflare-Deployment-Guide.md +719 -0
- package/docs/Cloudflare-TCP-Deployment.md +510 -0
- package/docs/Home.md +15 -0
- package/docs/Observability.md +87 -0
- package/docs/PlanExtensions.md +812 -0
- package/docs/PlanIRCv3Websocket.md +503 -0
- package/docs/PlanWebClient.md +451 -0
- package/docs/Release-Process.md +443 -0
- package/package.json +2 -2
- package/packages/aws-adapter/package.json +1 -1
- package/packages/aws-adapter/src/aws-runtime.ts +69 -0
- package/packages/aws-adapter/src/handlers/connect.ts +36 -5
- package/packages/aws-adapter/src/handlers/default.ts +66 -5
- package/packages/aws-adapter/src/handlers/index.ts +41 -2
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +18 -0
- package/packages/aws-adapter/src/index.ts +2 -0
- package/packages/aws-adapter/src/serialize.ts +40 -2
- package/packages/aws-adapter/src/stats.ts +80 -0
- package/packages/aws-adapter/tests/account-store-dynamo.test.ts +49 -8
- package/packages/aws-adapter/tests/account-store.test.ts +4 -7
- package/packages/aws-adapter/tests/aws-harness.ts +5 -2
- package/packages/aws-adapter/tests/aws-integration.test.ts +8 -4
- package/packages/aws-adapter/tests/aws-runtime.test.ts +140 -0
- package/packages/aws-adapter/tests/config-loader.test.ts +11 -2
- package/packages/aws-adapter/tests/connect.test.ts +100 -4
- package/packages/aws-adapter/tests/disconnect-fanout.test.ts +3 -4
- package/packages/aws-adapter/tests/global-setup.ts +55 -30
- package/packages/aws-adapter/tests/gone-exception.test.ts +4 -9
- package/packages/aws-adapter/tests/handlers.test.ts +238 -4
- package/packages/aws-adapter/tests/message-store.test.ts +6 -5
- package/packages/aws-adapter/tests/nlb-stream.test.ts +62 -8
- package/packages/aws-adapter/tests/stats.test.ts +317 -0
- package/packages/cf-adapter/package.json +6 -1
- package/packages/cf-adapter/src/cf-runtime.ts +66 -1
- package/packages/cf-adapter/src/channel-do.ts +2 -2
- package/packages/cf-adapter/src/connection-do.ts +185 -54
- package/packages/cf-adapter/src/env.ts +25 -6
- package/packages/cf-adapter/src/index.ts +2 -0
- package/packages/cf-adapter/src/registry-do.ts +22 -3
- package/packages/cf-adapter/src/serialize.ts +25 -4
- package/packages/cf-adapter/src/sharding.ts +1 -2
- package/packages/cf-adapter/src/stats.ts +65 -0
- package/packages/cf-adapter/tests/cf-harness.ts +1 -1
- package/packages/cf-adapter/tests/cf-integration.test.ts +4 -4
- package/packages/cf-adapter/tests/cf-runtime.test.ts +307 -2
- package/packages/cf-adapter/tests/channel-do.test.ts +119 -2
- package/packages/cf-adapter/tests/config-loader.test.ts +49 -0
- package/packages/cf-adapter/tests/connection-do-channel-registration.test.ts +2 -2
- package/packages/cf-adapter/tests/connection-do-no-batching-reservation.test.ts +2 -2
- package/packages/cf-adapter/tests/connection-do-pure.test.ts +91 -0
- package/packages/cf-adapter/tests/connection-do-ws-spec-contract.test.ts +289 -0
- package/packages/cf-adapter/tests/connection-do-ws-subprotocol.test.ts +184 -0
- package/packages/cf-adapter/tests/connection-do.test.ts +27 -2
- package/packages/cf-adapter/tests/d1-account-store.test.ts +48 -0
- package/packages/cf-adapter/tests/registry-do.test.ts +108 -4
- package/packages/cf-adapter/tests/serialize.test.ts +308 -0
- package/packages/cf-adapter/tests/sharding.test.ts +1 -1
- package/packages/cf-adapter/tests/stats.test.ts +120 -0
- package/packages/cf-adapter/tests/worker/main.ts +7 -7
- package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +2 -2
- package/packages/cf-adapter/tests/worker/stubs/registry-stub.ts +8 -2
- package/packages/cf-adapter/vitest.config.ts +1 -1
- package/packages/cf-adapter/wrangler.test.toml +7 -0
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/in-memory-runtime/src/in-memory-runtime.ts +39 -0
- package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +259 -0
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/src/admission.ts +16 -15
- package/packages/irc-core/src/caps/capabilities.ts +38 -4
- package/packages/irc-core/src/caps/index.ts +1 -0
- package/packages/irc-core/src/caps/sts.ts +84 -0
- package/packages/irc-core/src/commands/account-notify.ts +53 -0
- package/packages/irc-core/src/commands/away.ts +9 -3
- package/packages/irc-core/src/commands/cap.ts +23 -5
- package/packages/irc-core/src/commands/chathistory.ts +40 -11
- package/packages/irc-core/src/commands/index.ts +42 -0
- package/packages/irc-core/src/commands/invite.ts +2 -4
- package/packages/irc-core/src/commands/isupport.ts +59 -2
- package/packages/irc-core/src/commands/kick.ts +2 -4
- package/packages/irc-core/src/commands/kill.ts +127 -0
- package/packages/irc-core/src/commands/list.ts +1 -1
- package/packages/irc-core/src/commands/lusers.ts +204 -0
- package/packages/irc-core/src/commands/mode.ts +12 -9
- package/packages/irc-core/src/commands/monitor.ts +327 -0
- package/packages/irc-core/src/commands/multiline.ts +256 -0
- package/packages/irc-core/src/commands/names.ts +3 -5
- package/packages/irc-core/src/commands/part.ts +2 -4
- package/packages/irc-core/src/commands/pre-away.ts +112 -0
- package/packages/irc-core/src/commands/privmsg.ts +19 -12
- package/packages/irc-core/src/commands/read-marker.ts +108 -0
- package/packages/irc-core/src/commands/registration.ts +8 -0
- package/packages/irc-core/src/commands/rehash.ts +119 -0
- package/packages/irc-core/src/commands/sasl.ts +24 -1
- package/packages/irc-core/src/commands/setname.ts +109 -0
- package/packages/irc-core/src/commands/stats.ts +152 -0
- package/packages/irc-core/src/commands/tagmsg.ts +71 -1
- package/packages/irc-core/src/commands/topic.ts +2 -4
- package/packages/irc-core/src/commands/trace.ts +137 -0
- package/packages/irc-core/src/commands/wallops.ts +118 -0
- package/packages/irc-core/src/commands/whois.ts +5 -0
- package/packages/irc-core/src/config.ts +72 -10
- package/packages/irc-core/src/effects.ts +41 -1
- package/packages/irc-core/src/index.ts +2 -0
- package/packages/irc-core/src/ports.ts +568 -0
- package/packages/irc-core/src/protocol/index.ts +14 -0
- package/packages/irc-core/src/protocol/labeled-response.ts +133 -0
- package/packages/irc-core/src/protocol/numerics.ts +57 -11
- package/packages/irc-core/src/protocol/outbound.ts +36 -4
- package/packages/irc-core/src/protocol/standard-replies.ts +277 -0
- package/packages/irc-core/src/state/connection.ts +32 -1
- package/packages/irc-core/src/types.ts +120 -1
- package/packages/irc-core/src/ws-framing.ts +132 -0
- package/packages/irc-core/src/ws-subprotocol.ts +66 -0
- package/packages/irc-core/stryker.commands.conf.json +1 -2
- package/packages/irc-core/tests/admission.test.ts +18 -0
- package/packages/irc-core/tests/away-store.test.ts +73 -0
- package/packages/irc-core/tests/caps/capabilities.test.ts +119 -2
- package/packages/irc-core/tests/caps/sts.test.ts +123 -0
- package/packages/irc-core/tests/commands/account-notify.test.ts +171 -0
- package/packages/irc-core/tests/commands/away.test.ts +85 -2
- package/packages/irc-core/tests/commands/cap.test.ts +165 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +148 -1
- package/packages/irc-core/tests/commands/echo-message.test.ts +9 -3
- package/packages/irc-core/tests/commands/isupport.test.ts +80 -0
- package/packages/irc-core/tests/commands/kill.test.ts +243 -0
- package/packages/irc-core/tests/commands/lusers.test.ts +368 -0
- package/packages/irc-core/tests/commands/mode.test.ts +129 -0
- package/packages/irc-core/tests/commands/monitor.test.ts +411 -0
- package/packages/irc-core/tests/commands/multiline.test.ts +428 -0
- package/packages/irc-core/tests/commands/pre-away.test.ts +241 -0
- package/packages/irc-core/tests/commands/privmsg.test.ts +154 -3
- package/packages/irc-core/tests/commands/read-marker.test.ts +211 -0
- package/packages/irc-core/tests/commands/registration.test.ts +64 -0
- package/packages/irc-core/tests/commands/rehash.test.ts +171 -0
- package/packages/irc-core/tests/commands/sasl.test.ts +116 -2
- package/packages/irc-core/tests/commands/setname.test.ts +225 -0
- package/packages/irc-core/tests/commands/stats.test.ts +294 -0
- package/packages/irc-core/tests/commands/tagmsg.test.ts +357 -0
- package/packages/irc-core/tests/commands/trace.test.ts +282 -0
- package/packages/irc-core/tests/commands/wallops.test.ts +231 -0
- package/packages/irc-core/tests/commands/whois.test.ts +34 -0
- package/packages/irc-core/tests/config.test.ts +103 -13
- package/packages/irc-core/tests/dropped-s2s-and-obsolete-verbs.test.ts +90 -0
- package/packages/irc-core/tests/effects.test.ts +14 -0
- package/packages/irc-core/tests/labeled-response.test.ts +181 -0
- package/packages/irc-core/tests/message-tags.test.ts +114 -0
- package/packages/irc-core/tests/monitor-store.test.ts +200 -0
- package/packages/irc-core/tests/numerics.test.ts +90 -0
- package/packages/irc-core/tests/outbound.test.ts +51 -0
- package/packages/irc-core/tests/ports.test.ts +22 -0
- package/packages/irc-core/tests/raw-modules.d.ts +11 -0
- package/packages/irc-core/tests/read-marker-store.test.ts +108 -0
- package/packages/irc-core/tests/standard-replies.test.ts +412 -0
- package/packages/irc-core/tests/stats-store.test.ts +222 -0
- package/packages/irc-core/tests/types.test.ts +27 -0
- package/packages/irc-core/tests/ws-framing.test.ts +213 -0
- package/packages/irc-core/tests/ws-subprotocol.test.ts +111 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +699 -19
- package/packages/irc-server/src/dispatch.ts +109 -16
- package/packages/irc-server/src/routing.ts +3 -0
- package/packages/irc-server/src/runtime.ts +31 -0
- package/packages/irc-server/src/transport.ts +10 -7
- package/packages/irc-server/tests/actor.test.ts +2523 -42
- package/packages/irc-server/tests/dispatch.test.ts +300 -2
- package/packages/irc-server/tests/raw-modules.d.ts +11 -0
- package/packages/irc-server/tests/routing.test.ts +1 -0
- package/packages/irc-server/tests/runtime.test.ts +7 -0
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/index.ts +6 -0
- package/packages/irc-test-support/src/scenarios.ts +9 -1
- package/packages/irc-test-support/src/test-config.ts +54 -0
- package/packages/irc-test-support/tests/in-memory-scenarios.test.ts +1 -1
- package/packages/irc-test-support/tests/test-config.test.ts +51 -0
- package/pnpm-workspace.yaml +1 -0
- package/tools/ci-hardening/package.json +1 -1
- package/tools/ci-hardening/tests/ddb-local.test.ts +82 -0
- package/tools/load-test/package.json +33 -0
- package/tools/load-test/src/client.ts +351 -0
- package/tools/load-test/src/config.ts +313 -0
- package/tools/load-test/src/harness.ts +116 -0
- package/tools/load-test/src/main.ts +120 -0
- package/tools/load-test/src/metrics.ts +168 -0
- package/tools/load-test/src/report.ts +106 -0
- package/tools/load-test/tests/client.test.ts +212 -0
- package/tools/load-test/tests/config.test.ts +152 -0
- package/tools/load-test/tests/framing.test.ts +37 -0
- package/tools/load-test/tests/harness.test.ts +165 -0
- package/tools/load-test/tests/metrics.test.ts +174 -0
- package/tools/load-test/tests/report.test.ts +161 -0
- package/tools/load-test/tests/smoke.test.ts +67 -0
- package/tools/load-test/tsconfig.build.json +12 -0
- package/tools/load-test/tsconfig.test.json +10 -0
- package/tools/load-test/vitest.config.ts +29 -0
- package/tools/package.json +6 -1
- package/tools/seed-cf-accounts.ts +4 -1
- package/tools/tcp-ws-forwarder/package.json +1 -1
- package/tools/tcp-ws-forwarder/src/forwarder.ts +57 -9
- package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +34 -1
- package/tools/tcp-ws-forwarder/tests/framing.test.ts +65 -1
|
@@ -17,25 +17,72 @@
|
|
|
17
17
|
* pipeline can be reconstructed end-to-end.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import type {
|
|
21
|
-
|
|
20
|
+
import type {
|
|
21
|
+
BroadcastEffect,
|
|
22
|
+
Effect,
|
|
23
|
+
Logger,
|
|
24
|
+
MonitorStore,
|
|
25
|
+
RawLine,
|
|
26
|
+
} from '@serverless-ircd/irc-core';
|
|
27
|
+
import { LogLevel, filterClientTags } from '@serverless-ircd/irc-core';
|
|
22
28
|
import type { IrcRuntime } from './runtime.js';
|
|
23
29
|
|
|
24
|
-
/**
|
|
25
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Discriminated-union handler: each Effect tag maps to one runtime call.
|
|
32
|
+
* The handler also receives the bound {@link MonitorStore} (or
|
|
33
|
+
* `undefined`) so ReserveNick / ChangeNick / ReleaseNick can react to the
|
|
34
|
+
* runtime's success/failure and enqueue `730`/`731` pushes for subscribed
|
|
35
|
+
* watchers.
|
|
36
|
+
*/
|
|
37
|
+
type Handler<E extends Effect> = (
|
|
38
|
+
effect: E,
|
|
39
|
+
runtime: IrcRuntime,
|
|
40
|
+
monitor: MonitorStore | undefined,
|
|
41
|
+
) => Promise<void>;
|
|
26
42
|
|
|
27
43
|
const handlers: { [K in Effect['tag']]: Handler<Extract<Effect, { tag: K }>> } = {
|
|
28
44
|
Send: (e, r) => r.send(e.to, e.lines),
|
|
29
45
|
Broadcast: (e, r) => broadcast(e, r),
|
|
30
46
|
Disconnect: (e, r) => r.disconnect(e.conn, e.reason),
|
|
31
47
|
SendToNick: (e, r) => r.sendToNick(e.nick, e.sender, e.lines, e.notFoundLines),
|
|
32
|
-
ReserveNick: (e, r) =>
|
|
33
|
-
ChangeNick: (e, r) =>
|
|
34
|
-
ReleaseNick: (e, r) =>
|
|
48
|
+
ReserveNick: (e, r, m) => reserveNickEtc(e, r, m),
|
|
49
|
+
ChangeNick: (e, r, m) => changeNickEtc(e, r, m),
|
|
50
|
+
ReleaseNick: (e, r, m) => releaseNickEtc(e, r, m),
|
|
35
51
|
ApplyChannelDelta: (e, r) => r.applyChannelDelta(e.chan, e.delta),
|
|
52
|
+
BroadcastWallops: (e, r) => r.broadcastWallops(e.lines, e.except),
|
|
36
53
|
};
|
|
37
54
|
|
|
38
|
-
function
|
|
55
|
+
async function reserveNickEtc(
|
|
56
|
+
e: Extract<Effect, { tag: 'ReserveNick' }>,
|
|
57
|
+
r: IrcRuntime,
|
|
58
|
+
m: MonitorStore | undefined,
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
const result = await r.reserveNick(e.nick, e.conn);
|
|
61
|
+
if (result.ok) {
|
|
62
|
+
m?.onNickOnline(e.nick);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function changeNickEtc(
|
|
67
|
+
e: Extract<Effect, { tag: 'ChangeNick' }>,
|
|
68
|
+
r: IrcRuntime,
|
|
69
|
+
m: MonitorStore | undefined,
|
|
70
|
+
): Promise<void> {
|
|
71
|
+
const ok = await r.changeNick(e.conn, e.oldNick, e.newNick);
|
|
72
|
+
if (ok) {
|
|
73
|
+
m?.onNickOffline(e.oldNick);
|
|
74
|
+
m?.onNickOnline(e.newNick);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function releaseNickEtc(
|
|
79
|
+
e: Extract<Effect, { tag: 'ReleaseNick' }>,
|
|
80
|
+
r: IrcRuntime,
|
|
81
|
+
m: MonitorStore | undefined,
|
|
82
|
+
): Promise<void> {
|
|
83
|
+
await r.releaseNick(e.nick);
|
|
84
|
+
m?.onNickOffline(e.nick);
|
|
85
|
+
}
|
|
39
86
|
|
|
40
87
|
/**
|
|
41
88
|
* Optional knobs for {@link dispatch}. All observability-related; the
|
|
@@ -58,6 +105,16 @@ export interface DispatchOptions {
|
|
|
58
105
|
* Stamped on every record alongside `traceId`.
|
|
59
106
|
*/
|
|
60
107
|
readonly connectionId?: string;
|
|
108
|
+
/**
|
|
109
|
+
* Optional {@link MonitorStore} consulted by the ReserveNick /
|
|
110
|
+
* ChangeNick / ReleaseNick handlers. When supplied, successful nick
|
|
111
|
+
* registry transitions fan out an asynchronous `730`/`731` push to
|
|
112
|
+
* every subscribed watcher of the affected nick. The actor drains
|
|
113
|
+
* those pushes after `dispatch` returns and emits one `Send` effect
|
|
114
|
+
* per push. Omitted (→ no monitor fanout) preserves the existing
|
|
115
|
+
* dispatch behaviour for callers that have not opted into MONITOR.
|
|
116
|
+
*/
|
|
117
|
+
readonly monitor?: MonitorStore;
|
|
61
118
|
}
|
|
62
119
|
|
|
63
120
|
/**
|
|
@@ -106,17 +163,22 @@ export async function dispatch(
|
|
|
106
163
|
previous = effect;
|
|
107
164
|
continue;
|
|
108
165
|
}
|
|
109
|
-
await handle(effect, runtime, opts);
|
|
166
|
+
await handle(effect, runtime, opts, opts?.monitor);
|
|
110
167
|
previous = effect;
|
|
111
168
|
}
|
|
112
169
|
}
|
|
113
170
|
|
|
114
|
-
async function handle(
|
|
171
|
+
async function handle(
|
|
172
|
+
effect: Effect,
|
|
173
|
+
runtime: IrcRuntime,
|
|
174
|
+
opts: DispatchOptions | undefined,
|
|
175
|
+
monitor: MonitorStore | undefined,
|
|
176
|
+
): Promise<void> {
|
|
115
177
|
// The dispatch table is keyed by tag; the cast is the standard TS pattern
|
|
116
178
|
// for exhaustive-union dispatch.
|
|
117
179
|
const handler = handlers[effect.tag] as Handler<Effect>;
|
|
118
180
|
try {
|
|
119
|
-
await handler(effect, runtime);
|
|
181
|
+
await handler(effect, runtime, monitor);
|
|
120
182
|
} catch (err) {
|
|
121
183
|
if (opts?.logger !== undefined) {
|
|
122
184
|
opts.logger.on(logLevelFor(err), 'dispatch.error', {
|
|
@@ -174,11 +236,18 @@ function logLevelFor(_err: unknown): LogLevel {
|
|
|
174
236
|
* - `cap` without `capLines` (cap-only mode): `lines` go ONLY to
|
|
175
237
|
* cap-enabled members; the rest receive nothing.
|
|
176
238
|
*
|
|
239
|
+
* `caps` (plural) is the OR-semantics variant: `lines` go to members that
|
|
240
|
+
* announced ANY of the listed caps. Used by capabilities like
|
|
241
|
+
* `draft/typing` whose delivery rule unions `message-tags` (the baseline
|
|
242
|
+
* for every TAGMSG) with `draft/typing` (the client-tag exception for
|
|
243
|
+
* peers that did not negotiate `message-tags`). `caps` does not support
|
|
244
|
+
* a `capLines` split — both audiences receive the same `lines`.
|
|
245
|
+
*
|
|
177
246
|
* `except` is always honored. A member whose connection info is unavailable
|
|
178
247
|
* (the connection has gone) is treated as lacking the cap.
|
|
179
248
|
*/
|
|
180
249
|
async function broadcast(e: BroadcastEffect, r: IrcRuntime): Promise<void> {
|
|
181
|
-
if (e.cap === undefined) {
|
|
250
|
+
if (e.cap === undefined && e.caps === undefined) {
|
|
182
251
|
await r.broadcast(e.chan, e.lines, e.except);
|
|
183
252
|
return;
|
|
184
253
|
}
|
|
@@ -186,16 +255,40 @@ async function broadcast(e: BroadcastEffect, r: IrcRuntime): Promise<void> {
|
|
|
186
255
|
const snapshot = await r.getChannelSnapshot(e.chan);
|
|
187
256
|
if (snapshot === null) return;
|
|
188
257
|
|
|
258
|
+
// `caps` (OR semantics) takes precedence; otherwise the single `cap` is
|
|
259
|
+
// the only entry. The early-return above guarantees one of them is set.
|
|
260
|
+
const capsList: readonly string[] = e.caps ?? [e.cap as string];
|
|
189
261
|
for (const connId of snapshot.members.keys()) {
|
|
190
262
|
if (connId === e.except) continue;
|
|
191
263
|
|
|
192
264
|
const info = await r.getConnectionInfo(connId);
|
|
193
|
-
const
|
|
265
|
+
const recipientCaps = info?.caps ?? EMPTY_CAPS;
|
|
266
|
+
const hasAnyCap = capsList.some((c) => recipientCaps.has(c));
|
|
194
267
|
|
|
268
|
+
let toSend: RawLine[] | undefined;
|
|
195
269
|
if (e.capLines !== undefined) {
|
|
196
|
-
|
|
197
|
-
} else if (
|
|
198
|
-
|
|
270
|
+
toSend = hasAnyCap ? e.capLines : e.lines;
|
|
271
|
+
} else if (hasAnyCap) {
|
|
272
|
+
toSend = e.lines;
|
|
273
|
+
}
|
|
274
|
+
if (toSend !== undefined) {
|
|
275
|
+
await r.send(connId, filterLinesForRecipient(toSend, recipientCaps));
|
|
199
276
|
}
|
|
200
277
|
}
|
|
201
278
|
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Applies per-recipient IRCv3 message-tags gating to each outbound line.
|
|
282
|
+
* Client-only tags (`+`-prefixed keys) are stripped for recipients that did
|
|
283
|
+
* NOT negotiate `message-tags` (with the documented `draft/typing` /
|
|
284
|
+
* `draft/read-marker` whitelist exceptions). Recipients that DID negotiate
|
|
285
|
+
* `message-tags` see the line verbatim. Lines with no `@` tag section pass
|
|
286
|
+
* through unchanged.
|
|
287
|
+
*/
|
|
288
|
+
function filterLinesForRecipient(lines: readonly RawLine[], caps: ReadonlySet<string>): RawLine[] {
|
|
289
|
+
if (caps.has('message-tags')) return [...lines];
|
|
290
|
+
return lines.map((l) => filterClientTags(l, caps));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Reusable empty cap set for recipients whose connection info is unavailable. */
|
|
294
|
+
const EMPTY_CAPS: ReadonlySet<string> = new Set<string>();
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
privmsgChannelReducer,
|
|
53
53
|
privmsgUserReducer,
|
|
54
54
|
quitReducer,
|
|
55
|
+
setnameReducer,
|
|
55
56
|
tagmsgChannelReducer,
|
|
56
57
|
tagmsgUserReducer,
|
|
57
58
|
timeReducer,
|
|
@@ -80,6 +81,7 @@ export interface RoutedReducers {
|
|
|
80
81
|
readonly cap: Reducer<ConnectionState>;
|
|
81
82
|
readonly authenticate: Reducer<ConnectionState>;
|
|
82
83
|
readonly away: Reducer<ConnectionState>;
|
|
84
|
+
readonly setname: Reducer<ConnectionState>;
|
|
83
85
|
readonly version: Reducer<ConnectionState>;
|
|
84
86
|
readonly time: Reducer<ConnectionState>;
|
|
85
87
|
readonly admin: Reducer<ConnectionState>;
|
|
@@ -150,6 +152,7 @@ export function buildRoutedReducers(serverConfig: ServerConfig): RoutedReducers
|
|
|
150
152
|
cap: wrap(capReducer),
|
|
151
153
|
authenticate: wrap(authenticateReducer),
|
|
152
154
|
away: wrap(awayReducer),
|
|
155
|
+
setname: wrap(setnameReducer),
|
|
153
156
|
version: wrap(versionReducer),
|
|
154
157
|
time: wrap(timeReducer),
|
|
155
158
|
admin: wrap(adminReducer),
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
ConnectionState,
|
|
18
18
|
Nick,
|
|
19
19
|
RawLine,
|
|
20
|
+
ServerConfig,
|
|
20
21
|
} from '@serverless-ircd/irc-core';
|
|
21
22
|
|
|
22
23
|
export interface IrcRuntime {
|
|
@@ -35,6 +36,24 @@ export interface IrcRuntime {
|
|
|
35
36
|
lines: RawLine[],
|
|
36
37
|
notFoundLines?: RawLine[],
|
|
37
38
|
): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Global cross-connection fanout for `WALLOPS`. Delivers `lines` to every
|
|
41
|
+
* live connection whose `+w` user mode is set, skipping `except` (the
|
|
42
|
+
* originator, so an oper never receives their own wallops). Unlike
|
|
43
|
+
* {@link broadcast} this is NOT channel-scoped — it reaches connections
|
|
44
|
+
* with no shared channel membership.
|
|
45
|
+
*
|
|
46
|
+
* Each adapter applies its own cost cap and enumeration strategy:
|
|
47
|
+
* - in-memory: scans its connection map, filters `+w`.
|
|
48
|
+
* - Cloudflare: walks the sharded registry to enumerate connIds, then
|
|
49
|
+
* fetches each state to check `+w`. Capped to bound the fan-out RPC
|
|
50
|
+
* count; document a ceiling for very large fleets.
|
|
51
|
+
* - AWS: `Scan`s the `Connections` table and `PostToConnection`s each
|
|
52
|
+
* `+w` row. DynamoDB `Scan` reads scale with table size, so this is
|
|
53
|
+
* expensive at high connection counts — callers should bound the
|
|
54
|
+
* oper's WALLOPS rate.
|
|
55
|
+
*/
|
|
56
|
+
broadcastWallops(lines: RawLine[], except?: ConnId): Promise<void>;
|
|
38
57
|
|
|
39
58
|
// nickname registry
|
|
40
59
|
reserveNick(nick: Nick, conn: ConnId): Promise<{ ok: true } | { ok: false }>;
|
|
@@ -58,4 +77,16 @@ export interface IrcRuntime {
|
|
|
58
77
|
getChannelSnapshot(chan: ChanName): Promise<ChanSnapshot | null>;
|
|
59
78
|
getChannelConnections(chan: ChanName): Promise<ReadonlyMap<ConnId, ConnectionState>>;
|
|
60
79
|
listChannels(): Promise<ChanSnapshot[]>;
|
|
80
|
+
/**
|
|
81
|
+
* Re-fetches the deployment's {@link ServerConfig} from the adapter's bound
|
|
82
|
+
* source (CF: KV/secret env; AWS: Secrets Manager/SSM env; local-cli: the
|
|
83
|
+
* config file/options) and returns the fresh value. The caller (the
|
|
84
|
+
* ConnectionActor's `REHASH` route) swaps its live config reference with
|
|
85
|
+
* the result; on rejection the previous config stays in effect.
|
|
86
|
+
*
|
|
87
|
+
* Modelled as a request/response runtime method — like `lookupNick` /
|
|
88
|
+
* `getConnection` — rather than a fire-and-forget `Effect`, because the
|
|
89
|
+
* refreshed value must come back to the actor.
|
|
90
|
+
*/
|
|
91
|
+
reloadConfig(): Promise<ServerConfig>;
|
|
61
92
|
}
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
* parser/reducer/dispatch pipeline.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { splitFrameLines } from '@serverless-ircd/irc-core';
|
|
19
|
+
|
|
20
|
+
export { splitFrameLines };
|
|
21
|
+
|
|
18
22
|
/**
|
|
19
23
|
* The line-framing contract a {@link ConnectionActor} consumes. `feed`
|
|
20
24
|
* returns the complete IRC lines terminated within `chunk`; a stateful
|
|
@@ -25,14 +29,13 @@ export interface Transport {
|
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
/**
|
|
28
|
-
* Splits a WebSocket text frame into IRC lines.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
32
|
+
* Splits a WebSocket text frame into IRC lines. Re-exported from
|
|
33
|
+
* `@serverless-ircd/irc-core` (`ws-framing.ts`), which owns the canonical
|
|
34
|
+
* legacy-tolerant framing so the IRCv3 spec-mode framing and this legacy
|
|
35
|
+
* behaviour never diverge. Tolerates `\r\n`, bare `\n`, and a missing
|
|
36
|
+
* trailing terminator (a WS frame is a complete unit). Returns each line
|
|
37
|
+
* bare (no CRLF). This is the legacy-tolerant WS contract.
|
|
31
38
|
*/
|
|
32
|
-
export function splitFrameLines(text: string): string[] {
|
|
33
|
-
if (text.length === 0) return [];
|
|
34
|
-
return text.split(/\r?\n/u);
|
|
35
|
-
}
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
41
|
* Stateless WS-frame transport: one {@link feed} call per inbound text frame.
|