serverless-ircd 0.1.0 → 0.3.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 +96 -2
- package/.github/workflows/deploy-aws.yml +129 -0
- package/.github/workflows/deploy-cf.yml +0 -2
- package/.gitmodules +3 -0
- package/CHANGELOG.md +436 -0
- package/README.md +127 -84
- package/apps/aws-stack/README.md +73 -0
- package/apps/aws-stack/bin/aws.ts +49 -0
- package/apps/aws-stack/cdk.json +10 -0
- package/apps/aws-stack/package.json +41 -0
- package/apps/aws-stack/scripts/smoke-helpers.d.mts +22 -0
- package/apps/aws-stack/scripts/smoke-helpers.mjs +89 -0
- package/apps/aws-stack/scripts/smoke.mjs +142 -0
- package/apps/aws-stack/src/aws-stack.ts +263 -0
- package/apps/aws-stack/src/tables.ts +10 -0
- package/apps/aws-stack/tests/localstack.test.ts +46 -0
- package/apps/aws-stack/tests/smoke-helpers.test.ts +98 -0
- package/apps/aws-stack/tests/stack.test.ts +464 -0
- package/apps/aws-stack/tsconfig.build.json +11 -0
- package/apps/aws-stack/tsconfig.test.json +8 -0
- package/apps/aws-stack/vitest.config.ts +20 -0
- package/apps/cf-worker/package.json +1 -1
- package/apps/cf-worker/scripts/smoke.mjs +0 -0
- package/apps/cf-worker/src/worker.ts +8 -2
- package/apps/cf-worker/tests/smoke.test.ts +1 -0
- package/apps/cf-worker/wrangler.test.toml +5 -1
- package/apps/cf-worker/wrangler.toml +66 -17
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +57 -0
- package/apps/local-cli/src/main.ts +1 -1
- package/apps/local-cli/src/server.ts +267 -32
- package/apps/local-cli/tests/config-loader.test.ts +107 -0
- package/apps/local-cli/tests/e2e.test.ts +126 -0
- package/apps/local-cli/tests/security-e2e.test.ts +239 -0
- package/biome.json +3 -1
- 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 +93 -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 +86 -0
- package/docs/ADR-007-deterministic-ports.md +82 -0
- package/docs/ADR-008-monorepo-tooling.md +60 -0
- package/docs/AWS-Adapter-Architecture.md +496 -0
- package/docs/AWS-Deployment.md +1186 -0
- package/docs/Cloudflare-Deployment-Guide.md +660 -0
- package/docs/Home.md +11 -0
- package/docs/Observability.md +87 -0
- package/docs/PlanIRCv3Websocket.md +489 -0
- package/docs/PlanWebClient.md +451 -0
- package/docs/Release-Process.md +443 -0
- package/package.json +19 -14
- package/packages/aws-adapter/README.md +35 -0
- package/packages/aws-adapter/package.json +52 -0
- package/packages/aws-adapter/src/account-store.ts +121 -0
- package/packages/aws-adapter/src/aws-runtime.ts +871 -0
- package/packages/aws-adapter/src/cdk-table-defs.ts +73 -0
- package/packages/aws-adapter/src/config-loader.ts +126 -0
- package/packages/aws-adapter/src/dynamo-account-store.ts +156 -0
- package/packages/aws-adapter/src/dynamo.ts +61 -0
- package/packages/aws-adapter/src/handlers/connect.ts +44 -0
- package/packages/aws-adapter/src/handlers/default.ts +330 -0
- package/packages/aws-adapter/src/handlers/disconnect.ts +48 -0
- package/packages/aws-adapter/src/handlers/index.ts +293 -0
- package/packages/aws-adapter/src/handlers/ping-checker.ts +217 -0
- package/packages/aws-adapter/src/handlers/state.ts +13 -0
- package/packages/aws-adapter/src/handlers/sweeper.ts +84 -0
- package/packages/aws-adapter/src/index.ts +74 -0
- package/packages/aws-adapter/src/message-store.ts +34 -0
- package/packages/aws-adapter/src/serialize.ts +283 -0
- package/packages/aws-adapter/src/tables.ts +53 -0
- package/packages/aws-adapter/tests/account-store-dynamo.test.ts +171 -0
- package/packages/aws-adapter/tests/account-store.test.ts +280 -0
- package/packages/aws-adapter/tests/aws-harness.ts +408 -0
- package/packages/aws-adapter/tests/aws-integration.test.ts +17 -0
- package/packages/aws-adapter/tests/aws-runtime.test.ts +654 -0
- package/packages/aws-adapter/tests/config-loader.test.ts +213 -0
- package/packages/aws-adapter/tests/disconnect-fanout.test.ts +336 -0
- package/packages/aws-adapter/tests/dynamo.test.ts +57 -0
- package/packages/aws-adapter/tests/global-setup.ts +301 -0
- package/packages/aws-adapter/tests/gone-exception.test.ts +271 -0
- package/packages/aws-adapter/tests/handlers.test.ts +473 -0
- package/packages/aws-adapter/tests/message-store.test.ts +55 -0
- package/packages/aws-adapter/tests/ping-checker.test.ts +571 -0
- package/packages/aws-adapter/tests/serialize.test.ts +249 -0
- package/packages/aws-adapter/tests/smoke.test.ts +48 -0
- package/packages/aws-adapter/tests/sweeper.test.ts +420 -0
- package/packages/aws-adapter/tests/tables.test.ts +74 -0
- package/packages/aws-adapter/tests/transactions.test.ts +446 -0
- package/packages/aws-adapter/tsconfig.build.json +16 -0
- package/packages/aws-adapter/tsconfig.test.json +14 -0
- package/packages/aws-adapter/vitest.config.ts +23 -0
- package/packages/cf-adapter/package.json +2 -1
- package/packages/cf-adapter/src/cf-runtime.ts +42 -22
- package/packages/cf-adapter/src/channel-do.ts +40 -1
- package/packages/cf-adapter/src/channel-registry-do.ts +58 -0
- package/packages/cf-adapter/src/config-loader.ts +135 -0
- package/packages/cf-adapter/src/connection-do.ts +119 -0
- package/packages/cf-adapter/src/env.ts +27 -1
- package/packages/cf-adapter/src/index.ts +2 -1
- package/packages/cf-adapter/tests/cf-harness.ts +2 -2
- package/packages/cf-adapter/tests/cf-integration.test.ts +2 -2
- package/packages/cf-adapter/tests/cf-runtime.test.ts +91 -0
- package/packages/cf-adapter/tests/config-loader.test.ts +149 -0
- package/packages/cf-adapter/tests/connection-do.test.ts +82 -0
- package/packages/cf-adapter/tests/worker/main.ts +2 -1
- package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +7 -1
- package/packages/cf-adapter/wrangler.test.toml +10 -1
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/in-memory-runtime/src/in-memory-runtime.ts +107 -16
- package/packages/in-memory-runtime/src/index.ts +1 -1
- package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +134 -0
- package/packages/irc-core/package.json +13 -2
- package/packages/irc-core/src/admission.ts +216 -0
- package/packages/irc-core/src/caps/capabilities.ts +1 -0
- package/packages/irc-core/src/case-fold.ts +64 -0
- package/packages/irc-core/src/cloak.ts +81 -0
- package/packages/irc-core/src/commands/cap.ts +1 -2
- package/packages/irc-core/src/commands/chathistory.ts +305 -0
- package/packages/irc-core/src/commands/index.ts +9 -0
- package/packages/irc-core/src/commands/invite.ts +6 -9
- package/packages/irc-core/src/commands/isupport.ts +1 -1
- package/packages/irc-core/src/commands/join.ts +63 -10
- package/packages/irc-core/src/commands/kick.ts +4 -11
- package/packages/irc-core/src/commands/list.ts +5 -4
- package/packages/irc-core/src/commands/mode.ts +5 -9
- package/packages/irc-core/src/commands/motd-lines.ts +127 -0
- package/packages/irc-core/src/commands/motd.ts +6 -110
- package/packages/irc-core/src/commands/oper.ts +151 -0
- package/packages/irc-core/src/commands/privmsg.ts +24 -0
- package/packages/irc-core/src/commands/registration.ts +79 -21
- package/packages/irc-core/src/commands/sasl.ts +251 -0
- package/packages/irc-core/src/commands/tagmsg.ts +205 -0
- package/packages/irc-core/src/config.ts +270 -0
- package/packages/irc-core/src/flood-control.ts +175 -0
- package/packages/irc-core/src/index.ts +7 -0
- package/packages/irc-core/src/ports.ts +719 -0
- package/packages/irc-core/src/protocol/base64.ts +16 -0
- package/packages/irc-core/src/protocol/index.ts +2 -1
- package/packages/irc-core/src/protocol/numerics.ts +11 -0
- package/packages/irc-core/src/protocol/parser.ts +27 -2
- package/packages/irc-core/src/state/connection.ts +41 -0
- package/packages/irc-core/src/types.ts +88 -2
- package/packages/irc-core/stryker.commands.conf.json +41 -0
- package/packages/irc-core/stryker.protocol.conf.json +26 -0
- package/packages/irc-core/tests/account-store.test.ts +88 -0
- package/packages/irc-core/tests/admission.test.ts +229 -0
- package/packages/irc-core/tests/caps/capabilities.test.ts +22 -0
- package/packages/irc-core/tests/case-fold.test.ts +80 -0
- package/packages/irc-core/tests/cloak.test.ts +78 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +996 -0
- package/packages/irc-core/tests/commands/join.test.ts +246 -2
- package/packages/irc-core/tests/commands/kick.test.ts +15 -0
- package/packages/irc-core/tests/commands/oper.test.ts +257 -0
- package/packages/irc-core/tests/commands/privmsg.test.ts +146 -2
- package/packages/irc-core/tests/commands/registration.test.ts +380 -20
- package/packages/irc-core/tests/commands/sasl.test.ts +536 -0
- package/packages/irc-core/tests/commands/tagmsg.test.ts +688 -0
- package/packages/irc-core/tests/commands/who.test.ts +22 -4
- package/packages/irc-core/tests/commands/whois.test.ts +8 -1
- package/packages/irc-core/tests/config.test.ts +721 -0
- package/packages/irc-core/tests/flood-control.test.ts +394 -0
- package/packages/irc-core/tests/message-store.test.ts +530 -0
- package/packages/irc-core/tests/parser.test.ts +44 -1
- package/packages/irc-core/tests/ports.test.ts +263 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +186 -44
- package/packages/irc-server/src/dispatch.ts +89 -5
- package/packages/irc-server/src/index.ts +2 -0
- package/packages/irc-server/src/routing.ts +160 -0
- package/packages/irc-server/tests/actor.test.ts +690 -15
- package/packages/irc-server/tests/dispatch.test.ts +84 -0
- package/packages/irc-server/tests/routing.test.ts +204 -0
- package/packages/irc-test-support/README.md +32 -0
- package/packages/irc-test-support/package.json +37 -0
- package/packages/{cf-adapter/tests/integration → irc-test-support/src}/in-memory-harness.ts +6 -5
- package/packages/irc-test-support/src/index.ts +24 -0
- package/packages/{cf-adapter/tests/integration/harness.test.ts → irc-test-support/tests/in-memory-harness.test.ts} +1 -1
- package/packages/{cf-adapter/tests/integration/scenarios.test.ts → irc-test-support/tests/in-memory-scenarios.test.ts} +1 -2
- package/packages/irc-test-support/tests/smoke.test.ts +11 -0
- package/packages/irc-test-support/tsconfig.build.json +16 -0
- package/packages/irc-test-support/tsconfig.test.json +14 -0
- package/packages/irc-test-support/vitest.config.ts +20 -0
- package/pnpm-workspace.yaml +1 -0
- package/tools/ci-hardening/package.json +30 -0
- package/tools/ci-hardening/src/index.ts +6 -0
- package/tools/ci-hardening/src/validate.ts +103 -0
- package/tools/ci-hardening/tests/__no_thresholds__.txt +11 -0
- package/tools/ci-hardening/tests/__partial_thresholds__.txt +16 -0
- package/tools/ci-hardening/tests/__stryker_bad__.json +4 -0
- package/tools/ci-hardening/tests/validate.test.ts +177 -0
- package/tools/ci-hardening/tsconfig.build.json +12 -0
- package/tools/ci-hardening/tsconfig.test.json +10 -0
- package/tools/ci-hardening/vitest.config.ts +21 -0
- package/tools/seed-aws-accounts.ts +80 -0
- package/tools/tcp-ws-forwarder/package.json +1 -1
- package/tools/tcp-ws-forwarder/src/forwarder.ts +34 -23
- package/tools/tcp-ws-forwarder/src/logger.ts +155 -0
- package/tools/tcp-ws-forwarder/src/main.ts +33 -14
- package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +86 -0
- package/tools/tcp-ws-forwarder/tests/logger.test.ts +136 -0
- package/packages/cf-adapter/tests/integration/index.ts +0 -17
- /package/packages/{cf-adapter/tests/integration → irc-test-support/src}/harness.ts +0 -0
- /package/packages/{cf-adapter/tests/integration → irc-test-support/src}/scenarios.ts +0 -0
|
@@ -9,9 +9,16 @@
|
|
|
9
9
|
* - Consecutive identical Send effects coalesce into one runtime call
|
|
10
10
|
* (the test named "dedup").
|
|
11
11
|
* - The first runtime rejection propagates and stops processing.
|
|
12
|
+
*
|
|
13
|
+
* When an optional {@link DispatchOptions.logger} is supplied, dispatch
|
|
14
|
+
* emits a `dispatch` debug record describing the effect count and a
|
|
15
|
+
* `dispatch.error` warn record when a handler rejects — both stamped
|
|
16
|
+
* with the caller's `traceId`/`connectionId` so the actor → runtime
|
|
17
|
+
* pipeline can be reconstructed end-to-end.
|
|
12
18
|
*/
|
|
13
19
|
|
|
14
|
-
import type { BroadcastEffect, Effect, RawLine } from '@serverless-ircd/irc-core';
|
|
20
|
+
import type { BroadcastEffect, Effect, Logger, RawLine } from '@serverless-ircd/irc-core';
|
|
21
|
+
import { LogLevel } from '@serverless-ircd/irc-core';
|
|
15
22
|
import type { IrcRuntime } from './runtime.js';
|
|
16
23
|
|
|
17
24
|
/** Discriminated-union handler: each Effect tag maps to one runtime call. */
|
|
@@ -33,6 +40,29 @@ const handlers: { [K in Effect['tag']]: Handler<Extract<Effect, { tag: K }>> } =
|
|
|
33
40
|
|
|
34
41
|
function noop(): void {}
|
|
35
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Optional knobs for {@link dispatch}. All observability-related; the
|
|
45
|
+
* core effect-interpretation behaviour is unchanged when they are absent.
|
|
46
|
+
*/
|
|
47
|
+
export interface DispatchOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Logger to emit `dispatch` / `dispatch.error` records to. When omitted,
|
|
50
|
+
* dispatch is silent — preserving the default silent contract.
|
|
51
|
+
*/
|
|
52
|
+
readonly logger?: Logger;
|
|
53
|
+
/**
|
|
54
|
+
* Trace id to stamp on every log record. When omitted, records carry
|
|
55
|
+
* `traceId: undefined` (loggers drop undefined fields, so the field is
|
|
56
|
+
* absent from the JSON line).
|
|
57
|
+
*/
|
|
58
|
+
readonly traceId?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Connection id the dispatch is serving (the actor's bound connId).
|
|
61
|
+
* Stamped on every record alongside `traceId`.
|
|
62
|
+
*/
|
|
63
|
+
readonly connectionId?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
36
66
|
/**
|
|
37
67
|
* Returns true iff two effects are interchangeable — used to coalesce
|
|
38
68
|
* consecutive duplicates so a reducer's accidental double-send is a no-op.
|
|
@@ -61,24 +91,78 @@ function linesEqual(a: RawLine[], b: RawLine[]): boolean {
|
|
|
61
91
|
/**
|
|
62
92
|
* Interprets `effects` in order against `runtime`. Returns when every
|
|
63
93
|
* effect has been performed; the first rejection propagates.
|
|
94
|
+
*
|
|
95
|
+
* When `opts.logger` is supplied a `dispatch` debug record is emitted
|
|
96
|
+
* before the loop begins (with the effect count and a per-tag histogram).
|
|
64
97
|
*/
|
|
65
|
-
export async function dispatch(
|
|
98
|
+
export async function dispatch(
|
|
99
|
+
effects: Effect[],
|
|
100
|
+
runtime: IrcRuntime,
|
|
101
|
+
opts?: DispatchOptions,
|
|
102
|
+
): Promise<void> {
|
|
103
|
+
if (opts?.logger !== undefined) {
|
|
104
|
+
logDispatch(opts.logger, opts.traceId, opts.connectionId, effects);
|
|
105
|
+
}
|
|
66
106
|
let previous: Effect | undefined;
|
|
67
107
|
for (const effect of effects) {
|
|
68
108
|
if (previous !== undefined && isSameSend(previous, effect)) {
|
|
69
109
|
previous = effect;
|
|
70
110
|
continue;
|
|
71
111
|
}
|
|
72
|
-
await handle(effect, runtime);
|
|
112
|
+
await handle(effect, runtime, opts);
|
|
73
113
|
previous = effect;
|
|
74
114
|
}
|
|
75
115
|
}
|
|
76
116
|
|
|
77
|
-
async function handle(effect: Effect, runtime: IrcRuntime): Promise<void> {
|
|
117
|
+
async function handle(effect: Effect, runtime: IrcRuntime, opts?: DispatchOptions): Promise<void> {
|
|
78
118
|
// The dispatch table is keyed by tag; the cast is the standard TS pattern
|
|
79
119
|
// for exhaustive-union dispatch.
|
|
80
120
|
const handler = handlers[effect.tag] as Handler<Effect>;
|
|
81
|
-
|
|
121
|
+
try {
|
|
122
|
+
await handler(effect, runtime);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
if (opts?.logger !== undefined) {
|
|
125
|
+
opts.logger.on(logLevelFor(err), 'dispatch.error', {
|
|
126
|
+
traceId: opts.traceId,
|
|
127
|
+
connectionId: opts.connectionId,
|
|
128
|
+
effect: effect.tag,
|
|
129
|
+
err: err instanceof Error ? { name: err.name, message: err.message } : String(err),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Emits one `dispatch` debug record per call with a per-tag histogram so
|
|
138
|
+
* dashboards can slice "broadcasts per frame", "sends per frame", etc.
|
|
139
|
+
*/
|
|
140
|
+
function logDispatch(
|
|
141
|
+
logger: Logger,
|
|
142
|
+
traceId: string | undefined,
|
|
143
|
+
connectionId: string | undefined,
|
|
144
|
+
effects: Effect[],
|
|
145
|
+
): void {
|
|
146
|
+
const histogram: Record<string, number> = {};
|
|
147
|
+
for (const e of effects) {
|
|
148
|
+
histogram[e.tag] = (histogram[e.tag] ?? 0) + 1;
|
|
149
|
+
}
|
|
150
|
+
logger.on(LogLevel.Debug, 'dispatch', {
|
|
151
|
+
traceId,
|
|
152
|
+
connectionId,
|
|
153
|
+
effects: effects.map((e) => e.tag),
|
|
154
|
+
histogram,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Maps a thrown error to the level dispatch should log it at. Today every
|
|
160
|
+
* dispatch rejection is operational (transient network / DO fetch issues),
|
|
161
|
+
* so warn is appropriate; handlers do not throw for input-shape problems
|
|
162
|
+
* (those surface as IRC numerics via Send effects).
|
|
163
|
+
*/
|
|
164
|
+
function logLevelFor(_err: unknown): LogLevel {
|
|
165
|
+
return LogLevel.Warn;
|
|
82
166
|
}
|
|
83
167
|
|
|
84
168
|
/**
|
|
@@ -9,3 +9,5 @@ export type { IrcRuntime } from './runtime.js';
|
|
|
9
9
|
export { dispatch } from './dispatch.js';
|
|
10
10
|
export { ConnectionActor, splitFrameLines } from './actor.js';
|
|
11
11
|
export type { ActorChannelAccess, ConnectionActorOptions } from './actor.js';
|
|
12
|
+
export { buildRoutedReducers, resolveFloodControl } from './routing.js';
|
|
13
|
+
export type { RoutedReducers } from './routing.js';
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routed reducer map construction for the {@link ConnectionActor}.
|
|
3
|
+
*
|
|
4
|
+
* Every command the actor dispatches runs through one of the reducers in
|
|
5
|
+
* this map. {@link buildRoutedReducers} wraps each reducer with the
|
|
6
|
+
* per-connection flood-control token bucket so a flooding client is
|
|
7
|
+
* disconnected end-to-end regardless of whether the routed command mutates
|
|
8
|
+
* connection state (PING/USER/…) or channel state (JOIN/PRIVMSG/…). Flood
|
|
9
|
+
* control bounds bytes-in from the client, not which authority owns the
|
|
10
|
+
* state the command touches.
|
|
11
|
+
*
|
|
12
|
+
* The wrapper reads `ctx.clock` (the actor's injected {@link Clock}) and
|
|
13
|
+
* stores the bucket on `ctx.connection.floodBucket`, so the same time source
|
|
14
|
+
* the rest of the actor uses drives refill. Extracted as a pure helper so the
|
|
15
|
+
* actor and unit tests can inspect the wrapped map without spinning up a full
|
|
16
|
+
* actor + fake runtime.
|
|
17
|
+
*
|
|
18
|
+
* Three reducers (`listReducer`, `whoReducer`, `whoisReducer`) take a bespoke
|
|
19
|
+
* signature that fetches runtime state before invocation; they do not fit the
|
|
20
|
+
* `Reducer<S>` shape the flood wrapper operates on and are therefore left
|
|
21
|
+
* unwrapped here. They are read-only query paths; the per-connection bucket
|
|
22
|
+
* still bounds the write-path commands a client can issue around them.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
type ChannelState,
|
|
27
|
+
type ConnectionState,
|
|
28
|
+
DEFAULT_FLOOD_CONTROL_CONFIG,
|
|
29
|
+
type FloodControlConfig,
|
|
30
|
+
type Reducer,
|
|
31
|
+
type ServerConfig,
|
|
32
|
+
authenticateReducer,
|
|
33
|
+
awayReducer,
|
|
34
|
+
capReducer,
|
|
35
|
+
channelModeReducer,
|
|
36
|
+
defaultCommandCost,
|
|
37
|
+
inviteReducer,
|
|
38
|
+
joinReducer,
|
|
39
|
+
kickReducer,
|
|
40
|
+
motdReducer,
|
|
41
|
+
namesReducer,
|
|
42
|
+
nickReducer,
|
|
43
|
+
noticeChannelReducer,
|
|
44
|
+
noticeUserReducer,
|
|
45
|
+
operReducer,
|
|
46
|
+
partReducer,
|
|
47
|
+
passReducer,
|
|
48
|
+
pingReducer,
|
|
49
|
+
pongReducer,
|
|
50
|
+
privmsgChannelReducer,
|
|
51
|
+
privmsgUserReducer,
|
|
52
|
+
quitReducer,
|
|
53
|
+
tagmsgChannelReducer,
|
|
54
|
+
tagmsgUserReducer,
|
|
55
|
+
topicReducer,
|
|
56
|
+
userModeReducer,
|
|
57
|
+
userReducer,
|
|
58
|
+
wrapWithFloodControl,
|
|
59
|
+
} from '@serverless-ircd/irc-core';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The wrapped reducer for every command the actor routes by name. Keyed by
|
|
63
|
+
* logical command rather than IRC verb because PRIVMSG/NOTICE/MODE each fan
|
|
64
|
+
* out to two reducers depending on the target shape (channel vs. user).
|
|
65
|
+
*/
|
|
66
|
+
export interface RoutedReducers {
|
|
67
|
+
// Connection-authority reducers.
|
|
68
|
+
readonly ping: Reducer<ConnectionState>;
|
|
69
|
+
readonly pong: Reducer<ConnectionState>;
|
|
70
|
+
readonly nick: Reducer<ConnectionState>;
|
|
71
|
+
readonly user: Reducer<ConnectionState>;
|
|
72
|
+
readonly pass: Reducer<ConnectionState>;
|
|
73
|
+
readonly quit: Reducer<ConnectionState>;
|
|
74
|
+
readonly motd: Reducer<ConnectionState>;
|
|
75
|
+
readonly oper: Reducer<ConnectionState>;
|
|
76
|
+
readonly cap: Reducer<ConnectionState>;
|
|
77
|
+
readonly authenticate: Reducer<ConnectionState>;
|
|
78
|
+
readonly away: Reducer<ConnectionState>;
|
|
79
|
+
// Channel-authority reducers.
|
|
80
|
+
readonly join: Reducer<ChannelState>;
|
|
81
|
+
readonly part: Reducer<ChannelState>;
|
|
82
|
+
readonly topic: Reducer<ChannelState>;
|
|
83
|
+
readonly names: Reducer<ChannelState>;
|
|
84
|
+
readonly kick: Reducer<ChannelState>;
|
|
85
|
+
readonly invite: Reducer<ChannelState>;
|
|
86
|
+
// Target-dependent reducers.
|
|
87
|
+
readonly privmsgChannel: Reducer<ChannelState>;
|
|
88
|
+
readonly privmsgUser: Reducer<ConnectionState>;
|
|
89
|
+
readonly noticeChannel: Reducer<ChannelState>;
|
|
90
|
+
readonly noticeUser: Reducer<ConnectionState>;
|
|
91
|
+
readonly tagmsgChannel: Reducer<ChannelState>;
|
|
92
|
+
readonly tagmsgUser: Reducer<ConnectionState>;
|
|
93
|
+
readonly channelMode: Reducer<ChannelState>;
|
|
94
|
+
readonly userMode: Reducer<ConnectionState>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolves the effective flood-control config for a deployment.
|
|
99
|
+
*
|
|
100
|
+
* - `serverConfig.floodControl === null` → returns `null` (disabled; the
|
|
101
|
+
* actor skips wrapping entirely).
|
|
102
|
+
* - `undefined` → the documented {@link DEFAULT_FLOOD_CONTROL_CONFIG}.
|
|
103
|
+
* - an object → those thresholds.
|
|
104
|
+
*
|
|
105
|
+
* Control-plane commands (`PING`/`PONG`/`CAP`/`AUTHENTICATE`/`QUIT`) are
|
|
106
|
+
* exempt by default via {@link defaultCommandCost}. A caller-supplied
|
|
107
|
+
* `commandCost` (only possible on a code-constructed config — it cannot be
|
|
108
|
+
* serialized) is preserved as-is.
|
|
109
|
+
*/
|
|
110
|
+
export function resolveFloodControl(serverConfig: ServerConfig): FloodControlConfig | null {
|
|
111
|
+
const fc = serverConfig.floodControl;
|
|
112
|
+
if (fc === null) return null;
|
|
113
|
+
if (fc === undefined) {
|
|
114
|
+
return { ...DEFAULT_FLOOD_CONTROL_CONFIG, commandCost: defaultCommandCost };
|
|
115
|
+
}
|
|
116
|
+
return { ...fc, commandCost: fc.commandCost ?? defaultCommandCost };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Builds the wrapped reducer map for a connection.
|
|
121
|
+
*
|
|
122
|
+
* When flood control is disabled (`floodControl: null`), returns the raw
|
|
123
|
+
* reducers unchanged so existing behaviour is preserved bit-for-bit.
|
|
124
|
+
* Otherwise every reducer is wrapped with {@link wrapWithFloodControl} using
|
|
125
|
+
* the resolved config, so the token-bucket check runs on every dispatched
|
|
126
|
+
* command.
|
|
127
|
+
*/
|
|
128
|
+
export function buildRoutedReducers(serverConfig: ServerConfig): RoutedReducers {
|
|
129
|
+
const floodControl = resolveFloodControl(serverConfig);
|
|
130
|
+
const wrap = <S>(reducer: Reducer<S>): Reducer<S> =>
|
|
131
|
+
floodControl === null ? reducer : wrapWithFloodControl(reducer, floodControl);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
ping: wrap(pingReducer),
|
|
135
|
+
pong: wrap(pongReducer),
|
|
136
|
+
nick: wrap(nickReducer),
|
|
137
|
+
user: wrap(userReducer),
|
|
138
|
+
pass: wrap(passReducer),
|
|
139
|
+
quit: wrap(quitReducer),
|
|
140
|
+
motd: wrap(motdReducer),
|
|
141
|
+
oper: wrap(operReducer),
|
|
142
|
+
cap: wrap(capReducer),
|
|
143
|
+
authenticate: wrap(authenticateReducer),
|
|
144
|
+
away: wrap(awayReducer),
|
|
145
|
+
join: wrap(joinReducer),
|
|
146
|
+
part: wrap(partReducer),
|
|
147
|
+
topic: wrap(topicReducer),
|
|
148
|
+
names: wrap(namesReducer),
|
|
149
|
+
kick: wrap(kickReducer),
|
|
150
|
+
invite: wrap(inviteReducer),
|
|
151
|
+
privmsgChannel: wrap(privmsgChannelReducer),
|
|
152
|
+
privmsgUser: wrap(privmsgUserReducer),
|
|
153
|
+
noticeChannel: wrap(noticeChannelReducer),
|
|
154
|
+
noticeUser: wrap(noticeUserReducer),
|
|
155
|
+
tagmsgChannel: wrap(tagmsgChannelReducer),
|
|
156
|
+
tagmsgUser: wrap(tagmsgUserReducer),
|
|
157
|
+
channelMode: wrap(channelModeReducer),
|
|
158
|
+
userMode: wrap(userModeReducer),
|
|
159
|
+
};
|
|
160
|
+
}
|