@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 +230 -101
- package/dist/index.cjs +303 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +303 -58
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
# @aegis-fluxion/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Low-level E2E-encrypted WebSocket primitives for the `aegis-fluxion` ecosystem.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
##
|
|
11
|
+
## Highlights
|
|
10
12
|
|
|
11
|
-
- ECDH handshake (`prime256v1`)
|
|
12
|
-
- AES-256-GCM encrypted
|
|
13
|
-
-
|
|
14
|
-
- Secure room routing (`join`, `leave`, `to(room).emit`)
|
|
15
|
-
- Heartbeat
|
|
16
|
-
- Auto-reconnect with
|
|
17
|
-
- **
|
|
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
|
-
##
|
|
35
|
+
## Middleware in 0.6.0+
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
`SecureServer` now supports phase-based middleware for auth, policy enforcement, and payload
|
|
38
|
+
normalization.
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
43
|
+
const server = new SecureServer({ host: "127.0.0.1", port: 8080 });
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
50
|
+
if (apiKey !== "dev-secret") {
|
|
51
|
+
throw new Error("Unauthorized");
|
|
52
|
+
}
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
##
|
|
110
|
+
## MCP Adapter Integration (0.7.0)
|
|
68
111
|
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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
|
|
136
|
+
const secureClient = new SecureClient("ws://127.0.0.1:9091");
|
|
81
137
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
138
|
+
const mcpClientTransport = new SecureMCPTransport({
|
|
139
|
+
mode: "client",
|
|
140
|
+
client: secureClient
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await mcpClientTransport.start();
|
|
88
144
|
|
|
89
|
-
|
|
145
|
+
await mcpClientTransport.send({
|
|
146
|
+
jsonrpc: "2.0",
|
|
147
|
+
id: 100,
|
|
148
|
+
method: "tools/list",
|
|
149
|
+
params: {}
|
|
90
150
|
});
|
|
91
151
|
```
|
|
92
152
|
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
198
|
+
---
|
|
112
199
|
|
|
113
|
-
|
|
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
|
-
|
|
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("
|
|
125
|
-
|
|
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
|
-
|
|
234
|
+
---
|
|
130
235
|
|
|
131
|
-
|
|
236
|
+
## API Snapshot
|
|
132
237
|
|
|
133
|
-
|
|
134
|
-
- Callback form -> callback receives `Error`
|
|
238
|
+
### `SecureServer`
|
|
135
239
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
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
|
|
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
|