@aegis-fluxion/core 0.4.0 → 0.7.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,20 +1,26 @@
1
1
  # @aegis-fluxion/core
2
2
 
3
- Core E2E-encrypted WebSocket primitives for `aegis-fluxion`.
3
+ Low-level E2E-encrypted WebSocket primitives for the `aegis-fluxion` ecosystem.
4
4
 
5
- Version: **0.4.0**
5
+ If you prefer a single user-facing package, use [`aegis-fluxion`](../aegis-fluxion/README.md).
6
+
7
+ Version: **0.7.0**
6
8
 
7
9
  ---
8
10
 
9
- ## Features
11
+ ## Highlights
10
12
 
11
- - ECDH handshake (`prime256v1`) with ephemeral key exchange
12
- - AES-256-GCM encrypted message envelopes
13
- - Server/client lifecycle events (`connect`, `ready`, `disconnect`, `error`)
14
- - Secure room routing (`join`, `leave`, `to(room).emit`)
15
- - Heartbeat-based zombie cleanup
16
- - Auto-reconnect with exponential backoff
17
- - **RPC-style ACK request/response with timeout support**
13
+ - Ephemeral ECDH handshake (`prime256v1`)
14
+ - 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(...)`
21
+ - Middleware phases: `connection`, `incoming`, `outgoing`
22
+ - Per-socket middleware metadata available as `SecureServerClient.metadata`
23
+ - Optional MCP bridge package: `@aegis-fluxion/mcp-adapter`
18
24
 
19
25
  ---
20
26
 
@@ -26,135 +32,268 @@ npm install @aegis-fluxion/core ws
26
32
 
27
33
  ---
28
34
 
29
- ## API at a Glance
35
+ ## Middleware in 0.6.0+
30
36
 
31
- ### `SecureServer`
37
+ `SecureServer` now supports phase-based middleware for auth, policy enforcement, and payload
38
+ normalization.
32
39
 
33
- - `on("connection" | "ready" | "disconnect" | "error", handler)`
34
- - `on("custom:event", (data, client) => unknown | Promise<unknown>)`
35
- - `emit(event, data): SecureServer`
36
- - `emitTo(clientId, event, data): boolean`
37
- - `emitTo(clientId, event, data, callback): boolean`
38
- - `emitTo(clientId, event, data, options): Promise<unknown>`
39
- - `emitTo(clientId, event, data, options, callback): boolean`
40
- - `to(room).emit(event, data): SecureServer`
41
- - `close(code?, reason?): void`
40
+ ```ts
41
+ import { SecureClient, SecureServer } from "@aegis-fluxion/core";
42
42
 
43
- ### `SecureServerClient`
43
+ const server = new SecureServer({ host: "127.0.0.1", port: 8080 });
44
44
 
45
- - `id: string`
46
- - `socket: WebSocket`
47
- - `join(room): boolean`
48
- - `leave(room): boolean`
49
- - `leaveAll(): number`
50
- - `emit(event, data, ...ackArgs): boolean | Promise<unknown>`
45
+ server.use(async (context, next) => {
46
+ if (context.phase === "connection") {
47
+ const rawApiKey = context.request.headers["x-api-key"];
48
+ const apiKey = Array.isArray(rawApiKey) ? rawApiKey[0] : rawApiKey;
51
49
 
52
- ### `SecureClient`
50
+ if (apiKey !== "dev-secret") {
51
+ throw new Error("Unauthorized");
52
+ }
53
53
 
54
- - `connect(): void`
55
- - `disconnect(code?, reason?): void`
56
- - `isConnected(): boolean`
57
- - `readyState: number | null`
58
- - `emit(event, data): boolean`
59
- - `emit(event, data, callback): boolean`
60
- - `emit(event, data, options): Promise<unknown>`
61
- - `emit(event, data, options, callback): boolean`
62
- - `on("connect" | "ready" | "disconnect" | "error", handler)`
63
- - `on("custom:event", handler)`
54
+ context.metadata.set("role", "editor");
55
+ }
56
+
57
+ await next();
58
+ });
59
+
60
+ server.use(async (context, next) => {
61
+ if (
62
+ context.phase === "incoming" &&
63
+ context.event === "post:create" &&
64
+ typeof context.data === "object" &&
65
+ context.data !== null
66
+ ) {
67
+ const payload = context.data as { title?: string };
68
+ context.data = { title: String(payload.title ?? "").trim() };
69
+ }
70
+
71
+ await next();
72
+
73
+ if (context.phase === "outgoing" && context.event === "post:create") {
74
+ context.data = {
75
+ ...(context.data as Record<string, unknown>),
76
+ middleware: true
77
+ };
78
+ }
79
+ });
80
+
81
+ server.on("post:create", async (payload, client) => {
82
+ return {
83
+ ok: true,
84
+ role: client.metadata.get("role"),
85
+ payload
86
+ };
87
+ });
88
+
89
+ const client = new SecureClient("ws://127.0.0.1:8080", {
90
+ wsOptions: {
91
+ headers: {
92
+ "x-api-key": "dev-secret"
93
+ }
94
+ }
95
+ });
96
+
97
+ client.on("ready", async () => {
98
+ const response = await client.emit("post:create", { title: " Hello " }, { timeoutMs: 1500 });
99
+ console.log(response);
100
+ });
101
+ ```
102
+
103
+ Notes:
104
+
105
+ - Throwing in `connection` middleware rejects the socket and closes with code `1008`.
106
+ - `metadata` is mutable in middleware (`Map`) and exposed read-only on `SecureServerClient`.
64
107
 
65
108
  ---
66
109
 
67
- ## ACK (Request-Response) Usage
110
+ ## MCP Adapter Integration (0.7.0)
68
111
 
69
- ### 1) Client -> Server (Promise ACK)
112
+ Use `@aegis-fluxion/mcp-adapter` to carry MCP JSON-RPC messages through your encrypted core
113
+ transport.
70
114
 
71
115
  ```ts
72
116
  import { SecureClient, SecureServer } from "@aegis-fluxion/core";
117
+ import { SecureMCPTransport } from "@aegis-fluxion/mcp-adapter";
118
+
119
+ const secureServer = new SecureServer({ host: "127.0.0.1", port: 9091 });
73
120
 
74
- const server = new SecureServer({ port: 8080, host: "127.0.0.1" });
121
+ secureServer.on("connection", async (client) => {
122
+ const mcpServerTransport = new SecureMCPTransport({
123
+ mode: "server",
124
+ server: secureServer,
125
+ clientId: client.id
126
+ });
75
127
 
76
- server.on("math:add", ({ a, b }) => {
77
- return { total: Number(a) + Number(b) };
128
+ mcpServerTransport.onmessage = async (message) => {
129
+ // Forward into your MCP server runtime.
130
+ console.log("MCP request on server tunnel", message);
131
+ };
132
+
133
+ await mcpServerTransport.start();
78
134
  });
79
135
 
80
- const client = new SecureClient("ws://127.0.0.1:8080");
136
+ const secureClient = new SecureClient("ws://127.0.0.1:9091");
81
137
 
82
- client.on("ready", async () => {
83
- const response = await client.emit(
84
- "math:add",
85
- { a: 2, b: 3 },
86
- { timeoutMs: 1000 }
87
- );
138
+ const mcpClientTransport = new SecureMCPTransport({
139
+ mode: "client",
140
+ client: secureClient
141
+ });
142
+
143
+ await mcpClientTransport.start();
88
144
 
89
- console.log(response); // { total: 5 }
145
+ await mcpClientTransport.send({
146
+ jsonrpc: "2.0",
147
+ id: 100,
148
+ method: "tools/list",
149
+ params: {}
90
150
  });
91
151
  ```
92
152
 
93
- ### 2) Client -> Server (Callback ACK)
153
+ ---
154
+
155
+ ## Binary Data Support
156
+
157
+ `@aegis-fluxion/core` supports encrypted binary payload transfer while preserving type fidelity.
158
+
159
+ Supported send/receive types:
160
+
161
+ - `Buffer`
162
+ - `Uint8Array`
163
+ - `Blob`
164
+
165
+ Binary values can be nested in regular objects and arrays.
166
+
167
+ ---
168
+
169
+ ## Example: Encrypted Binary Event
94
170
 
95
171
  ```ts
96
- client.emit(
97
- "math:add",
98
- { a: 4, b: 6 },
99
- { timeoutMs: 1000 },
100
- (error, response) => {
101
- if (error) {
102
- console.error("ACK error:", error.message);
103
- return;
104
- }
172
+ import { SecureClient, SecureServer } from "@aegis-fluxion/core";
105
173
 
106
- console.log(response); // { total: 10 }
174
+ const server = new SecureServer({ host: "127.0.0.1", port: 8080 });
175
+ const client = new SecureClient("ws://127.0.0.1:8080");
176
+
177
+ server.on("image:chunk", (data, socket) => {
178
+ const chunk = data as Buffer;
179
+
180
+ if (!Buffer.isBuffer(chunk)) {
181
+ throw new Error("Expected Buffer payload.");
107
182
  }
108
- );
183
+
184
+ socket.emit("image:chunk:ack", chunk);
185
+ });
186
+
187
+ client.on("ready", () => {
188
+ const imageChunk = Buffer.from("89504e470d0a", "hex");
189
+ client.emit("image:chunk", imageChunk);
190
+ });
191
+
192
+ client.on("image:chunk:ack", (payload) => {
193
+ const echoedChunk = payload as Buffer;
194
+ console.log("Echoed bytes:", echoedChunk.byteLength);
195
+ });
109
196
  ```
110
197
 
111
- ### 3) Server -> Client (Promise ACK)
198
+ ---
112
199
 
113
- ```ts
114
- server.on("ready", async (clientSocket) => {
115
- const response = await clientSocket.emit(
116
- "agent:health",
117
- { verbose: true },
118
- { timeoutMs: 1200 }
119
- );
200
+ ## Example: ACK Roundtrip with Mixed Binary Types
120
201
 
121
- console.log(response);
202
+ ```ts
203
+ server.on("binary:inspect", async (payload) => {
204
+ const { file, bytes, blob } = payload as {
205
+ file: Buffer;
206
+ bytes: Uint8Array;
207
+ blob: Blob;
208
+ };
209
+
210
+ return {
211
+ fileBytes: file.byteLength,
212
+ bytesBytes: bytes.byteLength,
213
+ blobBytes: blob.size
214
+ };
122
215
  });
123
216
 
124
- client.on("agent:health", () => {
125
- return { ok: true, uptime: process.uptime() };
217
+ client.on("ready", async () => {
218
+ const result = await client.emit(
219
+ "binary:inspect",
220
+ {
221
+ file: Buffer.from("file-binary"),
222
+ bytes: Uint8Array.from([1, 2, 3, 4]),
223
+ blob: new Blob([Buffer.from("blob-binary")], {
224
+ type: "application/octet-stream"
225
+ })
226
+ },
227
+ { timeoutMs: 1500 }
228
+ );
229
+
230
+ console.log(result);
126
231
  });
127
232
  ```
128
233
 
129
- ### 4) ACK Timeout Behavior
234
+ ---
130
235
 
131
- When no response arrives before `timeoutMs`, ACK request fails:
236
+ ## API Snapshot
132
237
 
133
- - Promise form -> rejects with timeout error
134
- - Callback form -> callback receives `Error`
238
+ ### `SecureServer`
135
239
 
136
- ```ts
137
- try {
138
- await client.emit("never:respond", { ping: true }, { timeoutMs: 300 });
139
- } catch (error) {
140
- console.error((error as Error).message);
141
- // ACK response timed out after 300ms for event "never:respond".
142
- }
143
- ```
240
+ - `on("connection" | "ready" | "disconnect" | "error", handler)`
241
+ - `on("custom:event", (data, client) => unknown | Promise<unknown>)`
242
+ - `use((context, next) => void | Promise<void>)`
243
+ - `emit(event, data): SecureServer`
244
+ - `emitTo(clientId, event, data): boolean`
245
+ - `emitTo(clientId, event, data, callback): boolean`
246
+ - `emitTo(clientId, event, data, options): Promise<unknown>`
247
+ - `emitTo(clientId, event, data, options, callback): boolean`
248
+ - `to(room).emit(event, data): SecureServer`
249
+ - `close(code?, reason?): void`
250
+
251
+ ### `SecureServerClient`
252
+
253
+ - `id: string`
254
+ - `socket: WebSocket`
255
+ - `metadata: ReadonlyMap<string, unknown>`
256
+ - `emit(event, data, ...ackArgs): boolean | Promise<unknown>`
257
+ - `join(room): boolean`
258
+ - `leave(room): boolean`
259
+ - `leaveAll(): number`
260
+
261
+ ### Middleware Types
262
+
263
+ - `SecureServerMiddleware`
264
+ - `SecureServerMiddlewareContext`
265
+ - `SecureServerConnectionMiddlewareContext`
266
+ - `SecureServerMessageMiddlewareContext`
267
+ - `SecureServerMiddlewareNext`
268
+
269
+ ### `SecureClient`
270
+
271
+ - `connect(): void`
272
+ - `disconnect(code?, reason?): void`
273
+ - `isConnected(): boolean`
274
+ - `readyState: number | null`
275
+ - `emit(event, data): boolean`
276
+ - `emit(event, data, callback): boolean`
277
+ - `emit(event, data, options): Promise<unknown>`
278
+ - `emit(event, data, options, callback): boolean`
279
+ - `on("connect" | "ready" | "disconnect" | "error", handler)`
280
+ - `on("custom:event", handler)`
144
281
 
145
282
  ---
146
283
 
147
284
  ## Security Notes
148
285
 
149
- - ACK request and ACK response frames are encrypted with the same AES-GCM tunnel as normal events.
150
- - Internal handshake/RPC transport events are reserved and cannot be emitted manually.
151
- - On disconnect/heartbeat timeout, pending ACK promises are rejected and memory state is cleaned.
286
+ - All payloads (including binary) are encrypted end-to-end with AES-256-GCM.
287
+ - Authentication tags are verified on every packet (tampered packets are dropped).
288
+ - Internal transport events are reserved (`__handshake`, `__rpc:req`, `__rpc:res`).
289
+ - Pending ACK requests are rejected on timeout/disconnect.
290
+ - Middleware-level policy rejection uses WebSocket close code `1008`.
152
291
 
153
292
  ---
154
293
 
155
294
  ## Development
156
295
 
157
- From monorepo root:
296
+ From repository root:
158
297
 
159
298
  ```bash
160
299
  npm run typecheck -w @aegis-fluxion/core
@@ -164,16 +303,6 @@ npm run build -w @aegis-fluxion/core
164
303
 
165
304
  ---
166
305
 
167
- ## Publish
168
-
169
- From monorepo root:
170
-
171
- ```bash
172
- npm publish -w @aegis-fluxion/core --access public
173
- ```
174
-
175
- ---
176
-
177
306
  ## License
178
307
 
179
308
  MIT