@aegis-fluxion/core 0.7.1 → 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/README.md CHANGED
@@ -1,10 +1,8 @@
1
1
  # @aegis-fluxion/core
2
2
 
3
- Low-level E2E-encrypted WebSocket primitives for the `aegis-fluxion` ecosystem.
3
+ Low-level encrypted WebSocket primitives for the `aegis-fluxion` ecosystem.
4
4
 
5
- If you prefer a single user-facing package, use [`aegis-fluxion`](../aegis-fluxion/README.md).
6
-
7
- Version: **0.7.1**
5
+ Version: **0.8.0**
8
6
 
9
7
  ---
10
8
 
@@ -12,16 +10,12 @@ Version: **0.7.1**
12
10
 
13
11
  - Ephemeral ECDH handshake (`prime256v1`)
14
12
  - AES-256-GCM encrypted envelopes
15
- - Encrypted ACK request/response (`Promise` and callback)
16
- - Secure room routing (`join`, `leave`, `leaveAll`, `to(room).emit`)
17
- - Heartbeat and zombie socket cleanup
18
- - Auto-reconnect with fresh re-handshake
19
- - **Binary payload support**: `Buffer`, `Uint8Array`, `Blob`
20
- - **Server middleware pipeline** via `SecureServer.use(...)`
13
+ - ACK request/response (Promise + callback styles)
14
+ - Secure room routing (`join`, `leave`, `leaveAll`, `to(room).emit(...)`)
21
15
  - Middleware phases: `connection`, `incoming`, `outgoing`
22
- - Per-socket middleware metadata available as `SecureServerClient.metadata`
23
- - **Rate limiting & DDoS shield** with per-connection/per-IP controls
24
- - Optional MCP bridge package: `@aegis-fluxion/mcp-adapter`
16
+ - Rate limiting and DDoS controls per connection and IP
17
+ - TLS 1.3-style session resumption with encrypted one-time tickets
18
+ - **Horizontal scaling hooks** via pluggable `SecureServerAdapter`
25
19
 
26
20
  ---
27
21
 
@@ -33,299 +27,208 @@ npm install @aegis-fluxion/core ws
33
27
 
34
28
  ---
35
29
 
36
- ## Middleware in 0.6.0+
37
-
38
- `SecureServer` now supports phase-based middleware for auth, policy enforcement, and payload
39
- normalization.
30
+ ## Session resumption (TLS 1.3-style)
40
31
 
41
- ```ts
42
- import { SecureClient, SecureServer } from "@aegis-fluxion/core";
32
+ `@aegis-fluxion/core@0.8.0` introduces secure resume-first reconnect behavior:
43
33
 
44
- const server = new SecureServer({ host: "127.0.0.1", port: 8080 });
34
+ - Full handshake path uses ephemeral ECDH (`hello` frame).
35
+ - Resume path uses ticket-bound proofs (`resume` / `resume-ack` frames).
36
+ - Successful resumes derive fresh channel keys from ticket secret + client nonce.
37
+ - Servers enforce ticket TTL, bounded cache size, and one-time ticket consumption.
38
+ - Clients automatically fall back to full handshake when resume is rejected.
45
39
 
46
- server.use(async (context, next) => {
47
- if (context.phase === "connection") {
48
- const rawApiKey = context.request.headers["x-api-key"];
49
- const apiKey = Array.isArray(rawApiKey) ? rawApiKey[0] : rawApiKey;
40
+ ### Server configuration
50
41
 
51
- if (apiKey !== "dev-secret") {
52
- throw new Error("Unauthorized");
53
- }
42
+ ```ts
43
+ import { SecureServer } from "@aegis-fluxion/core";
54
44
 
55
- context.metadata.set("role", "editor");
45
+ const server = new SecureServer({
46
+ host: "127.0.0.1",
47
+ port: 8080,
48
+ sessionResumption: {
49
+ enabled: true,
50
+ ticketTtlMs: 60_000,
51
+ maxCachedTickets: 10_000
56
52
  }
57
-
58
- await next();
59
53
  });
54
+ ```
60
55
 
61
- server.use(async (context, next) => {
62
- if (
63
- context.phase === "incoming" &&
64
- context.event === "post:create" &&
65
- typeof context.data === "object" &&
66
- context.data !== null
67
- ) {
68
- const payload = context.data as { title?: string };
69
- context.data = { title: String(payload.title ?? "").trim() };
70
- }
71
-
72
- await next();
56
+ ### Client configuration
73
57
 
74
- if (context.phase === "outgoing" && context.event === "post:create") {
75
- context.data = {
76
- ...(context.data as Record<string, unknown>),
77
- middleware: true
78
- };
79
- }
80
- });
81
-
82
- server.on("post:create", async (payload, client) => {
83
- return {
84
- ok: true,
85
- role: client.metadata.get("role"),
86
- payload
87
- };
88
- });
58
+ ```ts
59
+ import { SecureClient } from "@aegis-fluxion/core";
89
60
 
90
61
  const client = new SecureClient("ws://127.0.0.1:8080", {
91
- wsOptions: {
92
- headers: {
93
- "x-api-key": "dev-secret"
94
- }
62
+ reconnect: true,
63
+ sessionResumption: {
64
+ enabled: true,
65
+ maxAcceptedTicketTtlMs: 60_000
95
66
  }
96
67
  });
97
-
98
- client.on("ready", async () => {
99
- const response = await client.emit("post:create", { title: " Hello " }, { timeoutMs: 1500 });
100
- console.log(response);
101
- });
102
68
  ```
103
69
 
104
- Notes:
70
+ ### Security model
105
71
 
106
- - Throwing in `connection` middleware rejects the socket and closes with code `1008`.
107
- - `metadata` is mutable in middleware (`Map`) and exposed read-only on `SecureServerClient`.
72
+ - Resume proofs are validated with HMAC and constant-time comparison.
73
+ - Resume tickets are encrypted in transit (same channel protections as all payloads).
74
+ - Resume tickets are discarded if expired, policy-invalid, or already consumed.
75
+ - Reserved internal events (e.g., session-ticket transport) cannot be emitted by user code.
108
76
 
109
77
  ---
110
78
 
111
- ## Rate Limiting & DDoS Protection (0.7.1)
79
+ ## SecureServer adapter API (horizontal scaling)
112
80
 
113
- `SecureServer` can enforce burst limits per connection and per source IP before event handlers run.
81
+ ### Core types
114
82
 
115
83
  ```ts
116
- import { SecureServer } from "@aegis-fluxion/core";
117
-
118
- const server = new SecureServer({
119
- host: "127.0.0.1",
120
- port: 8080,
121
- rateLimit: {
122
- enabled: true,
123
- windowMs: 1_000,
124
- maxEventsPerConnection: 120,
125
- maxEventsPerIp: 300,
126
- action: "throttle",
127
- throttleMs: 150,
128
- maxThrottleMs: 2_000,
129
- disconnectAfterViolations: 4,
130
- disconnectCode: 1013,
131
- disconnectReason: "Rate limit exceeded. Please retry later."
132
- }
133
- });
84
+ export interface SecureServerAdapterMessage {
85
+ version: 1;
86
+ originServerId: string;
87
+ scope: "broadcast" | "room";
88
+ event: string;
89
+ data: unknown;
90
+ emittedAt: number;
91
+ room?: string;
92
+ }
93
+
94
+ export interface SecureServerAdapter {
95
+ attach(server: SecureServer): void | Promise<void>;
96
+ publish(message: SecureServerAdapterMessage): void | Promise<void>;
97
+ detach?(server: SecureServer): void | Promise<void>;
98
+ }
134
99
  ```
135
100
 
136
- Behavior summary:
101
+ ### SecureServer hooks
137
102
 
138
- - When limits are exceeded, the server can **throttle** or **disconnect** the peer.
139
- - Throttle mode delays the first over-limit message and drops subsequent flood packets during the throttle window.
140
- - Disconnect mode closes abusive sockets with your configured close code/reason.
141
- - Source IP is resolved from `x-forwarded-for` (first hop) or socket remote address.
103
+ - constructor option: `new SecureServer({ ..., adapter })`
104
+ - runtime binding: `await server.setAdapter(adapter)`
105
+ - inbound relay: `await server.handleAdapterMessage(message)`
106
+ - instance identity: `server.serverId`
142
107
 
143
- ---
144
-
145
- ## MCP Adapter Integration (0.7.0)
146
-
147
- Use `@aegis-fluxion/mcp-adapter` to carry MCP JSON-RPC messages through your encrypted core
148
- transport.
108
+ ### Message normalization helper
149
109
 
150
110
  ```ts
151
- import { SecureClient, SecureServer } from "@aegis-fluxion/core";
152
- import { SecureMCPTransport } from "@aegis-fluxion/mcp-adapter";
111
+ import { normalizeSecureServerAdapterMessage } from "@aegis-fluxion/core";
112
+ ```
153
113
 
154
- const secureServer = new SecureServer({ host: "127.0.0.1", port: 9091 });
114
+ Use it in adapters before delivering inbound Pub/Sub payloads to `SecureServer`.
155
115
 
156
- secureServer.on("connection", async (client) => {
157
- const mcpServerTransport = new SecureMCPTransport({
158
- mode: "server",
159
- server: secureServer,
160
- clientId: client.id
161
- });
116
+ ---
162
117
 
163
- mcpServerTransport.onmessage = async (message) => {
164
- // Forward into your MCP server runtime.
165
- console.log("MCP request on server tunnel", message);
166
- };
118
+ ## Adapter integration example
167
119
 
168
- await mcpServerTransport.start();
169
- });
120
+ ```ts
121
+ import {
122
+ SecureServer,
123
+ type SecureServerAdapter,
124
+ type SecureServerAdapterMessage
125
+ } from "@aegis-fluxion/core";
126
+
127
+ class InMemoryAdapter implements SecureServerAdapter {
128
+ private static readonly peers = new Set<InMemoryAdapter>();
129
+ private server: SecureServer | null = null;
130
+
131
+ async attach(server: SecureServer): Promise<void> {
132
+ this.server = server;
133
+ InMemoryAdapter.peers.add(this);
134
+ }
170
135
 
171
- const secureClient = new SecureClient("ws://127.0.0.1:9091");
136
+ async publish(message: SecureServerAdapterMessage): Promise<void> {
137
+ for (const peer of InMemoryAdapter.peers) {
138
+ if (peer === this || !peer.server) {
139
+ continue;
140
+ }
172
141
 
173
- const mcpClientTransport = new SecureMCPTransport({
174
- mode: "client",
175
- client: secureClient
176
- });
142
+ await peer.server.handleAdapterMessage(message);
143
+ }
144
+ }
177
145
 
178
- await mcpClientTransport.start();
146
+ async detach(server: SecureServer): Promise<void> {
147
+ if (this.server !== server) {
148
+ return;
149
+ }
179
150
 
180
- await mcpClientTransport.send({
181
- jsonrpc: "2.0",
182
- id: 100,
183
- method: "tools/list",
184
- params: {}
151
+ this.server = null;
152
+ InMemoryAdapter.peers.delete(this);
153
+ }
154
+ }
155
+
156
+ const server = new SecureServer({
157
+ host: "127.0.0.1",
158
+ port: 8080,
159
+ adapter: new InMemoryAdapter()
185
160
  });
186
161
  ```
187
162
 
188
163
  ---
189
164
 
190
- ## Binary Data Support
191
-
192
- `@aegis-fluxion/core` supports encrypted binary payload transfer while preserving type fidelity.
193
-
194
- Supported send/receive types:
195
-
196
- - `Buffer`
197
- - `Uint8Array`
198
- - `Blob`
199
-
200
- Binary values can be nested in regular objects and arrays.
201
-
202
- ---
203
-
204
- ## Example: Encrypted Binary Event
165
+ ## Middleware and ACK example
205
166
 
206
167
  ```ts
207
168
  import { SecureClient, SecureServer } from "@aegis-fluxion/core";
208
169
 
209
170
  const server = new SecureServer({ host: "127.0.0.1", port: 8080 });
210
- const client = new SecureClient("ws://127.0.0.1:8080");
211
171
 
212
- server.on("image:chunk", (data, socket) => {
213
- const chunk = data as Buffer;
214
-
215
- if (!Buffer.isBuffer(chunk)) {
216
- throw new Error("Expected Buffer payload.");
172
+ server.use(async (context, next) => {
173
+ if (context.phase === "connection") {
174
+ context.metadata.set("auth.role", "operator");
217
175
  }
218
176
 
219
- socket.emit("image:chunk:ack", chunk);
177
+ await next();
220
178
  });
221
179
 
222
- client.on("ready", () => {
223
- const imageChunk = Buffer.from("89504e470d0a", "hex");
224
- client.emit("image:chunk", imageChunk);
180
+ server.on("jobs:create", async (payload, client) => {
181
+ return {
182
+ ok: true,
183
+ role: client.metadata.get("auth.role"),
184
+ payload
185
+ };
225
186
  });
226
187
 
227
- client.on("image:chunk:ack", (payload) => {
228
- const echoedChunk = payload as Buffer;
229
- console.log("Echoed bytes:", echoedChunk.byteLength);
188
+ const client = new SecureClient("ws://127.0.0.1:8080");
189
+
190
+ client.on("ready", async () => {
191
+ const response = await client.emit("jobs:create", { id: "job-42" }, { timeoutMs: 1200 });
192
+ console.log(response);
230
193
  });
231
194
  ```
232
195
 
233
196
  ---
234
197
 
235
- ## Example: ACK Roundtrip with Mixed Binary Types
198
+ ## Rate limiting and DDoS shield
236
199
 
237
200
  ```ts
238
- server.on("binary:inspect", async (payload) => {
239
- const { file, bytes, blob } = payload as {
240
- file: Buffer;
241
- bytes: Uint8Array;
242
- blob: Blob;
243
- };
244
-
245
- return {
246
- fileBytes: file.byteLength,
247
- bytesBytes: bytes.byteLength,
248
- blobBytes: blob.size
249
- };
250
- });
201
+ import { SecureServer } from "@aegis-fluxion/core";
251
202
 
252
- client.on("ready", async () => {
253
- const result = await client.emit(
254
- "binary:inspect",
255
- {
256
- file: Buffer.from("file-binary"),
257
- bytes: Uint8Array.from([1, 2, 3, 4]),
258
- blob: new Blob([Buffer.from("blob-binary")], {
259
- type: "application/octet-stream"
260
- })
261
- },
262
- { timeoutMs: 1500 }
263
- );
264
-
265
- console.log(result);
203
+ const server = new SecureServer({
204
+ host: "127.0.0.1",
205
+ port: 8080,
206
+ rateLimit: {
207
+ enabled: true,
208
+ windowMs: 1_000,
209
+ maxEventsPerConnection: 120,
210
+ maxEventsPerIp: 300,
211
+ action: "throttle", // or "disconnect"
212
+ throttleMs: 150,
213
+ maxThrottleMs: 2_000,
214
+ disconnectAfterViolations: 4,
215
+ disconnectCode: 1013,
216
+ disconnectReason: "Rate limit exceeded. Please retry later."
217
+ }
266
218
  });
267
219
  ```
268
220
 
269
221
  ---
270
222
 
271
- ## API Snapshot
272
-
273
- ### `SecureServer`
274
-
275
- - `on("connection" | "ready" | "disconnect" | "error", handler)`
276
- - `on("custom:event", (data, client) => unknown | Promise<unknown>)`
277
- - `use((context, next) => void | Promise<void>)`
278
- - `emit(event, data): SecureServer`
279
- - `emitTo(clientId, event, data): boolean`
280
- - `emitTo(clientId, event, data, callback): boolean`
281
- - `emitTo(clientId, event, data, options): Promise<unknown>`
282
- - `emitTo(clientId, event, data, options, callback): boolean`
283
- - `to(room).emit(event, data): SecureServer`
284
- - `close(code?, reason?): void`
285
-
286
- ### `SecureServerClient`
287
-
288
- - `id: string`
289
- - `socket: WebSocket`
290
- - `metadata: ReadonlyMap<string, unknown>`
291
- - `emit(event, data, ...ackArgs): boolean | Promise<unknown>`
292
- - `join(room): boolean`
293
- - `leave(room): boolean`
294
- - `leaveAll(): number`
295
-
296
- ### Middleware Types
297
-
298
- - `SecureServerMiddleware`
299
- - `SecureServerMiddlewareContext`
300
- - `SecureServerConnectionMiddlewareContext`
301
- - `SecureServerMessageMiddlewareContext`
302
- - `SecureServerMiddlewareNext`
303
- - `SecureServerRateLimitOptions`
304
- - `SecureServerRateLimitAction`
305
-
306
- ### `SecureClient`
307
-
308
- - `connect(): void`
309
- - `disconnect(code?, reason?): void`
310
- - `isConnected(): boolean`
311
- - `readyState: number | null`
312
- - `emit(event, data): boolean`
313
- - `emit(event, data, callback): boolean`
314
- - `emit(event, data, options): Promise<unknown>`
315
- - `emit(event, data, options, callback): boolean`
316
- - `on("connect" | "ready" | "disconnect" | "error", handler)`
317
- - `on("custom:event", handler)`
223
+ ## Binary payload support
318
224
 
319
- ---
225
+ Supported encrypted payload types:
320
226
 
321
- ## Security Notes
227
+ - `Buffer`
228
+ - `Uint8Array`
229
+ - `Blob`
322
230
 
323
- - All payloads (including binary) are encrypted end-to-end with AES-256-GCM.
324
- - Authentication tags are verified on every packet (tampered packets are dropped).
325
- - Internal transport events are reserved (`__handshake`, `__rpc:req`, `__rpc:res`).
326
- - Pending ACK requests are rejected on timeout/disconnect.
327
- - Overload traffic can be throttled or disconnected before custom handlers are invoked.
328
- - Middleware-level policy rejection uses WebSocket close code `1008`.
231
+ Binary fields can be nested in standard JSON objects and arrays.
329
232
 
330
233
  ---
331
234