@aegis-fluxion/core 0.3.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 +173 -0
- package/dist/index.cjs +584 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -3
- package/dist/index.d.ts +27 -3
- package/dist/index.js +584 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,6 +5,8 @@ import WebSocket, { WebSocketServer } from 'ws';
|
|
|
5
5
|
var DEFAULT_CLOSE_CODE = 1e3;
|
|
6
6
|
var DEFAULT_CLOSE_REASON = "";
|
|
7
7
|
var INTERNAL_HANDSHAKE_EVENT = "__handshake";
|
|
8
|
+
var INTERNAL_RPC_REQUEST_EVENT = "__rpc:req";
|
|
9
|
+
var INTERNAL_RPC_RESPONSE_EVENT = "__rpc:res";
|
|
8
10
|
var READY_EVENT = "ready";
|
|
9
11
|
var HANDSHAKE_CURVE = "prime256v1";
|
|
10
12
|
var ENCRYPTION_ALGORITHM = "aes-256-gcm";
|
|
@@ -13,12 +15,15 @@ var GCM_AUTH_TAG_LENGTH = 16;
|
|
|
13
15
|
var ENCRYPTION_KEY_LENGTH = 32;
|
|
14
16
|
var ENCRYPTED_PACKET_VERSION = 1;
|
|
15
17
|
var ENCRYPTED_PACKET_PREFIX_LENGTH = 1 + GCM_IV_LENGTH + GCM_AUTH_TAG_LENGTH;
|
|
18
|
+
var BINARY_PAYLOAD_MARKER = "__afxBinaryPayload";
|
|
19
|
+
var BINARY_PAYLOAD_VERSION = 1;
|
|
16
20
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 15e3;
|
|
17
21
|
var DEFAULT_HEARTBEAT_TIMEOUT_MS = 15e3;
|
|
18
22
|
var DEFAULT_RECONNECT_INITIAL_DELAY_MS = 250;
|
|
19
23
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 1e4;
|
|
20
24
|
var DEFAULT_RECONNECT_FACTOR = 2;
|
|
21
25
|
var DEFAULT_RECONNECT_JITTER_RATIO = 0.2;
|
|
26
|
+
var DEFAULT_RPC_TIMEOUT_MS = 5e3;
|
|
22
27
|
function normalizeToError(error, fallbackMessage) {
|
|
23
28
|
if (error instanceof Error) {
|
|
24
29
|
return error;
|
|
@@ -52,7 +57,103 @@ function rawDataToBuffer(rawData) {
|
|
|
52
57
|
}
|
|
53
58
|
return Buffer.from(rawData);
|
|
54
59
|
}
|
|
55
|
-
function
|
|
60
|
+
function isBlobValue(value) {
|
|
61
|
+
return typeof Blob !== "undefined" && value instanceof Blob;
|
|
62
|
+
}
|
|
63
|
+
function isPlainObject(value) {
|
|
64
|
+
if (typeof value !== "object" || value === null) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
const prototype = Object.getPrototypeOf(value);
|
|
68
|
+
return prototype === Object.prototype || prototype === null;
|
|
69
|
+
}
|
|
70
|
+
function encodeBinaryPayload(kind, payloadBuffer, mimeType) {
|
|
71
|
+
const encodedPayload = {
|
|
72
|
+
[BINARY_PAYLOAD_MARKER]: BINARY_PAYLOAD_VERSION,
|
|
73
|
+
kind,
|
|
74
|
+
base64: payloadBuffer.toString("base64")
|
|
75
|
+
};
|
|
76
|
+
if (mimeType !== void 0 && mimeType.length > 0) {
|
|
77
|
+
encodedPayload.mimeType = mimeType;
|
|
78
|
+
}
|
|
79
|
+
return encodedPayload;
|
|
80
|
+
}
|
|
81
|
+
async function encodeEnvelopeData(value) {
|
|
82
|
+
if (Buffer.isBuffer(value)) {
|
|
83
|
+
return encodeBinaryPayload("buffer", value);
|
|
84
|
+
}
|
|
85
|
+
if (value instanceof Uint8Array) {
|
|
86
|
+
const typedArrayBuffer = Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
87
|
+
return encodeBinaryPayload("uint8array", typedArrayBuffer);
|
|
88
|
+
}
|
|
89
|
+
if (isBlobValue(value)) {
|
|
90
|
+
const blobBuffer = Buffer.from(await value.arrayBuffer());
|
|
91
|
+
return encodeBinaryPayload("blob", blobBuffer, value.type);
|
|
92
|
+
}
|
|
93
|
+
if (Array.isArray(value)) {
|
|
94
|
+
return Promise.all(value.map((item) => encodeEnvelopeData(item)));
|
|
95
|
+
}
|
|
96
|
+
if (isPlainObject(value)) {
|
|
97
|
+
const encodedEntries = await Promise.all(
|
|
98
|
+
Object.entries(value).map(async ([key, entryValue]) => {
|
|
99
|
+
return [key, await encodeEnvelopeData(entryValue)];
|
|
100
|
+
})
|
|
101
|
+
);
|
|
102
|
+
return Object.fromEntries(encodedEntries);
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
function isEncodedBinaryPayload(value) {
|
|
107
|
+
if (!isPlainObject(value)) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
if (value[BINARY_PAYLOAD_MARKER] !== BINARY_PAYLOAD_VERSION) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
if (value.kind !== "buffer" && value.kind !== "uint8array" && value.kind !== "blob") {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
if (typeof value.base64 !== "string") {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (value.mimeType !== void 0 && typeof value.mimeType !== "string") {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
function decodeEnvelopeData(value) {
|
|
125
|
+
if (Array.isArray(value)) {
|
|
126
|
+
return value.map((item) => decodeEnvelopeData(item));
|
|
127
|
+
}
|
|
128
|
+
if (isEncodedBinaryPayload(value)) {
|
|
129
|
+
const binaryBuffer = Buffer.from(value.base64, "base64");
|
|
130
|
+
if (value.kind === "buffer") {
|
|
131
|
+
return binaryBuffer;
|
|
132
|
+
}
|
|
133
|
+
if (value.kind === "uint8array") {
|
|
134
|
+
return Uint8Array.from(binaryBuffer);
|
|
135
|
+
}
|
|
136
|
+
if (typeof Blob === "undefined") {
|
|
137
|
+
return binaryBuffer;
|
|
138
|
+
}
|
|
139
|
+
return new Blob([binaryBuffer], {
|
|
140
|
+
type: value.mimeType ?? ""
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (isPlainObject(value)) {
|
|
144
|
+
const decodedEntries = Object.entries(value).map(([key, entryValue]) => {
|
|
145
|
+
return [key, decodeEnvelopeData(entryValue)];
|
|
146
|
+
});
|
|
147
|
+
return Object.fromEntries(decodedEntries);
|
|
148
|
+
}
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
async function serializeEnvelope(event, data) {
|
|
152
|
+
const encodedData = await encodeEnvelopeData(data);
|
|
153
|
+
const envelope = { event, data: encodedData };
|
|
154
|
+
return JSON.stringify(envelope);
|
|
155
|
+
}
|
|
156
|
+
function serializePlainEnvelope(event, data) {
|
|
56
157
|
const envelope = { event, data };
|
|
57
158
|
return JSON.stringify(envelope);
|
|
58
159
|
}
|
|
@@ -64,7 +165,7 @@ function parseEnvelope(rawData) {
|
|
|
64
165
|
}
|
|
65
166
|
return {
|
|
66
167
|
event: parsed.event,
|
|
67
|
-
data: parsed.data
|
|
168
|
+
data: decodeEnvelopeData(parsed.data)
|
|
68
169
|
};
|
|
69
170
|
}
|
|
70
171
|
function parseEnvelopeFromText(decodedPayload) {
|
|
@@ -74,14 +175,95 @@ function parseEnvelopeFromText(decodedPayload) {
|
|
|
74
175
|
}
|
|
75
176
|
return {
|
|
76
177
|
event: parsed.event,
|
|
77
|
-
data: parsed.data
|
|
178
|
+
data: decodeEnvelopeData(parsed.data)
|
|
78
179
|
};
|
|
79
180
|
}
|
|
80
181
|
function decodeCloseReason(reason) {
|
|
81
182
|
return reason.toString("utf8");
|
|
82
183
|
}
|
|
83
184
|
function isReservedEmitEvent(event) {
|
|
84
|
-
return event === INTERNAL_HANDSHAKE_EVENT || event === READY_EVENT;
|
|
185
|
+
return event === INTERNAL_HANDSHAKE_EVENT || event === INTERNAL_RPC_REQUEST_EVENT || event === INTERNAL_RPC_RESPONSE_EVENT || event === READY_EVENT;
|
|
186
|
+
}
|
|
187
|
+
function isPromiseLike(value) {
|
|
188
|
+
return typeof value === "object" && value !== null && "then" in value;
|
|
189
|
+
}
|
|
190
|
+
function normalizeRpcTimeout(timeoutMs) {
|
|
191
|
+
const resolvedTimeoutMs = timeoutMs ?? DEFAULT_RPC_TIMEOUT_MS;
|
|
192
|
+
if (!Number.isFinite(resolvedTimeoutMs) || resolvedTimeoutMs <= 0) {
|
|
193
|
+
throw new Error("ACK timeoutMs must be a positive number.");
|
|
194
|
+
}
|
|
195
|
+
return resolvedTimeoutMs;
|
|
196
|
+
}
|
|
197
|
+
function parseRpcRequestPayload(data) {
|
|
198
|
+
if (typeof data !== "object" || data === null) {
|
|
199
|
+
throw new Error("Invalid RPC request payload format.");
|
|
200
|
+
}
|
|
201
|
+
const payload = data;
|
|
202
|
+
if (typeof payload.id !== "string" || payload.id.trim().length === 0) {
|
|
203
|
+
throw new Error("RPC request payload must include a non-empty id.");
|
|
204
|
+
}
|
|
205
|
+
if (typeof payload.event !== "string" || payload.event.trim().length === 0) {
|
|
206
|
+
throw new Error("RPC request payload must include a non-empty event.");
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
id: payload.id,
|
|
210
|
+
event: payload.event,
|
|
211
|
+
data: payload.data
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function parseRpcResponsePayload(data) {
|
|
215
|
+
if (typeof data !== "object" || data === null) {
|
|
216
|
+
throw new Error("Invalid RPC response payload format.");
|
|
217
|
+
}
|
|
218
|
+
const payload = data;
|
|
219
|
+
if (typeof payload.id !== "string" || payload.id.trim().length === 0) {
|
|
220
|
+
throw new Error("RPC response payload must include a non-empty id.");
|
|
221
|
+
}
|
|
222
|
+
if (typeof payload.ok !== "boolean") {
|
|
223
|
+
throw new Error("RPC response payload must include a boolean ok field.");
|
|
224
|
+
}
|
|
225
|
+
if (payload.error !== void 0 && typeof payload.error !== "string") {
|
|
226
|
+
throw new Error("RPC response payload error must be a string when provided.");
|
|
227
|
+
}
|
|
228
|
+
const parsedPayload = {
|
|
229
|
+
id: payload.id,
|
|
230
|
+
ok: payload.ok,
|
|
231
|
+
data: payload.data
|
|
232
|
+
};
|
|
233
|
+
if (payload.error !== void 0) {
|
|
234
|
+
parsedPayload.error = payload.error;
|
|
235
|
+
}
|
|
236
|
+
return parsedPayload;
|
|
237
|
+
}
|
|
238
|
+
function resolveAckArguments(callbackOrOptions, maybeCallback) {
|
|
239
|
+
if (callbackOrOptions === void 0 && maybeCallback === void 0) {
|
|
240
|
+
return {
|
|
241
|
+
expectsAck: false,
|
|
242
|
+
timeoutMs: DEFAULT_RPC_TIMEOUT_MS
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (typeof callbackOrOptions === "function") {
|
|
246
|
+
if (maybeCallback !== void 0) {
|
|
247
|
+
throw new Error("ACK callback was provided more than once.");
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
expectsAck: true,
|
|
251
|
+
callback: callbackOrOptions,
|
|
252
|
+
timeoutMs: DEFAULT_RPC_TIMEOUT_MS
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const options = callbackOrOptions;
|
|
256
|
+
if (options !== void 0 && (typeof options !== "object" || options === null)) {
|
|
257
|
+
throw new Error("ACK options must be an object.");
|
|
258
|
+
}
|
|
259
|
+
if (maybeCallback !== void 0 && typeof maybeCallback !== "function") {
|
|
260
|
+
throw new Error("ACK callback must be a function.");
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
...maybeCallback ? { callback: maybeCallback } : {},
|
|
264
|
+
expectsAck: true,
|
|
265
|
+
timeoutMs: normalizeRpcTimeout(options?.timeoutMs)
|
|
266
|
+
};
|
|
85
267
|
}
|
|
86
268
|
function createEphemeralHandshakeState() {
|
|
87
269
|
const ecdh = createECDH(HANDSHAKE_CURVE);
|
|
@@ -179,6 +361,7 @@ var SecureServer = class {
|
|
|
179
361
|
sharedSecretBySocket = /* @__PURE__ */ new WeakMap();
|
|
180
362
|
encryptionKeyBySocket = /* @__PURE__ */ new WeakMap();
|
|
181
363
|
pendingPayloadsBySocket = /* @__PURE__ */ new WeakMap();
|
|
364
|
+
pendingRpcRequestsBySocket = /* @__PURE__ */ new WeakMap();
|
|
182
365
|
heartbeatStateBySocket = /* @__PURE__ */ new WeakMap();
|
|
183
366
|
roomMembersByName = /* @__PURE__ */ new Map();
|
|
184
367
|
roomNamesByClientId = /* @__PURE__ */ new Map();
|
|
@@ -270,14 +453,17 @@ var SecureServer = class {
|
|
|
270
453
|
}
|
|
271
454
|
const envelope = { event, data };
|
|
272
455
|
for (const client of this.clientsById.values()) {
|
|
273
|
-
this.sendOrQueuePayload(client.socket, envelope)
|
|
456
|
+
void this.sendOrQueuePayload(client.socket, envelope).catch(() => {
|
|
457
|
+
return void 0;
|
|
458
|
+
});
|
|
274
459
|
}
|
|
275
460
|
} catch (error) {
|
|
276
461
|
this.notifyError(normalizeToError(error, "Failed to emit server event."));
|
|
277
462
|
}
|
|
278
463
|
return this;
|
|
279
464
|
}
|
|
280
|
-
emitTo(clientId, event, data) {
|
|
465
|
+
emitTo(clientId, event, data, callbackOrOptions, maybeCallback) {
|
|
466
|
+
const ackArgs = resolveAckArguments(callbackOrOptions, maybeCallback);
|
|
281
467
|
try {
|
|
282
468
|
if (isReservedEmitEvent(event)) {
|
|
283
469
|
throw new Error(`The event "${event}" is reserved and cannot be emitted manually.`);
|
|
@@ -286,10 +472,39 @@ var SecureServer = class {
|
|
|
286
472
|
if (!client) {
|
|
287
473
|
throw new Error(`Client with id ${clientId} was not found.`);
|
|
288
474
|
}
|
|
289
|
-
|
|
290
|
-
|
|
475
|
+
if (!ackArgs.expectsAck) {
|
|
476
|
+
void this.sendOrQueuePayload(client.socket, { event, data }).catch(() => {
|
|
477
|
+
return void 0;
|
|
478
|
+
});
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
const ackPromise = this.sendRpcRequest(
|
|
482
|
+
client.socket,
|
|
483
|
+
event,
|
|
484
|
+
data,
|
|
485
|
+
ackArgs.timeoutMs
|
|
486
|
+
);
|
|
487
|
+
if (ackArgs.callback) {
|
|
488
|
+
ackPromise.then((response) => {
|
|
489
|
+
ackArgs.callback?.(null, response);
|
|
490
|
+
}).catch((error) => {
|
|
491
|
+
ackArgs.callback?.(
|
|
492
|
+
normalizeToError(error, `ACK callback failed for client ${client.id}.`)
|
|
493
|
+
);
|
|
494
|
+
});
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
return ackPromise;
|
|
291
498
|
} catch (error) {
|
|
292
|
-
|
|
499
|
+
const normalizedError = normalizeToError(error, "Failed to emit event to client.");
|
|
500
|
+
this.notifyError(normalizedError);
|
|
501
|
+
if (ackArgs.callback) {
|
|
502
|
+
ackArgs.callback(normalizedError);
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
if (ackArgs.expectsAck) {
|
|
506
|
+
return Promise.reject(normalizedError);
|
|
507
|
+
}
|
|
293
508
|
return false;
|
|
294
509
|
}
|
|
295
510
|
}
|
|
@@ -312,6 +527,10 @@ var SecureServer = class {
|
|
|
312
527
|
try {
|
|
313
528
|
this.stopHeartbeatLoop();
|
|
314
529
|
for (const client of this.clientsById.values()) {
|
|
530
|
+
this.rejectPendingRpcRequests(
|
|
531
|
+
client.socket,
|
|
532
|
+
new Error("Server closed before ACK response was received.")
|
|
533
|
+
);
|
|
315
534
|
if (client.socket.readyState === WebSocket.OPEN || client.socket.readyState === WebSocket.CONNECTING) {
|
|
316
535
|
client.socket.close(code, reason);
|
|
317
536
|
}
|
|
@@ -364,9 +583,14 @@ var SecureServer = class {
|
|
|
364
583
|
lastPingAt: 0
|
|
365
584
|
};
|
|
366
585
|
if (heartbeatState.awaitingPong && now - heartbeatState.lastPingAt >= this.heartbeatConfig.timeoutMs) {
|
|
586
|
+
this.rejectPendingRpcRequests(
|
|
587
|
+
socket,
|
|
588
|
+
new Error(`Heartbeat timeout while waiting for client ${client.id} ACK response.`)
|
|
589
|
+
);
|
|
367
590
|
this.sharedSecretBySocket.delete(socket);
|
|
368
591
|
this.encryptionKeyBySocket.delete(socket);
|
|
369
592
|
this.pendingPayloadsBySocket.delete(socket);
|
|
593
|
+
this.pendingRpcRequestsBySocket.delete(socket);
|
|
370
594
|
this.handshakeStateBySocket.delete(socket);
|
|
371
595
|
this.heartbeatStateBySocket.delete(socket);
|
|
372
596
|
socket.terminate();
|
|
@@ -413,6 +637,7 @@ var SecureServer = class {
|
|
|
413
637
|
this.clientIdBySocket.set(socket, clientId);
|
|
414
638
|
this.handshakeStateBySocket.set(socket, handshakeState);
|
|
415
639
|
this.pendingPayloadsBySocket.set(socket, []);
|
|
640
|
+
this.pendingRpcRequestsBySocket.set(socket, /* @__PURE__ */ new Map());
|
|
416
641
|
this.heartbeatStateBySocket.set(socket, {
|
|
417
642
|
awaitingPong: false,
|
|
418
643
|
lastPingAt: 0
|
|
@@ -480,6 +705,14 @@ var SecureServer = class {
|
|
|
480
705
|
return;
|
|
481
706
|
}
|
|
482
707
|
const decryptedEnvelope = parseEnvelopeFromText(decryptedPayload);
|
|
708
|
+
if (decryptedEnvelope.event === INTERNAL_RPC_RESPONSE_EVENT) {
|
|
709
|
+
this.handleRpcResponse(client.socket, decryptedEnvelope.data);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (decryptedEnvelope.event === INTERNAL_RPC_REQUEST_EVENT) {
|
|
713
|
+
void this.handleRpcRequest(client, decryptedEnvelope.data);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
483
716
|
this.dispatchCustomEvent(decryptedEnvelope.event, decryptedEnvelope.data, client);
|
|
484
717
|
} catch (error) {
|
|
485
718
|
this.notifyError(normalizeToError(error, "Failed to process incoming server message."));
|
|
@@ -494,6 +727,11 @@ var SecureServer = class {
|
|
|
494
727
|
this.sharedSecretBySocket.delete(client.socket);
|
|
495
728
|
this.encryptionKeyBySocket.delete(client.socket);
|
|
496
729
|
this.pendingPayloadsBySocket.delete(client.socket);
|
|
730
|
+
this.rejectPendingRpcRequests(
|
|
731
|
+
client.socket,
|
|
732
|
+
new Error(`Client ${client.id} disconnected before ACK response was received.`)
|
|
733
|
+
);
|
|
734
|
+
this.pendingRpcRequestsBySocket.delete(client.socket);
|
|
497
735
|
this.heartbeatStateBySocket.delete(client.socket);
|
|
498
736
|
const decodedReason = decodeCloseReason(reason);
|
|
499
737
|
for (const handler of this.disconnectHandlers) {
|
|
@@ -519,7 +757,17 @@ var SecureServer = class {
|
|
|
519
757
|
}
|
|
520
758
|
for (const handler of handlers) {
|
|
521
759
|
try {
|
|
522
|
-
handler(data, client);
|
|
760
|
+
const handlerResult = handler(data, client);
|
|
761
|
+
if (isPromiseLike(handlerResult)) {
|
|
762
|
+
void Promise.resolve(handlerResult).catch((error) => {
|
|
763
|
+
this.notifyError(
|
|
764
|
+
normalizeToError(
|
|
765
|
+
error,
|
|
766
|
+
`Server event handler failed for event ${event}.`
|
|
767
|
+
)
|
|
768
|
+
);
|
|
769
|
+
});
|
|
770
|
+
}
|
|
523
771
|
} catch (error) {
|
|
524
772
|
this.notifyError(
|
|
525
773
|
normalizeToError(
|
|
@@ -540,24 +788,138 @@ var SecureServer = class {
|
|
|
540
788
|
this.notifyError(normalizeToError(error, "Failed to send server payload."));
|
|
541
789
|
}
|
|
542
790
|
}
|
|
543
|
-
sendEncryptedEnvelope(socket, envelope) {
|
|
791
|
+
async sendEncryptedEnvelope(socket, envelope) {
|
|
792
|
+
if (socket.readyState !== WebSocket.OPEN) {
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const encryptionKey = this.encryptionKeyBySocket.get(socket);
|
|
796
|
+
if (!encryptionKey) {
|
|
797
|
+
const missingKeyError = new Error("Missing encryption key for connected socket.");
|
|
798
|
+
this.notifyError(missingKeyError);
|
|
799
|
+
throw missingKeyError;
|
|
800
|
+
}
|
|
544
801
|
try {
|
|
545
|
-
|
|
802
|
+
const serializedEnvelope = await serializeEnvelope(envelope.event, envelope.data);
|
|
803
|
+
const encryptedPayload = encryptSerializedEnvelope(serializedEnvelope, encryptionKey);
|
|
804
|
+
socket.send(encryptedPayload);
|
|
805
|
+
} catch (error) {
|
|
806
|
+
const normalizedError = normalizeToError(error, "Failed to send encrypted server payload.");
|
|
807
|
+
this.notifyError(normalizedError);
|
|
808
|
+
throw normalizedError;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
sendRpcRequest(socket, event, data, timeoutMs) {
|
|
812
|
+
if (socket.readyState !== WebSocket.OPEN && socket.readyState !== WebSocket.CONNECTING) {
|
|
813
|
+
throw new Error("Client socket is not connected for ACK request.");
|
|
814
|
+
}
|
|
815
|
+
const pendingRequests = this.pendingRpcRequestsBySocket.get(socket) ?? /* @__PURE__ */ new Map();
|
|
816
|
+
this.pendingRpcRequestsBySocket.set(socket, pendingRequests);
|
|
817
|
+
const requestId = randomUUID();
|
|
818
|
+
return new Promise((resolve, reject) => {
|
|
819
|
+
const timeoutHandle = setTimeout(() => {
|
|
820
|
+
pendingRequests.delete(requestId);
|
|
821
|
+
reject(new Error(`ACK response timed out after ${timeoutMs}ms for event "${event}".`));
|
|
822
|
+
}, timeoutMs);
|
|
823
|
+
timeoutHandle.unref?.();
|
|
824
|
+
pendingRequests.set(requestId, {
|
|
825
|
+
resolve,
|
|
826
|
+
reject,
|
|
827
|
+
timeoutHandle
|
|
828
|
+
});
|
|
829
|
+
void this.sendOrQueuePayload(socket, {
|
|
830
|
+
event: INTERNAL_RPC_REQUEST_EVENT,
|
|
831
|
+
data: {
|
|
832
|
+
id: requestId,
|
|
833
|
+
event,
|
|
834
|
+
data
|
|
835
|
+
}
|
|
836
|
+
}).catch((error) => {
|
|
837
|
+
clearTimeout(timeoutHandle);
|
|
838
|
+
pendingRequests.delete(requestId);
|
|
839
|
+
reject(
|
|
840
|
+
normalizeToError(error, `Failed to dispatch ACK request for event "${event}".`)
|
|
841
|
+
);
|
|
842
|
+
});
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
handleRpcResponse(socket, data) {
|
|
846
|
+
try {
|
|
847
|
+
const responsePayload = parseRpcResponsePayload(data);
|
|
848
|
+
const pendingRequests = this.pendingRpcRequestsBySocket.get(socket);
|
|
849
|
+
if (!pendingRequests) {
|
|
546
850
|
return;
|
|
547
851
|
}
|
|
548
|
-
const
|
|
549
|
-
if (!
|
|
550
|
-
|
|
852
|
+
const pendingRequest = pendingRequests.get(responsePayload.id);
|
|
853
|
+
if (!pendingRequest) {
|
|
854
|
+
return;
|
|
551
855
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
856
|
+
clearTimeout(pendingRequest.timeoutHandle);
|
|
857
|
+
pendingRequests.delete(responsePayload.id);
|
|
858
|
+
if (responsePayload.ok) {
|
|
859
|
+
pendingRequest.resolve(responsePayload.data);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
pendingRequest.reject(
|
|
863
|
+
new Error(responsePayload.error ?? "ACK request failed without an error message.")
|
|
555
864
|
);
|
|
556
|
-
socket.send(encryptedPayload);
|
|
557
865
|
} catch (error) {
|
|
558
|
-
this.notifyError(normalizeToError(error, "Failed to
|
|
866
|
+
this.notifyError(normalizeToError(error, "Failed to process server ACK response."));
|
|
559
867
|
}
|
|
560
868
|
}
|
|
869
|
+
async handleRpcRequest(client, data) {
|
|
870
|
+
let rpcRequestPayload;
|
|
871
|
+
try {
|
|
872
|
+
rpcRequestPayload = parseRpcRequestPayload(data);
|
|
873
|
+
} catch (error) {
|
|
874
|
+
this.notifyError(normalizeToError(error, "Invalid server ACK request payload."));
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
try {
|
|
878
|
+
const ackResponse = await this.executeRpcRequestHandler(
|
|
879
|
+
rpcRequestPayload.event,
|
|
880
|
+
rpcRequestPayload.data,
|
|
881
|
+
client
|
|
882
|
+
);
|
|
883
|
+
await this.sendEncryptedEnvelope(client.socket, {
|
|
884
|
+
event: INTERNAL_RPC_RESPONSE_EVENT,
|
|
885
|
+
data: {
|
|
886
|
+
id: rpcRequestPayload.id,
|
|
887
|
+
ok: true,
|
|
888
|
+
data: ackResponse
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
} catch (error) {
|
|
892
|
+
const normalizedError = normalizeToError(error, "Server ACK request handler failed.");
|
|
893
|
+
await this.sendEncryptedEnvelope(client.socket, {
|
|
894
|
+
event: INTERNAL_RPC_RESPONSE_EVENT,
|
|
895
|
+
data: {
|
|
896
|
+
id: rpcRequestPayload.id,
|
|
897
|
+
ok: false,
|
|
898
|
+
error: normalizedError.message
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
this.notifyError(normalizedError);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
async executeRpcRequestHandler(event, data, client) {
|
|
905
|
+
const handlers = this.customEventHandlers.get(event);
|
|
906
|
+
if (!handlers || handlers.size === 0) {
|
|
907
|
+
throw new Error(`No handler is registered for ACK request event "${event}".`);
|
|
908
|
+
}
|
|
909
|
+
const firstHandler = handlers.values().next().value;
|
|
910
|
+
return Promise.resolve(firstHandler(data, client));
|
|
911
|
+
}
|
|
912
|
+
rejectPendingRpcRequests(socket, error) {
|
|
913
|
+
const pendingRequests = this.pendingRpcRequestsBySocket.get(socket);
|
|
914
|
+
if (!pendingRequests) {
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
for (const pendingRequest of pendingRequests.values()) {
|
|
918
|
+
clearTimeout(pendingRequest.timeoutHandle);
|
|
919
|
+
pendingRequest.reject(error);
|
|
920
|
+
}
|
|
921
|
+
pendingRequests.clear();
|
|
922
|
+
}
|
|
561
923
|
notifyConnection(client) {
|
|
562
924
|
for (const handler of this.connectionHandlers) {
|
|
563
925
|
try {
|
|
@@ -602,7 +964,7 @@ var SecureServer = class {
|
|
|
602
964
|
sendInternalHandshake(socket, localPublicKey) {
|
|
603
965
|
this.sendRaw(
|
|
604
966
|
socket,
|
|
605
|
-
|
|
967
|
+
serializePlainEnvelope(INTERNAL_HANDSHAKE_EVENT, {
|
|
606
968
|
publicKey: localPublicKey
|
|
607
969
|
})
|
|
608
970
|
);
|
|
@@ -635,23 +997,23 @@ var SecureServer = class {
|
|
|
635
997
|
sendOrQueuePayload(socket, envelope) {
|
|
636
998
|
if (!this.isClientHandshakeReady(socket)) {
|
|
637
999
|
this.queuePayload(socket, envelope);
|
|
638
|
-
return;
|
|
1000
|
+
return Promise.resolve();
|
|
639
1001
|
}
|
|
640
|
-
this.sendEncryptedEnvelope(socket, envelope);
|
|
1002
|
+
return this.sendEncryptedEnvelope(socket, envelope);
|
|
641
1003
|
}
|
|
642
1004
|
queuePayload(socket, envelope) {
|
|
643
1005
|
const pendingPayloads = this.pendingPayloadsBySocket.get(socket) ?? [];
|
|
644
1006
|
pendingPayloads.push(envelope);
|
|
645
1007
|
this.pendingPayloadsBySocket.set(socket, pendingPayloads);
|
|
646
1008
|
}
|
|
647
|
-
flushQueuedPayloads(socket) {
|
|
1009
|
+
async flushQueuedPayloads(socket) {
|
|
648
1010
|
const pendingPayloads = this.pendingPayloadsBySocket.get(socket);
|
|
649
1011
|
if (!pendingPayloads || pendingPayloads.length === 0) {
|
|
650
1012
|
return;
|
|
651
1013
|
}
|
|
652
1014
|
this.pendingPayloadsBySocket.delete(socket);
|
|
653
1015
|
for (const envelope of pendingPayloads) {
|
|
654
|
-
this.sendEncryptedEnvelope(socket, envelope);
|
|
1016
|
+
await this.sendEncryptedEnvelope(socket, envelope);
|
|
655
1017
|
}
|
|
656
1018
|
}
|
|
657
1019
|
createSecureServerClient(clientId, socket, request) {
|
|
@@ -659,6 +1021,24 @@ var SecureServer = class {
|
|
|
659
1021
|
id: clientId,
|
|
660
1022
|
socket,
|
|
661
1023
|
request,
|
|
1024
|
+
emit: (event, data, callbackOrOptions, maybeCallback) => {
|
|
1025
|
+
if (callbackOrOptions === void 0 && maybeCallback === void 0) {
|
|
1026
|
+
return this.emitTo(clientId, event, data);
|
|
1027
|
+
}
|
|
1028
|
+
if (typeof callbackOrOptions === "function") {
|
|
1029
|
+
return this.emitTo(clientId, event, data, callbackOrOptions);
|
|
1030
|
+
}
|
|
1031
|
+
if (maybeCallback) {
|
|
1032
|
+
return this.emitTo(
|
|
1033
|
+
clientId,
|
|
1034
|
+
event,
|
|
1035
|
+
data,
|
|
1036
|
+
callbackOrOptions ?? {},
|
|
1037
|
+
maybeCallback
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
return this.emitTo(clientId, event, data, callbackOrOptions ?? {});
|
|
1041
|
+
},
|
|
662
1042
|
join: (room) => this.joinClientToRoom(clientId, room),
|
|
663
1043
|
leave: (room) => this.leaveClientFromRoom(clientId, room),
|
|
664
1044
|
leaveAll: () => this.leaveClientFromAllRooms(clientId)
|
|
@@ -743,7 +1123,9 @@ var SecureServer = class {
|
|
|
743
1123
|
if (!client) {
|
|
744
1124
|
continue;
|
|
745
1125
|
}
|
|
746
|
-
this.sendOrQueuePayload(client.socket, envelope)
|
|
1126
|
+
void this.sendOrQueuePayload(client.socket, envelope).catch(() => {
|
|
1127
|
+
return void 0;
|
|
1128
|
+
});
|
|
747
1129
|
}
|
|
748
1130
|
}
|
|
749
1131
|
};
|
|
@@ -770,6 +1152,7 @@ var SecureClient = class {
|
|
|
770
1152
|
errorHandlers = /* @__PURE__ */ new Set();
|
|
771
1153
|
handshakeState = null;
|
|
772
1154
|
pendingPayloadQueue = [];
|
|
1155
|
+
pendingRpcRequests = /* @__PURE__ */ new Map();
|
|
773
1156
|
get readyState() {
|
|
774
1157
|
return this.socket?.readyState ?? null;
|
|
775
1158
|
}
|
|
@@ -878,7 +1261,8 @@ var SecureClient = class {
|
|
|
878
1261
|
}
|
|
879
1262
|
return this;
|
|
880
1263
|
}
|
|
881
|
-
emit(event, data) {
|
|
1264
|
+
emit(event, data, callbackOrOptions, maybeCallback) {
|
|
1265
|
+
const ackArgs = resolveAckArguments(callbackOrOptions, maybeCallback);
|
|
882
1266
|
try {
|
|
883
1267
|
if (isReservedEmitEvent(event)) {
|
|
884
1268
|
throw new Error(`The event "${event}" is reserved and cannot be emitted manually.`);
|
|
@@ -886,15 +1270,39 @@ var SecureClient = class {
|
|
|
886
1270
|
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
887
1271
|
throw new Error("Client socket is not connected.");
|
|
888
1272
|
}
|
|
1273
|
+
if (ackArgs.expectsAck) {
|
|
1274
|
+
const ackPromise = this.sendRpcRequest(event, data, ackArgs.timeoutMs);
|
|
1275
|
+
if (ackArgs.callback) {
|
|
1276
|
+
ackPromise.then((response) => {
|
|
1277
|
+
ackArgs.callback?.(null, response);
|
|
1278
|
+
}).catch((error) => {
|
|
1279
|
+
ackArgs.callback?.(
|
|
1280
|
+
normalizeToError(error, `ACK callback failed for event "${event}".`)
|
|
1281
|
+
);
|
|
1282
|
+
});
|
|
1283
|
+
return true;
|
|
1284
|
+
}
|
|
1285
|
+
return ackPromise;
|
|
1286
|
+
}
|
|
889
1287
|
const envelope = { event, data };
|
|
890
1288
|
if (!this.isHandshakeReady()) {
|
|
891
1289
|
this.pendingPayloadQueue.push(envelope);
|
|
892
1290
|
return true;
|
|
893
1291
|
}
|
|
894
|
-
this.sendEncryptedEnvelope(envelope)
|
|
1292
|
+
void this.sendEncryptedEnvelope(envelope).catch(() => {
|
|
1293
|
+
return void 0;
|
|
1294
|
+
});
|
|
895
1295
|
return true;
|
|
896
1296
|
} catch (error) {
|
|
897
|
-
|
|
1297
|
+
const normalizedError = normalizeToError(error, "Failed to emit client event.");
|
|
1298
|
+
this.notifyError(normalizedError);
|
|
1299
|
+
if (ackArgs.callback) {
|
|
1300
|
+
ackArgs.callback(normalizedError);
|
|
1301
|
+
return false;
|
|
1302
|
+
}
|
|
1303
|
+
if (ackArgs.expectsAck) {
|
|
1304
|
+
return Promise.reject(normalizedError);
|
|
1305
|
+
}
|
|
898
1306
|
return false;
|
|
899
1307
|
}
|
|
900
1308
|
}
|
|
@@ -1036,6 +1444,14 @@ var SecureClient = class {
|
|
|
1036
1444
|
return;
|
|
1037
1445
|
}
|
|
1038
1446
|
const decryptedEnvelope = parseEnvelopeFromText(decryptedPayload);
|
|
1447
|
+
if (decryptedEnvelope.event === INTERNAL_RPC_RESPONSE_EVENT) {
|
|
1448
|
+
this.handleRpcResponse(decryptedEnvelope.data);
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
if (decryptedEnvelope.event === INTERNAL_RPC_REQUEST_EVENT) {
|
|
1452
|
+
void this.handleRpcRequest(decryptedEnvelope.data);
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1039
1455
|
this.dispatchCustomEvent(decryptedEnvelope.event, decryptedEnvelope.data);
|
|
1040
1456
|
} catch (error) {
|
|
1041
1457
|
this.notifyError(normalizeToError(error, "Failed to process incoming client message."));
|
|
@@ -1046,6 +1462,9 @@ var SecureClient = class {
|
|
|
1046
1462
|
this.socket = null;
|
|
1047
1463
|
this.handshakeState = null;
|
|
1048
1464
|
this.pendingPayloadQueue = [];
|
|
1465
|
+
this.rejectPendingRpcRequests(
|
|
1466
|
+
new Error("Client disconnected before ACK response was received.")
|
|
1467
|
+
);
|
|
1049
1468
|
const decodedReason = decodeCloseReason(reason);
|
|
1050
1469
|
for (const handler of this.disconnectHandlers) {
|
|
1051
1470
|
try {
|
|
@@ -1071,7 +1490,17 @@ var SecureClient = class {
|
|
|
1071
1490
|
}
|
|
1072
1491
|
for (const handler of handlers) {
|
|
1073
1492
|
try {
|
|
1074
|
-
handler(data);
|
|
1493
|
+
const handlerResult = handler(data);
|
|
1494
|
+
if (isPromiseLike(handlerResult)) {
|
|
1495
|
+
void Promise.resolve(handlerResult).catch((error) => {
|
|
1496
|
+
this.notifyError(
|
|
1497
|
+
normalizeToError(
|
|
1498
|
+
error,
|
|
1499
|
+
`Client event handler failed for event ${event}.`
|
|
1500
|
+
)
|
|
1501
|
+
);
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1075
1504
|
} catch (error) {
|
|
1076
1505
|
this.notifyError(
|
|
1077
1506
|
normalizeToError(error, `Client event handler failed for event ${event}.`)
|
|
@@ -1108,23 +1537,133 @@ var SecureClient = class {
|
|
|
1108
1537
|
}
|
|
1109
1538
|
}
|
|
1110
1539
|
}
|
|
1111
|
-
sendEncryptedEnvelope(envelope) {
|
|
1540
|
+
async sendEncryptedEnvelope(envelope) {
|
|
1541
|
+
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
1542
|
+
const socketStateError = new Error("Client socket is not connected.");
|
|
1543
|
+
this.notifyError(socketStateError);
|
|
1544
|
+
throw socketStateError;
|
|
1545
|
+
}
|
|
1546
|
+
const encryptionKey = this.handshakeState?.encryptionKey;
|
|
1547
|
+
if (!encryptionKey) {
|
|
1548
|
+
const missingKeyError = new Error("Missing encryption key for client payload encryption.");
|
|
1549
|
+
this.notifyError(missingKeyError);
|
|
1550
|
+
throw missingKeyError;
|
|
1551
|
+
}
|
|
1112
1552
|
try {
|
|
1113
|
-
|
|
1114
|
-
|
|
1553
|
+
const serializedEnvelope = await serializeEnvelope(envelope.event, envelope.data);
|
|
1554
|
+
const encryptedPayload = encryptSerializedEnvelope(serializedEnvelope, encryptionKey);
|
|
1555
|
+
this.socket.send(encryptedPayload);
|
|
1556
|
+
} catch (error) {
|
|
1557
|
+
const normalizedError = normalizeToError(error, "Failed to send encrypted client payload.");
|
|
1558
|
+
this.notifyError(normalizedError);
|
|
1559
|
+
throw normalizedError;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
sendRpcRequest(event, data, timeoutMs) {
|
|
1563
|
+
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
1564
|
+
throw new Error("Client socket is not connected for ACK request.");
|
|
1565
|
+
}
|
|
1566
|
+
const requestId = randomUUID();
|
|
1567
|
+
return new Promise((resolve, reject) => {
|
|
1568
|
+
const timeoutHandle = setTimeout(() => {
|
|
1569
|
+
this.pendingRpcRequests.delete(requestId);
|
|
1570
|
+
reject(new Error(`ACK response timed out after ${timeoutMs}ms for event "${event}".`));
|
|
1571
|
+
}, timeoutMs);
|
|
1572
|
+
timeoutHandle.unref?.();
|
|
1573
|
+
this.pendingRpcRequests.set(requestId, {
|
|
1574
|
+
resolve,
|
|
1575
|
+
reject,
|
|
1576
|
+
timeoutHandle
|
|
1577
|
+
});
|
|
1578
|
+
const rpcRequestEnvelope = {
|
|
1579
|
+
event: INTERNAL_RPC_REQUEST_EVENT,
|
|
1580
|
+
data: {
|
|
1581
|
+
id: requestId,
|
|
1582
|
+
event,
|
|
1583
|
+
data
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
if (!this.isHandshakeReady()) {
|
|
1587
|
+
this.pendingPayloadQueue.push(rpcRequestEnvelope);
|
|
1588
|
+
return;
|
|
1115
1589
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1590
|
+
void this.sendEncryptedEnvelope(rpcRequestEnvelope).catch((error) => {
|
|
1591
|
+
clearTimeout(timeoutHandle);
|
|
1592
|
+
this.pendingRpcRequests.delete(requestId);
|
|
1593
|
+
reject(
|
|
1594
|
+
normalizeToError(error, `Failed to dispatch ACK request for event "${event}".`)
|
|
1595
|
+
);
|
|
1596
|
+
});
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
handleRpcResponse(data) {
|
|
1600
|
+
try {
|
|
1601
|
+
const responsePayload = parseRpcResponsePayload(data);
|
|
1602
|
+
const pendingRequest = this.pendingRpcRequests.get(responsePayload.id);
|
|
1603
|
+
if (!pendingRequest) {
|
|
1604
|
+
return;
|
|
1119
1605
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1606
|
+
clearTimeout(pendingRequest.timeoutHandle);
|
|
1607
|
+
this.pendingRpcRequests.delete(responsePayload.id);
|
|
1608
|
+
if (responsePayload.ok) {
|
|
1609
|
+
pendingRequest.resolve(responsePayload.data);
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
pendingRequest.reject(
|
|
1613
|
+
new Error(responsePayload.error ?? "ACK request failed without an error message.")
|
|
1123
1614
|
);
|
|
1124
|
-
this.socket.send(encryptedPayload);
|
|
1125
1615
|
} catch (error) {
|
|
1126
|
-
this.notifyError(normalizeToError(error, "Failed to
|
|
1616
|
+
this.notifyError(normalizeToError(error, "Failed to process client ACK response."));
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
async handleRpcRequest(data) {
|
|
1620
|
+
let rpcRequestPayload;
|
|
1621
|
+
try {
|
|
1622
|
+
rpcRequestPayload = parseRpcRequestPayload(data);
|
|
1623
|
+
} catch (error) {
|
|
1624
|
+
this.notifyError(normalizeToError(error, "Invalid client ACK request payload."));
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
try {
|
|
1628
|
+
const ackResponse = await this.executeRpcRequestHandler(
|
|
1629
|
+
rpcRequestPayload.event,
|
|
1630
|
+
rpcRequestPayload.data
|
|
1631
|
+
);
|
|
1632
|
+
await this.sendEncryptedEnvelope({
|
|
1633
|
+
event: INTERNAL_RPC_RESPONSE_EVENT,
|
|
1634
|
+
data: {
|
|
1635
|
+
id: rpcRequestPayload.id,
|
|
1636
|
+
ok: true,
|
|
1637
|
+
data: ackResponse
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1640
|
+
} catch (error) {
|
|
1641
|
+
const normalizedError = normalizeToError(error, "Client ACK request handler failed.");
|
|
1642
|
+
await this.sendEncryptedEnvelope({
|
|
1643
|
+
event: INTERNAL_RPC_RESPONSE_EVENT,
|
|
1644
|
+
data: {
|
|
1645
|
+
id: rpcRequestPayload.id,
|
|
1646
|
+
ok: false,
|
|
1647
|
+
error: normalizedError.message
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
this.notifyError(normalizedError);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
async executeRpcRequestHandler(event, data) {
|
|
1654
|
+
const handlers = this.customEventHandlers.get(event);
|
|
1655
|
+
if (!handlers || handlers.size === 0) {
|
|
1656
|
+
throw new Error(`No handler is registered for ACK request event "${event}".`);
|
|
1657
|
+
}
|
|
1658
|
+
const firstHandler = handlers.values().next().value;
|
|
1659
|
+
return Promise.resolve(firstHandler(data));
|
|
1660
|
+
}
|
|
1661
|
+
rejectPendingRpcRequests(error) {
|
|
1662
|
+
for (const pendingRequest of this.pendingRpcRequests.values()) {
|
|
1663
|
+
clearTimeout(pendingRequest.timeoutHandle);
|
|
1664
|
+
pendingRequest.reject(error);
|
|
1127
1665
|
}
|
|
1666
|
+
this.pendingRpcRequests.clear();
|
|
1128
1667
|
}
|
|
1129
1668
|
createClientHandshakeState() {
|
|
1130
1669
|
const { ecdh, localPublicKey } = createEphemeralHandshakeState();
|
|
@@ -1145,7 +1684,7 @@ var SecureClient = class {
|
|
|
1145
1684
|
throw new Error("Missing client handshake state.");
|
|
1146
1685
|
}
|
|
1147
1686
|
this.socket.send(
|
|
1148
|
-
|
|
1687
|
+
serializePlainEnvelope(INTERNAL_HANDSHAKE_EVENT, {
|
|
1149
1688
|
publicKey: this.handshakeState.localPublicKey
|
|
1150
1689
|
})
|
|
1151
1690
|
);
|
|
@@ -1167,7 +1706,7 @@ var SecureClient = class {
|
|
|
1167
1706
|
this.handshakeState.sharedSecret = sharedSecret;
|
|
1168
1707
|
this.handshakeState.encryptionKey = deriveEncryptionKey(sharedSecret);
|
|
1169
1708
|
this.handshakeState.isReady = true;
|
|
1170
|
-
this.flushPendingPayloadQueue();
|
|
1709
|
+
void this.flushPendingPayloadQueue();
|
|
1171
1710
|
this.notifyReady();
|
|
1172
1711
|
} catch (error) {
|
|
1173
1712
|
this.notifyError(normalizeToError(error, "Failed to complete client handshake."));
|
|
@@ -1176,14 +1715,14 @@ var SecureClient = class {
|
|
|
1176
1715
|
isHandshakeReady() {
|
|
1177
1716
|
return this.handshakeState?.isReady ?? false;
|
|
1178
1717
|
}
|
|
1179
|
-
flushPendingPayloadQueue() {
|
|
1718
|
+
async flushPendingPayloadQueue() {
|
|
1180
1719
|
if (!this.socket || this.socket.readyState !== WebSocket.OPEN || !this.isHandshakeReady()) {
|
|
1181
1720
|
return;
|
|
1182
1721
|
}
|
|
1183
1722
|
const pendingPayloads = this.pendingPayloadQueue;
|
|
1184
1723
|
this.pendingPayloadQueue = [];
|
|
1185
1724
|
for (const envelope of pendingPayloads) {
|
|
1186
|
-
this.sendEncryptedEnvelope(envelope);
|
|
1725
|
+
await this.sendEncryptedEnvelope(envelope);
|
|
1187
1726
|
}
|
|
1188
1727
|
}
|
|
1189
1728
|
};
|