@aegis-fluxion/core 0.4.0 → 0.5.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 +100 -106
- package/dist/index.cjs +172 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +172 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
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.5.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`
|
|
18
20
|
|
|
19
21
|
---
|
|
20
22
|
|
|
@@ -26,7 +28,88 @@ npm install @aegis-fluxion/core ws
|
|
|
26
28
|
|
|
27
29
|
---
|
|
28
30
|
|
|
29
|
-
##
|
|
31
|
+
## Binary Data Support
|
|
32
|
+
|
|
33
|
+
`@aegis-fluxion/core` supports encrypted binary payload transfer while preserving type fidelity.
|
|
34
|
+
|
|
35
|
+
Supported send/receive types:
|
|
36
|
+
|
|
37
|
+
- `Buffer`
|
|
38
|
+
- `Uint8Array`
|
|
39
|
+
- `Blob`
|
|
40
|
+
|
|
41
|
+
Binary values can be nested in regular objects and arrays.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Example: Encrypted Binary Event
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { SecureClient, SecureServer } from "@aegis-fluxion/core";
|
|
49
|
+
|
|
50
|
+
const server = new SecureServer({ host: "127.0.0.1", port: 8080 });
|
|
51
|
+
const client = new SecureClient("ws://127.0.0.1:8080");
|
|
52
|
+
|
|
53
|
+
server.on("image:chunk", (data, socket) => {
|
|
54
|
+
const chunk = data as Buffer;
|
|
55
|
+
|
|
56
|
+
if (!Buffer.isBuffer(chunk)) {
|
|
57
|
+
throw new Error("Expected Buffer payload.");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
socket.emit("image:chunk:ack", chunk);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
client.on("ready", () => {
|
|
64
|
+
const imageChunk = Buffer.from("89504e470d0a", "hex");
|
|
65
|
+
client.emit("image:chunk", imageChunk);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
client.on("image:chunk:ack", (payload) => {
|
|
69
|
+
const echoedChunk = payload as Buffer;
|
|
70
|
+
console.log("Echoed bytes:", echoedChunk.byteLength);
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Example: ACK Roundtrip with Mixed Binary Types
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
server.on("binary:inspect", async (payload) => {
|
|
80
|
+
const { file, bytes, blob } = payload as {
|
|
81
|
+
file: Buffer;
|
|
82
|
+
bytes: Uint8Array;
|
|
83
|
+
blob: Blob;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
fileBytes: file.byteLength,
|
|
88
|
+
bytesBytes: bytes.byteLength,
|
|
89
|
+
blobBytes: blob.size
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
client.on("ready", async () => {
|
|
94
|
+
const result = await client.emit(
|
|
95
|
+
"binary:inspect",
|
|
96
|
+
{
|
|
97
|
+
file: Buffer.from("file-binary"),
|
|
98
|
+
bytes: Uint8Array.from([1, 2, 3, 4]),
|
|
99
|
+
blob: new Blob([Buffer.from("blob-binary")], {
|
|
100
|
+
type: "application/octet-stream"
|
|
101
|
+
})
|
|
102
|
+
},
|
|
103
|
+
{ timeoutMs: 1500 }
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
console.log(result);
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## API Snapshot
|
|
30
113
|
|
|
31
114
|
### `SecureServer`
|
|
32
115
|
|
|
@@ -44,10 +127,10 @@ npm install @aegis-fluxion/core ws
|
|
|
44
127
|
|
|
45
128
|
- `id: string`
|
|
46
129
|
- `socket: WebSocket`
|
|
130
|
+
- `emit(event, data, ...ackArgs): boolean | Promise<unknown>`
|
|
47
131
|
- `join(room): boolean`
|
|
48
132
|
- `leave(room): boolean`
|
|
49
133
|
- `leaveAll(): number`
|
|
50
|
-
- `emit(event, data, ...ackArgs): boolean | Promise<unknown>`
|
|
51
134
|
|
|
52
135
|
### `SecureClient`
|
|
53
136
|
|
|
@@ -64,97 +147,18 @@ npm install @aegis-fluxion/core ws
|
|
|
64
147
|
|
|
65
148
|
---
|
|
66
149
|
|
|
67
|
-
## ACK (Request-Response) Usage
|
|
68
|
-
|
|
69
|
-
### 1) Client -> Server (Promise ACK)
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
import { SecureClient, SecureServer } from "@aegis-fluxion/core";
|
|
73
|
-
|
|
74
|
-
const server = new SecureServer({ port: 8080, host: "127.0.0.1" });
|
|
75
|
-
|
|
76
|
-
server.on("math:add", ({ a, b }) => {
|
|
77
|
-
return { total: Number(a) + Number(b) };
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const client = new SecureClient("ws://127.0.0.1:8080");
|
|
81
|
-
|
|
82
|
-
client.on("ready", async () => {
|
|
83
|
-
const response = await client.emit(
|
|
84
|
-
"math:add",
|
|
85
|
-
{ a: 2, b: 3 },
|
|
86
|
-
{ timeoutMs: 1000 }
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
console.log(response); // { total: 5 }
|
|
90
|
-
});
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### 2) Client -> Server (Callback ACK)
|
|
94
|
-
|
|
95
|
-
```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
|
-
}
|
|
105
|
-
|
|
106
|
-
console.log(response); // { total: 10 }
|
|
107
|
-
}
|
|
108
|
-
);
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### 3) Server -> Client (Promise ACK)
|
|
112
|
-
|
|
113
|
-
```ts
|
|
114
|
-
server.on("ready", async (clientSocket) => {
|
|
115
|
-
const response = await clientSocket.emit(
|
|
116
|
-
"agent:health",
|
|
117
|
-
{ verbose: true },
|
|
118
|
-
{ timeoutMs: 1200 }
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
console.log(response);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
client.on("agent:health", () => {
|
|
125
|
-
return { ok: true, uptime: process.uptime() };
|
|
126
|
-
});
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### 4) ACK Timeout Behavior
|
|
130
|
-
|
|
131
|
-
When no response arrives before `timeoutMs`, ACK request fails:
|
|
132
|
-
|
|
133
|
-
- Promise form -> rejects with timeout error
|
|
134
|
-
- Callback form -> callback receives `Error`
|
|
135
|
-
|
|
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
|
-
```
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
150
|
## Security Notes
|
|
148
151
|
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
+
- All payloads (including binary) are encrypted end-to-end with AES-256-GCM.
|
|
153
|
+
- Authentication tags are verified on every packet (tampered packets are dropped).
|
|
154
|
+
- Internal transport events are reserved (`__handshake`, `__rpc:req`, `__rpc:res`).
|
|
155
|
+
- Pending ACK requests are rejected on timeout/disconnect.
|
|
152
156
|
|
|
153
157
|
---
|
|
154
158
|
|
|
155
159
|
## Development
|
|
156
160
|
|
|
157
|
-
From
|
|
161
|
+
From repository root:
|
|
158
162
|
|
|
159
163
|
```bash
|
|
160
164
|
npm run typecheck -w @aegis-fluxion/core
|
|
@@ -164,16 +168,6 @@ npm run build -w @aegis-fluxion/core
|
|
|
164
168
|
|
|
165
169
|
---
|
|
166
170
|
|
|
167
|
-
## Publish
|
|
168
|
-
|
|
169
|
-
From monorepo root:
|
|
170
|
-
|
|
171
|
-
```bash
|
|
172
|
-
npm publish -w @aegis-fluxion/core --access public
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
---
|
|
176
|
-
|
|
177
171
|
## License
|
|
178
172
|
|
|
179
173
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,8 @@ var GCM_AUTH_TAG_LENGTH = 16;
|
|
|
21
21
|
var ENCRYPTION_KEY_LENGTH = 32;
|
|
22
22
|
var ENCRYPTED_PACKET_VERSION = 1;
|
|
23
23
|
var ENCRYPTED_PACKET_PREFIX_LENGTH = 1 + GCM_IV_LENGTH + GCM_AUTH_TAG_LENGTH;
|
|
24
|
+
var BINARY_PAYLOAD_MARKER = "__afxBinaryPayload";
|
|
25
|
+
var BINARY_PAYLOAD_VERSION = 1;
|
|
24
26
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 15e3;
|
|
25
27
|
var DEFAULT_HEARTBEAT_TIMEOUT_MS = 15e3;
|
|
26
28
|
var DEFAULT_RECONNECT_INITIAL_DELAY_MS = 250;
|
|
@@ -61,7 +63,103 @@ function rawDataToBuffer(rawData) {
|
|
|
61
63
|
}
|
|
62
64
|
return Buffer.from(rawData);
|
|
63
65
|
}
|
|
64
|
-
function
|
|
66
|
+
function isBlobValue(value) {
|
|
67
|
+
return typeof Blob !== "undefined" && value instanceof Blob;
|
|
68
|
+
}
|
|
69
|
+
function isPlainObject(value) {
|
|
70
|
+
if (typeof value !== "object" || value === null) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
const prototype = Object.getPrototypeOf(value);
|
|
74
|
+
return prototype === Object.prototype || prototype === null;
|
|
75
|
+
}
|
|
76
|
+
function encodeBinaryPayload(kind, payloadBuffer, mimeType) {
|
|
77
|
+
const encodedPayload = {
|
|
78
|
+
[BINARY_PAYLOAD_MARKER]: BINARY_PAYLOAD_VERSION,
|
|
79
|
+
kind,
|
|
80
|
+
base64: payloadBuffer.toString("base64")
|
|
81
|
+
};
|
|
82
|
+
if (mimeType !== void 0 && mimeType.length > 0) {
|
|
83
|
+
encodedPayload.mimeType = mimeType;
|
|
84
|
+
}
|
|
85
|
+
return encodedPayload;
|
|
86
|
+
}
|
|
87
|
+
async function encodeEnvelopeData(value) {
|
|
88
|
+
if (Buffer.isBuffer(value)) {
|
|
89
|
+
return encodeBinaryPayload("buffer", value);
|
|
90
|
+
}
|
|
91
|
+
if (value instanceof Uint8Array) {
|
|
92
|
+
const typedArrayBuffer = Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
93
|
+
return encodeBinaryPayload("uint8array", typedArrayBuffer);
|
|
94
|
+
}
|
|
95
|
+
if (isBlobValue(value)) {
|
|
96
|
+
const blobBuffer = Buffer.from(await value.arrayBuffer());
|
|
97
|
+
return encodeBinaryPayload("blob", blobBuffer, value.type);
|
|
98
|
+
}
|
|
99
|
+
if (Array.isArray(value)) {
|
|
100
|
+
return Promise.all(value.map((item) => encodeEnvelopeData(item)));
|
|
101
|
+
}
|
|
102
|
+
if (isPlainObject(value)) {
|
|
103
|
+
const encodedEntries = await Promise.all(
|
|
104
|
+
Object.entries(value).map(async ([key, entryValue]) => {
|
|
105
|
+
return [key, await encodeEnvelopeData(entryValue)];
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
return Object.fromEntries(encodedEntries);
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
function isEncodedBinaryPayload(value) {
|
|
113
|
+
if (!isPlainObject(value)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
if (value[BINARY_PAYLOAD_MARKER] !== BINARY_PAYLOAD_VERSION) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (value.kind !== "buffer" && value.kind !== "uint8array" && value.kind !== "blob") {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
if (typeof value.base64 !== "string") {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (value.mimeType !== void 0 && typeof value.mimeType !== "string") {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
function decodeEnvelopeData(value) {
|
|
131
|
+
if (Array.isArray(value)) {
|
|
132
|
+
return value.map((item) => decodeEnvelopeData(item));
|
|
133
|
+
}
|
|
134
|
+
if (isEncodedBinaryPayload(value)) {
|
|
135
|
+
const binaryBuffer = Buffer.from(value.base64, "base64");
|
|
136
|
+
if (value.kind === "buffer") {
|
|
137
|
+
return binaryBuffer;
|
|
138
|
+
}
|
|
139
|
+
if (value.kind === "uint8array") {
|
|
140
|
+
return Uint8Array.from(binaryBuffer);
|
|
141
|
+
}
|
|
142
|
+
if (typeof Blob === "undefined") {
|
|
143
|
+
return binaryBuffer;
|
|
144
|
+
}
|
|
145
|
+
return new Blob([binaryBuffer], {
|
|
146
|
+
type: value.mimeType ?? ""
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (isPlainObject(value)) {
|
|
150
|
+
const decodedEntries = Object.entries(value).map(([key, entryValue]) => {
|
|
151
|
+
return [key, decodeEnvelopeData(entryValue)];
|
|
152
|
+
});
|
|
153
|
+
return Object.fromEntries(decodedEntries);
|
|
154
|
+
}
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
async function serializeEnvelope(event, data) {
|
|
158
|
+
const encodedData = await encodeEnvelopeData(data);
|
|
159
|
+
const envelope = { event, data: encodedData };
|
|
160
|
+
return JSON.stringify(envelope);
|
|
161
|
+
}
|
|
162
|
+
function serializePlainEnvelope(event, data) {
|
|
65
163
|
const envelope = { event, data };
|
|
66
164
|
return JSON.stringify(envelope);
|
|
67
165
|
}
|
|
@@ -73,7 +171,7 @@ function parseEnvelope(rawData) {
|
|
|
73
171
|
}
|
|
74
172
|
return {
|
|
75
173
|
event: parsed.event,
|
|
76
|
-
data: parsed.data
|
|
174
|
+
data: decodeEnvelopeData(parsed.data)
|
|
77
175
|
};
|
|
78
176
|
}
|
|
79
177
|
function parseEnvelopeFromText(decodedPayload) {
|
|
@@ -83,7 +181,7 @@ function parseEnvelopeFromText(decodedPayload) {
|
|
|
83
181
|
}
|
|
84
182
|
return {
|
|
85
183
|
event: parsed.event,
|
|
86
|
-
data: parsed.data
|
|
184
|
+
data: decodeEnvelopeData(parsed.data)
|
|
87
185
|
};
|
|
88
186
|
}
|
|
89
187
|
function decodeCloseReason(reason) {
|
|
@@ -361,7 +459,9 @@ var SecureServer = class {
|
|
|
361
459
|
}
|
|
362
460
|
const envelope = { event, data };
|
|
363
461
|
for (const client of this.clientsById.values()) {
|
|
364
|
-
this.sendOrQueuePayload(client.socket, envelope)
|
|
462
|
+
void this.sendOrQueuePayload(client.socket, envelope).catch(() => {
|
|
463
|
+
return void 0;
|
|
464
|
+
});
|
|
365
465
|
}
|
|
366
466
|
} catch (error) {
|
|
367
467
|
this.notifyError(normalizeToError(error, "Failed to emit server event."));
|
|
@@ -379,7 +479,9 @@ var SecureServer = class {
|
|
|
379
479
|
throw new Error(`Client with id ${clientId} was not found.`);
|
|
380
480
|
}
|
|
381
481
|
if (!ackArgs.expectsAck) {
|
|
382
|
-
this.sendOrQueuePayload(client.socket, { event, data })
|
|
482
|
+
void this.sendOrQueuePayload(client.socket, { event, data }).catch(() => {
|
|
483
|
+
return void 0;
|
|
484
|
+
});
|
|
383
485
|
return true;
|
|
384
486
|
}
|
|
385
487
|
const ackPromise = this.sendRpcRequest(
|
|
@@ -692,22 +794,24 @@ var SecureServer = class {
|
|
|
692
794
|
this.notifyError(normalizeToError(error, "Failed to send server payload."));
|
|
693
795
|
}
|
|
694
796
|
}
|
|
695
|
-
sendEncryptedEnvelope(socket, envelope) {
|
|
797
|
+
async sendEncryptedEnvelope(socket, envelope) {
|
|
798
|
+
if (socket.readyState !== WebSocket__default.default.OPEN) {
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
const encryptionKey = this.encryptionKeyBySocket.get(socket);
|
|
802
|
+
if (!encryptionKey) {
|
|
803
|
+
const missingKeyError = new Error("Missing encryption key for connected socket.");
|
|
804
|
+
this.notifyError(missingKeyError);
|
|
805
|
+
throw missingKeyError;
|
|
806
|
+
}
|
|
696
807
|
try {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
}
|
|
700
|
-
const encryptionKey = this.encryptionKeyBySocket.get(socket);
|
|
701
|
-
if (!encryptionKey) {
|
|
702
|
-
throw new Error("Missing encryption key for connected socket.");
|
|
703
|
-
}
|
|
704
|
-
const encryptedPayload = encryptSerializedEnvelope(
|
|
705
|
-
serializeEnvelope(envelope.event, envelope.data),
|
|
706
|
-
encryptionKey
|
|
707
|
-
);
|
|
808
|
+
const serializedEnvelope = await serializeEnvelope(envelope.event, envelope.data);
|
|
809
|
+
const encryptedPayload = encryptSerializedEnvelope(serializedEnvelope, encryptionKey);
|
|
708
810
|
socket.send(encryptedPayload);
|
|
709
811
|
} catch (error) {
|
|
710
|
-
|
|
812
|
+
const normalizedError = normalizeToError(error, "Failed to send encrypted server payload.");
|
|
813
|
+
this.notifyError(normalizedError);
|
|
814
|
+
throw normalizedError;
|
|
711
815
|
}
|
|
712
816
|
}
|
|
713
817
|
sendRpcRequest(socket, event, data, timeoutMs) {
|
|
@@ -728,13 +832,19 @@ var SecureServer = class {
|
|
|
728
832
|
reject,
|
|
729
833
|
timeoutHandle
|
|
730
834
|
});
|
|
731
|
-
this.sendOrQueuePayload(socket, {
|
|
835
|
+
void this.sendOrQueuePayload(socket, {
|
|
732
836
|
event: INTERNAL_RPC_REQUEST_EVENT,
|
|
733
837
|
data: {
|
|
734
838
|
id: requestId,
|
|
735
839
|
event,
|
|
736
840
|
data
|
|
737
841
|
}
|
|
842
|
+
}).catch((error) => {
|
|
843
|
+
clearTimeout(timeoutHandle);
|
|
844
|
+
pendingRequests.delete(requestId);
|
|
845
|
+
reject(
|
|
846
|
+
normalizeToError(error, `Failed to dispatch ACK request for event "${event}".`)
|
|
847
|
+
);
|
|
738
848
|
});
|
|
739
849
|
});
|
|
740
850
|
}
|
|
@@ -776,7 +886,7 @@ var SecureServer = class {
|
|
|
776
886
|
rpcRequestPayload.data,
|
|
777
887
|
client
|
|
778
888
|
);
|
|
779
|
-
this.sendEncryptedEnvelope(client.socket, {
|
|
889
|
+
await this.sendEncryptedEnvelope(client.socket, {
|
|
780
890
|
event: INTERNAL_RPC_RESPONSE_EVENT,
|
|
781
891
|
data: {
|
|
782
892
|
id: rpcRequestPayload.id,
|
|
@@ -786,7 +896,7 @@ var SecureServer = class {
|
|
|
786
896
|
});
|
|
787
897
|
} catch (error) {
|
|
788
898
|
const normalizedError = normalizeToError(error, "Server ACK request handler failed.");
|
|
789
|
-
this.sendEncryptedEnvelope(client.socket, {
|
|
899
|
+
await this.sendEncryptedEnvelope(client.socket, {
|
|
790
900
|
event: INTERNAL_RPC_RESPONSE_EVENT,
|
|
791
901
|
data: {
|
|
792
902
|
id: rpcRequestPayload.id,
|
|
@@ -860,7 +970,7 @@ var SecureServer = class {
|
|
|
860
970
|
sendInternalHandshake(socket, localPublicKey) {
|
|
861
971
|
this.sendRaw(
|
|
862
972
|
socket,
|
|
863
|
-
|
|
973
|
+
serializePlainEnvelope(INTERNAL_HANDSHAKE_EVENT, {
|
|
864
974
|
publicKey: localPublicKey
|
|
865
975
|
})
|
|
866
976
|
);
|
|
@@ -893,23 +1003,23 @@ var SecureServer = class {
|
|
|
893
1003
|
sendOrQueuePayload(socket, envelope) {
|
|
894
1004
|
if (!this.isClientHandshakeReady(socket)) {
|
|
895
1005
|
this.queuePayload(socket, envelope);
|
|
896
|
-
return;
|
|
1006
|
+
return Promise.resolve();
|
|
897
1007
|
}
|
|
898
|
-
this.sendEncryptedEnvelope(socket, envelope);
|
|
1008
|
+
return this.sendEncryptedEnvelope(socket, envelope);
|
|
899
1009
|
}
|
|
900
1010
|
queuePayload(socket, envelope) {
|
|
901
1011
|
const pendingPayloads = this.pendingPayloadsBySocket.get(socket) ?? [];
|
|
902
1012
|
pendingPayloads.push(envelope);
|
|
903
1013
|
this.pendingPayloadsBySocket.set(socket, pendingPayloads);
|
|
904
1014
|
}
|
|
905
|
-
flushQueuedPayloads(socket) {
|
|
1015
|
+
async flushQueuedPayloads(socket) {
|
|
906
1016
|
const pendingPayloads = this.pendingPayloadsBySocket.get(socket);
|
|
907
1017
|
if (!pendingPayloads || pendingPayloads.length === 0) {
|
|
908
1018
|
return;
|
|
909
1019
|
}
|
|
910
1020
|
this.pendingPayloadsBySocket.delete(socket);
|
|
911
1021
|
for (const envelope of pendingPayloads) {
|
|
912
|
-
this.sendEncryptedEnvelope(socket, envelope);
|
|
1022
|
+
await this.sendEncryptedEnvelope(socket, envelope);
|
|
913
1023
|
}
|
|
914
1024
|
}
|
|
915
1025
|
createSecureServerClient(clientId, socket, request) {
|
|
@@ -1019,7 +1129,9 @@ var SecureServer = class {
|
|
|
1019
1129
|
if (!client) {
|
|
1020
1130
|
continue;
|
|
1021
1131
|
}
|
|
1022
|
-
this.sendOrQueuePayload(client.socket, envelope)
|
|
1132
|
+
void this.sendOrQueuePayload(client.socket, envelope).catch(() => {
|
|
1133
|
+
return void 0;
|
|
1134
|
+
});
|
|
1023
1135
|
}
|
|
1024
1136
|
}
|
|
1025
1137
|
};
|
|
@@ -1183,7 +1295,9 @@ var SecureClient = class {
|
|
|
1183
1295
|
this.pendingPayloadQueue.push(envelope);
|
|
1184
1296
|
return true;
|
|
1185
1297
|
}
|
|
1186
|
-
this.sendEncryptedEnvelope(envelope)
|
|
1298
|
+
void this.sendEncryptedEnvelope(envelope).catch(() => {
|
|
1299
|
+
return void 0;
|
|
1300
|
+
});
|
|
1187
1301
|
return true;
|
|
1188
1302
|
} catch (error) {
|
|
1189
1303
|
const normalizedError = normalizeToError(error, "Failed to emit client event.");
|
|
@@ -1429,22 +1543,26 @@ var SecureClient = class {
|
|
|
1429
1543
|
}
|
|
1430
1544
|
}
|
|
1431
1545
|
}
|
|
1432
|
-
sendEncryptedEnvelope(envelope) {
|
|
1546
|
+
async sendEncryptedEnvelope(envelope) {
|
|
1547
|
+
if (!this.socket || this.socket.readyState !== WebSocket__default.default.OPEN) {
|
|
1548
|
+
const socketStateError = new Error("Client socket is not connected.");
|
|
1549
|
+
this.notifyError(socketStateError);
|
|
1550
|
+
throw socketStateError;
|
|
1551
|
+
}
|
|
1552
|
+
const encryptionKey = this.handshakeState?.encryptionKey;
|
|
1553
|
+
if (!encryptionKey) {
|
|
1554
|
+
const missingKeyError = new Error("Missing encryption key for client payload encryption.");
|
|
1555
|
+
this.notifyError(missingKeyError);
|
|
1556
|
+
throw missingKeyError;
|
|
1557
|
+
}
|
|
1433
1558
|
try {
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
}
|
|
1437
|
-
const encryptionKey = this.handshakeState?.encryptionKey;
|
|
1438
|
-
if (!encryptionKey) {
|
|
1439
|
-
throw new Error("Missing encryption key for client payload encryption.");
|
|
1440
|
-
}
|
|
1441
|
-
const encryptedPayload = encryptSerializedEnvelope(
|
|
1442
|
-
serializeEnvelope(envelope.event, envelope.data),
|
|
1443
|
-
encryptionKey
|
|
1444
|
-
);
|
|
1559
|
+
const serializedEnvelope = await serializeEnvelope(envelope.event, envelope.data);
|
|
1560
|
+
const encryptedPayload = encryptSerializedEnvelope(serializedEnvelope, encryptionKey);
|
|
1445
1561
|
this.socket.send(encryptedPayload);
|
|
1446
1562
|
} catch (error) {
|
|
1447
|
-
|
|
1563
|
+
const normalizedError = normalizeToError(error, "Failed to send encrypted client payload.");
|
|
1564
|
+
this.notifyError(normalizedError);
|
|
1565
|
+
throw normalizedError;
|
|
1448
1566
|
}
|
|
1449
1567
|
}
|
|
1450
1568
|
sendRpcRequest(event, data, timeoutMs) {
|
|
@@ -1475,7 +1593,13 @@ var SecureClient = class {
|
|
|
1475
1593
|
this.pendingPayloadQueue.push(rpcRequestEnvelope);
|
|
1476
1594
|
return;
|
|
1477
1595
|
}
|
|
1478
|
-
this.sendEncryptedEnvelope(rpcRequestEnvelope)
|
|
1596
|
+
void this.sendEncryptedEnvelope(rpcRequestEnvelope).catch((error) => {
|
|
1597
|
+
clearTimeout(timeoutHandle);
|
|
1598
|
+
this.pendingRpcRequests.delete(requestId);
|
|
1599
|
+
reject(
|
|
1600
|
+
normalizeToError(error, `Failed to dispatch ACK request for event "${event}".`)
|
|
1601
|
+
);
|
|
1602
|
+
});
|
|
1479
1603
|
});
|
|
1480
1604
|
}
|
|
1481
1605
|
handleRpcResponse(data) {
|
|
@@ -1511,7 +1635,7 @@ var SecureClient = class {
|
|
|
1511
1635
|
rpcRequestPayload.event,
|
|
1512
1636
|
rpcRequestPayload.data
|
|
1513
1637
|
);
|
|
1514
|
-
this.sendEncryptedEnvelope({
|
|
1638
|
+
await this.sendEncryptedEnvelope({
|
|
1515
1639
|
event: INTERNAL_RPC_RESPONSE_EVENT,
|
|
1516
1640
|
data: {
|
|
1517
1641
|
id: rpcRequestPayload.id,
|
|
@@ -1521,7 +1645,7 @@ var SecureClient = class {
|
|
|
1521
1645
|
});
|
|
1522
1646
|
} catch (error) {
|
|
1523
1647
|
const normalizedError = normalizeToError(error, "Client ACK request handler failed.");
|
|
1524
|
-
this.sendEncryptedEnvelope({
|
|
1648
|
+
await this.sendEncryptedEnvelope({
|
|
1525
1649
|
event: INTERNAL_RPC_RESPONSE_EVENT,
|
|
1526
1650
|
data: {
|
|
1527
1651
|
id: rpcRequestPayload.id,
|
|
@@ -1566,7 +1690,7 @@ var SecureClient = class {
|
|
|
1566
1690
|
throw new Error("Missing client handshake state.");
|
|
1567
1691
|
}
|
|
1568
1692
|
this.socket.send(
|
|
1569
|
-
|
|
1693
|
+
serializePlainEnvelope(INTERNAL_HANDSHAKE_EVENT, {
|
|
1570
1694
|
publicKey: this.handshakeState.localPublicKey
|
|
1571
1695
|
})
|
|
1572
1696
|
);
|
|
@@ -1588,7 +1712,7 @@ var SecureClient = class {
|
|
|
1588
1712
|
this.handshakeState.sharedSecret = sharedSecret;
|
|
1589
1713
|
this.handshakeState.encryptionKey = deriveEncryptionKey(sharedSecret);
|
|
1590
1714
|
this.handshakeState.isReady = true;
|
|
1591
|
-
this.flushPendingPayloadQueue();
|
|
1715
|
+
void this.flushPendingPayloadQueue();
|
|
1592
1716
|
this.notifyReady();
|
|
1593
1717
|
} catch (error) {
|
|
1594
1718
|
this.notifyError(normalizeToError(error, "Failed to complete client handshake."));
|
|
@@ -1597,14 +1721,14 @@ var SecureClient = class {
|
|
|
1597
1721
|
isHandshakeReady() {
|
|
1598
1722
|
return this.handshakeState?.isReady ?? false;
|
|
1599
1723
|
}
|
|
1600
|
-
flushPendingPayloadQueue() {
|
|
1724
|
+
async flushPendingPayloadQueue() {
|
|
1601
1725
|
if (!this.socket || this.socket.readyState !== WebSocket__default.default.OPEN || !this.isHandshakeReady()) {
|
|
1602
1726
|
return;
|
|
1603
1727
|
}
|
|
1604
1728
|
const pendingPayloads = this.pendingPayloadQueue;
|
|
1605
1729
|
this.pendingPayloadQueue = [];
|
|
1606
1730
|
for (const envelope of pendingPayloads) {
|
|
1607
|
-
this.sendEncryptedEnvelope(envelope);
|
|
1731
|
+
await this.sendEncryptedEnvelope(envelope);
|
|
1608
1732
|
}
|
|
1609
1733
|
}
|
|
1610
1734
|
};
|