@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.cjs
CHANGED
|
@@ -11,6 +11,8 @@ var WebSocket__default = /*#__PURE__*/_interopDefault(WebSocket);
|
|
|
11
11
|
var DEFAULT_CLOSE_CODE = 1e3;
|
|
12
12
|
var DEFAULT_CLOSE_REASON = "";
|
|
13
13
|
var INTERNAL_HANDSHAKE_EVENT = "__handshake";
|
|
14
|
+
var INTERNAL_RPC_REQUEST_EVENT = "__rpc:req";
|
|
15
|
+
var INTERNAL_RPC_RESPONSE_EVENT = "__rpc:res";
|
|
14
16
|
var READY_EVENT = "ready";
|
|
15
17
|
var HANDSHAKE_CURVE = "prime256v1";
|
|
16
18
|
var ENCRYPTION_ALGORITHM = "aes-256-gcm";
|
|
@@ -19,12 +21,15 @@ var GCM_AUTH_TAG_LENGTH = 16;
|
|
|
19
21
|
var ENCRYPTION_KEY_LENGTH = 32;
|
|
20
22
|
var ENCRYPTED_PACKET_VERSION = 1;
|
|
21
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;
|
|
22
26
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 15e3;
|
|
23
27
|
var DEFAULT_HEARTBEAT_TIMEOUT_MS = 15e3;
|
|
24
28
|
var DEFAULT_RECONNECT_INITIAL_DELAY_MS = 250;
|
|
25
29
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 1e4;
|
|
26
30
|
var DEFAULT_RECONNECT_FACTOR = 2;
|
|
27
31
|
var DEFAULT_RECONNECT_JITTER_RATIO = 0.2;
|
|
32
|
+
var DEFAULT_RPC_TIMEOUT_MS = 5e3;
|
|
28
33
|
function normalizeToError(error, fallbackMessage) {
|
|
29
34
|
if (error instanceof Error) {
|
|
30
35
|
return error;
|
|
@@ -58,7 +63,103 @@ function rawDataToBuffer(rawData) {
|
|
|
58
63
|
}
|
|
59
64
|
return Buffer.from(rawData);
|
|
60
65
|
}
|
|
61
|
-
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) {
|
|
62
163
|
const envelope = { event, data };
|
|
63
164
|
return JSON.stringify(envelope);
|
|
64
165
|
}
|
|
@@ -70,7 +171,7 @@ function parseEnvelope(rawData) {
|
|
|
70
171
|
}
|
|
71
172
|
return {
|
|
72
173
|
event: parsed.event,
|
|
73
|
-
data: parsed.data
|
|
174
|
+
data: decodeEnvelopeData(parsed.data)
|
|
74
175
|
};
|
|
75
176
|
}
|
|
76
177
|
function parseEnvelopeFromText(decodedPayload) {
|
|
@@ -80,14 +181,95 @@ function parseEnvelopeFromText(decodedPayload) {
|
|
|
80
181
|
}
|
|
81
182
|
return {
|
|
82
183
|
event: parsed.event,
|
|
83
|
-
data: parsed.data
|
|
184
|
+
data: decodeEnvelopeData(parsed.data)
|
|
84
185
|
};
|
|
85
186
|
}
|
|
86
187
|
function decodeCloseReason(reason) {
|
|
87
188
|
return reason.toString("utf8");
|
|
88
189
|
}
|
|
89
190
|
function isReservedEmitEvent(event) {
|
|
90
|
-
return event === INTERNAL_HANDSHAKE_EVENT || event === READY_EVENT;
|
|
191
|
+
return event === INTERNAL_HANDSHAKE_EVENT || event === INTERNAL_RPC_REQUEST_EVENT || event === INTERNAL_RPC_RESPONSE_EVENT || event === READY_EVENT;
|
|
192
|
+
}
|
|
193
|
+
function isPromiseLike(value) {
|
|
194
|
+
return typeof value === "object" && value !== null && "then" in value;
|
|
195
|
+
}
|
|
196
|
+
function normalizeRpcTimeout(timeoutMs) {
|
|
197
|
+
const resolvedTimeoutMs = timeoutMs ?? DEFAULT_RPC_TIMEOUT_MS;
|
|
198
|
+
if (!Number.isFinite(resolvedTimeoutMs) || resolvedTimeoutMs <= 0) {
|
|
199
|
+
throw new Error("ACK timeoutMs must be a positive number.");
|
|
200
|
+
}
|
|
201
|
+
return resolvedTimeoutMs;
|
|
202
|
+
}
|
|
203
|
+
function parseRpcRequestPayload(data) {
|
|
204
|
+
if (typeof data !== "object" || data === null) {
|
|
205
|
+
throw new Error("Invalid RPC request payload format.");
|
|
206
|
+
}
|
|
207
|
+
const payload = data;
|
|
208
|
+
if (typeof payload.id !== "string" || payload.id.trim().length === 0) {
|
|
209
|
+
throw new Error("RPC request payload must include a non-empty id.");
|
|
210
|
+
}
|
|
211
|
+
if (typeof payload.event !== "string" || payload.event.trim().length === 0) {
|
|
212
|
+
throw new Error("RPC request payload must include a non-empty event.");
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
id: payload.id,
|
|
216
|
+
event: payload.event,
|
|
217
|
+
data: payload.data
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function parseRpcResponsePayload(data) {
|
|
221
|
+
if (typeof data !== "object" || data === null) {
|
|
222
|
+
throw new Error("Invalid RPC response payload format.");
|
|
223
|
+
}
|
|
224
|
+
const payload = data;
|
|
225
|
+
if (typeof payload.id !== "string" || payload.id.trim().length === 0) {
|
|
226
|
+
throw new Error("RPC response payload must include a non-empty id.");
|
|
227
|
+
}
|
|
228
|
+
if (typeof payload.ok !== "boolean") {
|
|
229
|
+
throw new Error("RPC response payload must include a boolean ok field.");
|
|
230
|
+
}
|
|
231
|
+
if (payload.error !== void 0 && typeof payload.error !== "string") {
|
|
232
|
+
throw new Error("RPC response payload error must be a string when provided.");
|
|
233
|
+
}
|
|
234
|
+
const parsedPayload = {
|
|
235
|
+
id: payload.id,
|
|
236
|
+
ok: payload.ok,
|
|
237
|
+
data: payload.data
|
|
238
|
+
};
|
|
239
|
+
if (payload.error !== void 0) {
|
|
240
|
+
parsedPayload.error = payload.error;
|
|
241
|
+
}
|
|
242
|
+
return parsedPayload;
|
|
243
|
+
}
|
|
244
|
+
function resolveAckArguments(callbackOrOptions, maybeCallback) {
|
|
245
|
+
if (callbackOrOptions === void 0 && maybeCallback === void 0) {
|
|
246
|
+
return {
|
|
247
|
+
expectsAck: false,
|
|
248
|
+
timeoutMs: DEFAULT_RPC_TIMEOUT_MS
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
if (typeof callbackOrOptions === "function") {
|
|
252
|
+
if (maybeCallback !== void 0) {
|
|
253
|
+
throw new Error("ACK callback was provided more than once.");
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
expectsAck: true,
|
|
257
|
+
callback: callbackOrOptions,
|
|
258
|
+
timeoutMs: DEFAULT_RPC_TIMEOUT_MS
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const options = callbackOrOptions;
|
|
262
|
+
if (options !== void 0 && (typeof options !== "object" || options === null)) {
|
|
263
|
+
throw new Error("ACK options must be an object.");
|
|
264
|
+
}
|
|
265
|
+
if (maybeCallback !== void 0 && typeof maybeCallback !== "function") {
|
|
266
|
+
throw new Error("ACK callback must be a function.");
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
...maybeCallback ? { callback: maybeCallback } : {},
|
|
270
|
+
expectsAck: true,
|
|
271
|
+
timeoutMs: normalizeRpcTimeout(options?.timeoutMs)
|
|
272
|
+
};
|
|
91
273
|
}
|
|
92
274
|
function createEphemeralHandshakeState() {
|
|
93
275
|
const ecdh = crypto.createECDH(HANDSHAKE_CURVE);
|
|
@@ -185,6 +367,7 @@ var SecureServer = class {
|
|
|
185
367
|
sharedSecretBySocket = /* @__PURE__ */ new WeakMap();
|
|
186
368
|
encryptionKeyBySocket = /* @__PURE__ */ new WeakMap();
|
|
187
369
|
pendingPayloadsBySocket = /* @__PURE__ */ new WeakMap();
|
|
370
|
+
pendingRpcRequestsBySocket = /* @__PURE__ */ new WeakMap();
|
|
188
371
|
heartbeatStateBySocket = /* @__PURE__ */ new WeakMap();
|
|
189
372
|
roomMembersByName = /* @__PURE__ */ new Map();
|
|
190
373
|
roomNamesByClientId = /* @__PURE__ */ new Map();
|
|
@@ -276,14 +459,17 @@ var SecureServer = class {
|
|
|
276
459
|
}
|
|
277
460
|
const envelope = { event, data };
|
|
278
461
|
for (const client of this.clientsById.values()) {
|
|
279
|
-
this.sendOrQueuePayload(client.socket, envelope)
|
|
462
|
+
void this.sendOrQueuePayload(client.socket, envelope).catch(() => {
|
|
463
|
+
return void 0;
|
|
464
|
+
});
|
|
280
465
|
}
|
|
281
466
|
} catch (error) {
|
|
282
467
|
this.notifyError(normalizeToError(error, "Failed to emit server event."));
|
|
283
468
|
}
|
|
284
469
|
return this;
|
|
285
470
|
}
|
|
286
|
-
emitTo(clientId, event, data) {
|
|
471
|
+
emitTo(clientId, event, data, callbackOrOptions, maybeCallback) {
|
|
472
|
+
const ackArgs = resolveAckArguments(callbackOrOptions, maybeCallback);
|
|
287
473
|
try {
|
|
288
474
|
if (isReservedEmitEvent(event)) {
|
|
289
475
|
throw new Error(`The event "${event}" is reserved and cannot be emitted manually.`);
|
|
@@ -292,10 +478,39 @@ var SecureServer = class {
|
|
|
292
478
|
if (!client) {
|
|
293
479
|
throw new Error(`Client with id ${clientId} was not found.`);
|
|
294
480
|
}
|
|
295
|
-
|
|
296
|
-
|
|
481
|
+
if (!ackArgs.expectsAck) {
|
|
482
|
+
void this.sendOrQueuePayload(client.socket, { event, data }).catch(() => {
|
|
483
|
+
return void 0;
|
|
484
|
+
});
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
const ackPromise = this.sendRpcRequest(
|
|
488
|
+
client.socket,
|
|
489
|
+
event,
|
|
490
|
+
data,
|
|
491
|
+
ackArgs.timeoutMs
|
|
492
|
+
);
|
|
493
|
+
if (ackArgs.callback) {
|
|
494
|
+
ackPromise.then((response) => {
|
|
495
|
+
ackArgs.callback?.(null, response);
|
|
496
|
+
}).catch((error) => {
|
|
497
|
+
ackArgs.callback?.(
|
|
498
|
+
normalizeToError(error, `ACK callback failed for client ${client.id}.`)
|
|
499
|
+
);
|
|
500
|
+
});
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
return ackPromise;
|
|
297
504
|
} catch (error) {
|
|
298
|
-
|
|
505
|
+
const normalizedError = normalizeToError(error, "Failed to emit event to client.");
|
|
506
|
+
this.notifyError(normalizedError);
|
|
507
|
+
if (ackArgs.callback) {
|
|
508
|
+
ackArgs.callback(normalizedError);
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
if (ackArgs.expectsAck) {
|
|
512
|
+
return Promise.reject(normalizedError);
|
|
513
|
+
}
|
|
299
514
|
return false;
|
|
300
515
|
}
|
|
301
516
|
}
|
|
@@ -318,6 +533,10 @@ var SecureServer = class {
|
|
|
318
533
|
try {
|
|
319
534
|
this.stopHeartbeatLoop();
|
|
320
535
|
for (const client of this.clientsById.values()) {
|
|
536
|
+
this.rejectPendingRpcRequests(
|
|
537
|
+
client.socket,
|
|
538
|
+
new Error("Server closed before ACK response was received.")
|
|
539
|
+
);
|
|
321
540
|
if (client.socket.readyState === WebSocket__default.default.OPEN || client.socket.readyState === WebSocket__default.default.CONNECTING) {
|
|
322
541
|
client.socket.close(code, reason);
|
|
323
542
|
}
|
|
@@ -370,9 +589,14 @@ var SecureServer = class {
|
|
|
370
589
|
lastPingAt: 0
|
|
371
590
|
};
|
|
372
591
|
if (heartbeatState.awaitingPong && now - heartbeatState.lastPingAt >= this.heartbeatConfig.timeoutMs) {
|
|
592
|
+
this.rejectPendingRpcRequests(
|
|
593
|
+
socket,
|
|
594
|
+
new Error(`Heartbeat timeout while waiting for client ${client.id} ACK response.`)
|
|
595
|
+
);
|
|
373
596
|
this.sharedSecretBySocket.delete(socket);
|
|
374
597
|
this.encryptionKeyBySocket.delete(socket);
|
|
375
598
|
this.pendingPayloadsBySocket.delete(socket);
|
|
599
|
+
this.pendingRpcRequestsBySocket.delete(socket);
|
|
376
600
|
this.handshakeStateBySocket.delete(socket);
|
|
377
601
|
this.heartbeatStateBySocket.delete(socket);
|
|
378
602
|
socket.terminate();
|
|
@@ -419,6 +643,7 @@ var SecureServer = class {
|
|
|
419
643
|
this.clientIdBySocket.set(socket, clientId);
|
|
420
644
|
this.handshakeStateBySocket.set(socket, handshakeState);
|
|
421
645
|
this.pendingPayloadsBySocket.set(socket, []);
|
|
646
|
+
this.pendingRpcRequestsBySocket.set(socket, /* @__PURE__ */ new Map());
|
|
422
647
|
this.heartbeatStateBySocket.set(socket, {
|
|
423
648
|
awaitingPong: false,
|
|
424
649
|
lastPingAt: 0
|
|
@@ -486,6 +711,14 @@ var SecureServer = class {
|
|
|
486
711
|
return;
|
|
487
712
|
}
|
|
488
713
|
const decryptedEnvelope = parseEnvelopeFromText(decryptedPayload);
|
|
714
|
+
if (decryptedEnvelope.event === INTERNAL_RPC_RESPONSE_EVENT) {
|
|
715
|
+
this.handleRpcResponse(client.socket, decryptedEnvelope.data);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (decryptedEnvelope.event === INTERNAL_RPC_REQUEST_EVENT) {
|
|
719
|
+
void this.handleRpcRequest(client, decryptedEnvelope.data);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
489
722
|
this.dispatchCustomEvent(decryptedEnvelope.event, decryptedEnvelope.data, client);
|
|
490
723
|
} catch (error) {
|
|
491
724
|
this.notifyError(normalizeToError(error, "Failed to process incoming server message."));
|
|
@@ -500,6 +733,11 @@ var SecureServer = class {
|
|
|
500
733
|
this.sharedSecretBySocket.delete(client.socket);
|
|
501
734
|
this.encryptionKeyBySocket.delete(client.socket);
|
|
502
735
|
this.pendingPayloadsBySocket.delete(client.socket);
|
|
736
|
+
this.rejectPendingRpcRequests(
|
|
737
|
+
client.socket,
|
|
738
|
+
new Error(`Client ${client.id} disconnected before ACK response was received.`)
|
|
739
|
+
);
|
|
740
|
+
this.pendingRpcRequestsBySocket.delete(client.socket);
|
|
503
741
|
this.heartbeatStateBySocket.delete(client.socket);
|
|
504
742
|
const decodedReason = decodeCloseReason(reason);
|
|
505
743
|
for (const handler of this.disconnectHandlers) {
|
|
@@ -525,7 +763,17 @@ var SecureServer = class {
|
|
|
525
763
|
}
|
|
526
764
|
for (const handler of handlers) {
|
|
527
765
|
try {
|
|
528
|
-
handler(data, client);
|
|
766
|
+
const handlerResult = handler(data, client);
|
|
767
|
+
if (isPromiseLike(handlerResult)) {
|
|
768
|
+
void Promise.resolve(handlerResult).catch((error) => {
|
|
769
|
+
this.notifyError(
|
|
770
|
+
normalizeToError(
|
|
771
|
+
error,
|
|
772
|
+
`Server event handler failed for event ${event}.`
|
|
773
|
+
)
|
|
774
|
+
);
|
|
775
|
+
});
|
|
776
|
+
}
|
|
529
777
|
} catch (error) {
|
|
530
778
|
this.notifyError(
|
|
531
779
|
normalizeToError(
|
|
@@ -546,24 +794,138 @@ var SecureServer = class {
|
|
|
546
794
|
this.notifyError(normalizeToError(error, "Failed to send server payload."));
|
|
547
795
|
}
|
|
548
796
|
}
|
|
549
|
-
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
|
+
}
|
|
550
807
|
try {
|
|
551
|
-
|
|
808
|
+
const serializedEnvelope = await serializeEnvelope(envelope.event, envelope.data);
|
|
809
|
+
const encryptedPayload = encryptSerializedEnvelope(serializedEnvelope, encryptionKey);
|
|
810
|
+
socket.send(encryptedPayload);
|
|
811
|
+
} catch (error) {
|
|
812
|
+
const normalizedError = normalizeToError(error, "Failed to send encrypted server payload.");
|
|
813
|
+
this.notifyError(normalizedError);
|
|
814
|
+
throw normalizedError;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
sendRpcRequest(socket, event, data, timeoutMs) {
|
|
818
|
+
if (socket.readyState !== WebSocket__default.default.OPEN && socket.readyState !== WebSocket__default.default.CONNECTING) {
|
|
819
|
+
throw new Error("Client socket is not connected for ACK request.");
|
|
820
|
+
}
|
|
821
|
+
const pendingRequests = this.pendingRpcRequestsBySocket.get(socket) ?? /* @__PURE__ */ new Map();
|
|
822
|
+
this.pendingRpcRequestsBySocket.set(socket, pendingRequests);
|
|
823
|
+
const requestId = crypto.randomUUID();
|
|
824
|
+
return new Promise((resolve, reject) => {
|
|
825
|
+
const timeoutHandle = setTimeout(() => {
|
|
826
|
+
pendingRequests.delete(requestId);
|
|
827
|
+
reject(new Error(`ACK response timed out after ${timeoutMs}ms for event "${event}".`));
|
|
828
|
+
}, timeoutMs);
|
|
829
|
+
timeoutHandle.unref?.();
|
|
830
|
+
pendingRequests.set(requestId, {
|
|
831
|
+
resolve,
|
|
832
|
+
reject,
|
|
833
|
+
timeoutHandle
|
|
834
|
+
});
|
|
835
|
+
void this.sendOrQueuePayload(socket, {
|
|
836
|
+
event: INTERNAL_RPC_REQUEST_EVENT,
|
|
837
|
+
data: {
|
|
838
|
+
id: requestId,
|
|
839
|
+
event,
|
|
840
|
+
data
|
|
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
|
+
);
|
|
848
|
+
});
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
handleRpcResponse(socket, data) {
|
|
852
|
+
try {
|
|
853
|
+
const responsePayload = parseRpcResponsePayload(data);
|
|
854
|
+
const pendingRequests = this.pendingRpcRequestsBySocket.get(socket);
|
|
855
|
+
if (!pendingRequests) {
|
|
552
856
|
return;
|
|
553
857
|
}
|
|
554
|
-
const
|
|
555
|
-
if (!
|
|
556
|
-
|
|
858
|
+
const pendingRequest = pendingRequests.get(responsePayload.id);
|
|
859
|
+
if (!pendingRequest) {
|
|
860
|
+
return;
|
|
557
861
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
862
|
+
clearTimeout(pendingRequest.timeoutHandle);
|
|
863
|
+
pendingRequests.delete(responsePayload.id);
|
|
864
|
+
if (responsePayload.ok) {
|
|
865
|
+
pendingRequest.resolve(responsePayload.data);
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
pendingRequest.reject(
|
|
869
|
+
new Error(responsePayload.error ?? "ACK request failed without an error message.")
|
|
561
870
|
);
|
|
562
|
-
socket.send(encryptedPayload);
|
|
563
871
|
} catch (error) {
|
|
564
|
-
this.notifyError(normalizeToError(error, "Failed to
|
|
872
|
+
this.notifyError(normalizeToError(error, "Failed to process server ACK response."));
|
|
565
873
|
}
|
|
566
874
|
}
|
|
875
|
+
async handleRpcRequest(client, data) {
|
|
876
|
+
let rpcRequestPayload;
|
|
877
|
+
try {
|
|
878
|
+
rpcRequestPayload = parseRpcRequestPayload(data);
|
|
879
|
+
} catch (error) {
|
|
880
|
+
this.notifyError(normalizeToError(error, "Invalid server ACK request payload."));
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
try {
|
|
884
|
+
const ackResponse = await this.executeRpcRequestHandler(
|
|
885
|
+
rpcRequestPayload.event,
|
|
886
|
+
rpcRequestPayload.data,
|
|
887
|
+
client
|
|
888
|
+
);
|
|
889
|
+
await this.sendEncryptedEnvelope(client.socket, {
|
|
890
|
+
event: INTERNAL_RPC_RESPONSE_EVENT,
|
|
891
|
+
data: {
|
|
892
|
+
id: rpcRequestPayload.id,
|
|
893
|
+
ok: true,
|
|
894
|
+
data: ackResponse
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
} catch (error) {
|
|
898
|
+
const normalizedError = normalizeToError(error, "Server ACK request handler failed.");
|
|
899
|
+
await this.sendEncryptedEnvelope(client.socket, {
|
|
900
|
+
event: INTERNAL_RPC_RESPONSE_EVENT,
|
|
901
|
+
data: {
|
|
902
|
+
id: rpcRequestPayload.id,
|
|
903
|
+
ok: false,
|
|
904
|
+
error: normalizedError.message
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
this.notifyError(normalizedError);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
async executeRpcRequestHandler(event, data, client) {
|
|
911
|
+
const handlers = this.customEventHandlers.get(event);
|
|
912
|
+
if (!handlers || handlers.size === 0) {
|
|
913
|
+
throw new Error(`No handler is registered for ACK request event "${event}".`);
|
|
914
|
+
}
|
|
915
|
+
const firstHandler = handlers.values().next().value;
|
|
916
|
+
return Promise.resolve(firstHandler(data, client));
|
|
917
|
+
}
|
|
918
|
+
rejectPendingRpcRequests(socket, error) {
|
|
919
|
+
const pendingRequests = this.pendingRpcRequestsBySocket.get(socket);
|
|
920
|
+
if (!pendingRequests) {
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
for (const pendingRequest of pendingRequests.values()) {
|
|
924
|
+
clearTimeout(pendingRequest.timeoutHandle);
|
|
925
|
+
pendingRequest.reject(error);
|
|
926
|
+
}
|
|
927
|
+
pendingRequests.clear();
|
|
928
|
+
}
|
|
567
929
|
notifyConnection(client) {
|
|
568
930
|
for (const handler of this.connectionHandlers) {
|
|
569
931
|
try {
|
|
@@ -608,7 +970,7 @@ var SecureServer = class {
|
|
|
608
970
|
sendInternalHandshake(socket, localPublicKey) {
|
|
609
971
|
this.sendRaw(
|
|
610
972
|
socket,
|
|
611
|
-
|
|
973
|
+
serializePlainEnvelope(INTERNAL_HANDSHAKE_EVENT, {
|
|
612
974
|
publicKey: localPublicKey
|
|
613
975
|
})
|
|
614
976
|
);
|
|
@@ -641,23 +1003,23 @@ var SecureServer = class {
|
|
|
641
1003
|
sendOrQueuePayload(socket, envelope) {
|
|
642
1004
|
if (!this.isClientHandshakeReady(socket)) {
|
|
643
1005
|
this.queuePayload(socket, envelope);
|
|
644
|
-
return;
|
|
1006
|
+
return Promise.resolve();
|
|
645
1007
|
}
|
|
646
|
-
this.sendEncryptedEnvelope(socket, envelope);
|
|
1008
|
+
return this.sendEncryptedEnvelope(socket, envelope);
|
|
647
1009
|
}
|
|
648
1010
|
queuePayload(socket, envelope) {
|
|
649
1011
|
const pendingPayloads = this.pendingPayloadsBySocket.get(socket) ?? [];
|
|
650
1012
|
pendingPayloads.push(envelope);
|
|
651
1013
|
this.pendingPayloadsBySocket.set(socket, pendingPayloads);
|
|
652
1014
|
}
|
|
653
|
-
flushQueuedPayloads(socket) {
|
|
1015
|
+
async flushQueuedPayloads(socket) {
|
|
654
1016
|
const pendingPayloads = this.pendingPayloadsBySocket.get(socket);
|
|
655
1017
|
if (!pendingPayloads || pendingPayloads.length === 0) {
|
|
656
1018
|
return;
|
|
657
1019
|
}
|
|
658
1020
|
this.pendingPayloadsBySocket.delete(socket);
|
|
659
1021
|
for (const envelope of pendingPayloads) {
|
|
660
|
-
this.sendEncryptedEnvelope(socket, envelope);
|
|
1022
|
+
await this.sendEncryptedEnvelope(socket, envelope);
|
|
661
1023
|
}
|
|
662
1024
|
}
|
|
663
1025
|
createSecureServerClient(clientId, socket, request) {
|
|
@@ -665,6 +1027,24 @@ var SecureServer = class {
|
|
|
665
1027
|
id: clientId,
|
|
666
1028
|
socket,
|
|
667
1029
|
request,
|
|
1030
|
+
emit: (event, data, callbackOrOptions, maybeCallback) => {
|
|
1031
|
+
if (callbackOrOptions === void 0 && maybeCallback === void 0) {
|
|
1032
|
+
return this.emitTo(clientId, event, data);
|
|
1033
|
+
}
|
|
1034
|
+
if (typeof callbackOrOptions === "function") {
|
|
1035
|
+
return this.emitTo(clientId, event, data, callbackOrOptions);
|
|
1036
|
+
}
|
|
1037
|
+
if (maybeCallback) {
|
|
1038
|
+
return this.emitTo(
|
|
1039
|
+
clientId,
|
|
1040
|
+
event,
|
|
1041
|
+
data,
|
|
1042
|
+
callbackOrOptions ?? {},
|
|
1043
|
+
maybeCallback
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
return this.emitTo(clientId, event, data, callbackOrOptions ?? {});
|
|
1047
|
+
},
|
|
668
1048
|
join: (room) => this.joinClientToRoom(clientId, room),
|
|
669
1049
|
leave: (room) => this.leaveClientFromRoom(clientId, room),
|
|
670
1050
|
leaveAll: () => this.leaveClientFromAllRooms(clientId)
|
|
@@ -749,7 +1129,9 @@ var SecureServer = class {
|
|
|
749
1129
|
if (!client) {
|
|
750
1130
|
continue;
|
|
751
1131
|
}
|
|
752
|
-
this.sendOrQueuePayload(client.socket, envelope)
|
|
1132
|
+
void this.sendOrQueuePayload(client.socket, envelope).catch(() => {
|
|
1133
|
+
return void 0;
|
|
1134
|
+
});
|
|
753
1135
|
}
|
|
754
1136
|
}
|
|
755
1137
|
};
|
|
@@ -776,6 +1158,7 @@ var SecureClient = class {
|
|
|
776
1158
|
errorHandlers = /* @__PURE__ */ new Set();
|
|
777
1159
|
handshakeState = null;
|
|
778
1160
|
pendingPayloadQueue = [];
|
|
1161
|
+
pendingRpcRequests = /* @__PURE__ */ new Map();
|
|
779
1162
|
get readyState() {
|
|
780
1163
|
return this.socket?.readyState ?? null;
|
|
781
1164
|
}
|
|
@@ -884,7 +1267,8 @@ var SecureClient = class {
|
|
|
884
1267
|
}
|
|
885
1268
|
return this;
|
|
886
1269
|
}
|
|
887
|
-
emit(event, data) {
|
|
1270
|
+
emit(event, data, callbackOrOptions, maybeCallback) {
|
|
1271
|
+
const ackArgs = resolveAckArguments(callbackOrOptions, maybeCallback);
|
|
888
1272
|
try {
|
|
889
1273
|
if (isReservedEmitEvent(event)) {
|
|
890
1274
|
throw new Error(`The event "${event}" is reserved and cannot be emitted manually.`);
|
|
@@ -892,15 +1276,39 @@ var SecureClient = class {
|
|
|
892
1276
|
if (!this.socket || this.socket.readyState !== WebSocket__default.default.OPEN) {
|
|
893
1277
|
throw new Error("Client socket is not connected.");
|
|
894
1278
|
}
|
|
1279
|
+
if (ackArgs.expectsAck) {
|
|
1280
|
+
const ackPromise = this.sendRpcRequest(event, data, ackArgs.timeoutMs);
|
|
1281
|
+
if (ackArgs.callback) {
|
|
1282
|
+
ackPromise.then((response) => {
|
|
1283
|
+
ackArgs.callback?.(null, response);
|
|
1284
|
+
}).catch((error) => {
|
|
1285
|
+
ackArgs.callback?.(
|
|
1286
|
+
normalizeToError(error, `ACK callback failed for event "${event}".`)
|
|
1287
|
+
);
|
|
1288
|
+
});
|
|
1289
|
+
return true;
|
|
1290
|
+
}
|
|
1291
|
+
return ackPromise;
|
|
1292
|
+
}
|
|
895
1293
|
const envelope = { event, data };
|
|
896
1294
|
if (!this.isHandshakeReady()) {
|
|
897
1295
|
this.pendingPayloadQueue.push(envelope);
|
|
898
1296
|
return true;
|
|
899
1297
|
}
|
|
900
|
-
this.sendEncryptedEnvelope(envelope)
|
|
1298
|
+
void this.sendEncryptedEnvelope(envelope).catch(() => {
|
|
1299
|
+
return void 0;
|
|
1300
|
+
});
|
|
901
1301
|
return true;
|
|
902
1302
|
} catch (error) {
|
|
903
|
-
|
|
1303
|
+
const normalizedError = normalizeToError(error, "Failed to emit client event.");
|
|
1304
|
+
this.notifyError(normalizedError);
|
|
1305
|
+
if (ackArgs.callback) {
|
|
1306
|
+
ackArgs.callback(normalizedError);
|
|
1307
|
+
return false;
|
|
1308
|
+
}
|
|
1309
|
+
if (ackArgs.expectsAck) {
|
|
1310
|
+
return Promise.reject(normalizedError);
|
|
1311
|
+
}
|
|
904
1312
|
return false;
|
|
905
1313
|
}
|
|
906
1314
|
}
|
|
@@ -1042,6 +1450,14 @@ var SecureClient = class {
|
|
|
1042
1450
|
return;
|
|
1043
1451
|
}
|
|
1044
1452
|
const decryptedEnvelope = parseEnvelopeFromText(decryptedPayload);
|
|
1453
|
+
if (decryptedEnvelope.event === INTERNAL_RPC_RESPONSE_EVENT) {
|
|
1454
|
+
this.handleRpcResponse(decryptedEnvelope.data);
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
if (decryptedEnvelope.event === INTERNAL_RPC_REQUEST_EVENT) {
|
|
1458
|
+
void this.handleRpcRequest(decryptedEnvelope.data);
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1045
1461
|
this.dispatchCustomEvent(decryptedEnvelope.event, decryptedEnvelope.data);
|
|
1046
1462
|
} catch (error) {
|
|
1047
1463
|
this.notifyError(normalizeToError(error, "Failed to process incoming client message."));
|
|
@@ -1052,6 +1468,9 @@ var SecureClient = class {
|
|
|
1052
1468
|
this.socket = null;
|
|
1053
1469
|
this.handshakeState = null;
|
|
1054
1470
|
this.pendingPayloadQueue = [];
|
|
1471
|
+
this.rejectPendingRpcRequests(
|
|
1472
|
+
new Error("Client disconnected before ACK response was received.")
|
|
1473
|
+
);
|
|
1055
1474
|
const decodedReason = decodeCloseReason(reason);
|
|
1056
1475
|
for (const handler of this.disconnectHandlers) {
|
|
1057
1476
|
try {
|
|
@@ -1077,7 +1496,17 @@ var SecureClient = class {
|
|
|
1077
1496
|
}
|
|
1078
1497
|
for (const handler of handlers) {
|
|
1079
1498
|
try {
|
|
1080
|
-
handler(data);
|
|
1499
|
+
const handlerResult = handler(data);
|
|
1500
|
+
if (isPromiseLike(handlerResult)) {
|
|
1501
|
+
void Promise.resolve(handlerResult).catch((error) => {
|
|
1502
|
+
this.notifyError(
|
|
1503
|
+
normalizeToError(
|
|
1504
|
+
error,
|
|
1505
|
+
`Client event handler failed for event ${event}.`
|
|
1506
|
+
)
|
|
1507
|
+
);
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1081
1510
|
} catch (error) {
|
|
1082
1511
|
this.notifyError(
|
|
1083
1512
|
normalizeToError(error, `Client event handler failed for event ${event}.`)
|
|
@@ -1114,23 +1543,133 @@ var SecureClient = class {
|
|
|
1114
1543
|
}
|
|
1115
1544
|
}
|
|
1116
1545
|
}
|
|
1117
|
-
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
|
+
}
|
|
1118
1558
|
try {
|
|
1119
|
-
|
|
1120
|
-
|
|
1559
|
+
const serializedEnvelope = await serializeEnvelope(envelope.event, envelope.data);
|
|
1560
|
+
const encryptedPayload = encryptSerializedEnvelope(serializedEnvelope, encryptionKey);
|
|
1561
|
+
this.socket.send(encryptedPayload);
|
|
1562
|
+
} catch (error) {
|
|
1563
|
+
const normalizedError = normalizeToError(error, "Failed to send encrypted client payload.");
|
|
1564
|
+
this.notifyError(normalizedError);
|
|
1565
|
+
throw normalizedError;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
sendRpcRequest(event, data, timeoutMs) {
|
|
1569
|
+
if (!this.socket || this.socket.readyState !== WebSocket__default.default.OPEN) {
|
|
1570
|
+
throw new Error("Client socket is not connected for ACK request.");
|
|
1571
|
+
}
|
|
1572
|
+
const requestId = crypto.randomUUID();
|
|
1573
|
+
return new Promise((resolve, reject) => {
|
|
1574
|
+
const timeoutHandle = setTimeout(() => {
|
|
1575
|
+
this.pendingRpcRequests.delete(requestId);
|
|
1576
|
+
reject(new Error(`ACK response timed out after ${timeoutMs}ms for event "${event}".`));
|
|
1577
|
+
}, timeoutMs);
|
|
1578
|
+
timeoutHandle.unref?.();
|
|
1579
|
+
this.pendingRpcRequests.set(requestId, {
|
|
1580
|
+
resolve,
|
|
1581
|
+
reject,
|
|
1582
|
+
timeoutHandle
|
|
1583
|
+
});
|
|
1584
|
+
const rpcRequestEnvelope = {
|
|
1585
|
+
event: INTERNAL_RPC_REQUEST_EVENT,
|
|
1586
|
+
data: {
|
|
1587
|
+
id: requestId,
|
|
1588
|
+
event,
|
|
1589
|
+
data
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
if (!this.isHandshakeReady()) {
|
|
1593
|
+
this.pendingPayloadQueue.push(rpcRequestEnvelope);
|
|
1594
|
+
return;
|
|
1121
1595
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
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
|
+
});
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
handleRpcResponse(data) {
|
|
1606
|
+
try {
|
|
1607
|
+
const responsePayload = parseRpcResponsePayload(data);
|
|
1608
|
+
const pendingRequest = this.pendingRpcRequests.get(responsePayload.id);
|
|
1609
|
+
if (!pendingRequest) {
|
|
1610
|
+
return;
|
|
1125
1611
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1612
|
+
clearTimeout(pendingRequest.timeoutHandle);
|
|
1613
|
+
this.pendingRpcRequests.delete(responsePayload.id);
|
|
1614
|
+
if (responsePayload.ok) {
|
|
1615
|
+
pendingRequest.resolve(responsePayload.data);
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
pendingRequest.reject(
|
|
1619
|
+
new Error(responsePayload.error ?? "ACK request failed without an error message.")
|
|
1129
1620
|
);
|
|
1130
|
-
this.socket.send(encryptedPayload);
|
|
1131
1621
|
} catch (error) {
|
|
1132
|
-
this.notifyError(normalizeToError(error, "Failed to
|
|
1622
|
+
this.notifyError(normalizeToError(error, "Failed to process client ACK response."));
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
async handleRpcRequest(data) {
|
|
1626
|
+
let rpcRequestPayload;
|
|
1627
|
+
try {
|
|
1628
|
+
rpcRequestPayload = parseRpcRequestPayload(data);
|
|
1629
|
+
} catch (error) {
|
|
1630
|
+
this.notifyError(normalizeToError(error, "Invalid client ACK request payload."));
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
try {
|
|
1634
|
+
const ackResponse = await this.executeRpcRequestHandler(
|
|
1635
|
+
rpcRequestPayload.event,
|
|
1636
|
+
rpcRequestPayload.data
|
|
1637
|
+
);
|
|
1638
|
+
await this.sendEncryptedEnvelope({
|
|
1639
|
+
event: INTERNAL_RPC_RESPONSE_EVENT,
|
|
1640
|
+
data: {
|
|
1641
|
+
id: rpcRequestPayload.id,
|
|
1642
|
+
ok: true,
|
|
1643
|
+
data: ackResponse
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1646
|
+
} catch (error) {
|
|
1647
|
+
const normalizedError = normalizeToError(error, "Client ACK request handler failed.");
|
|
1648
|
+
await this.sendEncryptedEnvelope({
|
|
1649
|
+
event: INTERNAL_RPC_RESPONSE_EVENT,
|
|
1650
|
+
data: {
|
|
1651
|
+
id: rpcRequestPayload.id,
|
|
1652
|
+
ok: false,
|
|
1653
|
+
error: normalizedError.message
|
|
1654
|
+
}
|
|
1655
|
+
});
|
|
1656
|
+
this.notifyError(normalizedError);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
async executeRpcRequestHandler(event, data) {
|
|
1660
|
+
const handlers = this.customEventHandlers.get(event);
|
|
1661
|
+
if (!handlers || handlers.size === 0) {
|
|
1662
|
+
throw new Error(`No handler is registered for ACK request event "${event}".`);
|
|
1663
|
+
}
|
|
1664
|
+
const firstHandler = handlers.values().next().value;
|
|
1665
|
+
return Promise.resolve(firstHandler(data));
|
|
1666
|
+
}
|
|
1667
|
+
rejectPendingRpcRequests(error) {
|
|
1668
|
+
for (const pendingRequest of this.pendingRpcRequests.values()) {
|
|
1669
|
+
clearTimeout(pendingRequest.timeoutHandle);
|
|
1670
|
+
pendingRequest.reject(error);
|
|
1133
1671
|
}
|
|
1672
|
+
this.pendingRpcRequests.clear();
|
|
1134
1673
|
}
|
|
1135
1674
|
createClientHandshakeState() {
|
|
1136
1675
|
const { ecdh, localPublicKey } = createEphemeralHandshakeState();
|
|
@@ -1151,7 +1690,7 @@ var SecureClient = class {
|
|
|
1151
1690
|
throw new Error("Missing client handshake state.");
|
|
1152
1691
|
}
|
|
1153
1692
|
this.socket.send(
|
|
1154
|
-
|
|
1693
|
+
serializePlainEnvelope(INTERNAL_HANDSHAKE_EVENT, {
|
|
1155
1694
|
publicKey: this.handshakeState.localPublicKey
|
|
1156
1695
|
})
|
|
1157
1696
|
);
|
|
@@ -1173,7 +1712,7 @@ var SecureClient = class {
|
|
|
1173
1712
|
this.handshakeState.sharedSecret = sharedSecret;
|
|
1174
1713
|
this.handshakeState.encryptionKey = deriveEncryptionKey(sharedSecret);
|
|
1175
1714
|
this.handshakeState.isReady = true;
|
|
1176
|
-
this.flushPendingPayloadQueue();
|
|
1715
|
+
void this.flushPendingPayloadQueue();
|
|
1177
1716
|
this.notifyReady();
|
|
1178
1717
|
} catch (error) {
|
|
1179
1718
|
this.notifyError(normalizeToError(error, "Failed to complete client handshake."));
|
|
@@ -1182,14 +1721,14 @@ var SecureClient = class {
|
|
|
1182
1721
|
isHandshakeReady() {
|
|
1183
1722
|
return this.handshakeState?.isReady ?? false;
|
|
1184
1723
|
}
|
|
1185
|
-
flushPendingPayloadQueue() {
|
|
1724
|
+
async flushPendingPayloadQueue() {
|
|
1186
1725
|
if (!this.socket || this.socket.readyState !== WebSocket__default.default.OPEN || !this.isHandshakeReady()) {
|
|
1187
1726
|
return;
|
|
1188
1727
|
}
|
|
1189
1728
|
const pendingPayloads = this.pendingPayloadQueue;
|
|
1190
1729
|
this.pendingPayloadQueue = [];
|
|
1191
1730
|
for (const envelope of pendingPayloads) {
|
|
1192
|
-
this.sendEncryptedEnvelope(envelope);
|
|
1731
|
+
await this.sendEncryptedEnvelope(envelope);
|
|
1193
1732
|
}
|
|
1194
1733
|
}
|
|
1195
1734
|
};
|