@aegis-fluxion/core 0.3.0 → 0.4.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/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";
@@ -25,6 +27,7 @@ var DEFAULT_RECONNECT_INITIAL_DELAY_MS = 250;
25
27
  var DEFAULT_RECONNECT_MAX_DELAY_MS = 1e4;
26
28
  var DEFAULT_RECONNECT_FACTOR = 2;
27
29
  var DEFAULT_RECONNECT_JITTER_RATIO = 0.2;
30
+ var DEFAULT_RPC_TIMEOUT_MS = 5e3;
28
31
  function normalizeToError(error, fallbackMessage) {
29
32
  if (error instanceof Error) {
30
33
  return error;
@@ -87,7 +90,88 @@ function decodeCloseReason(reason) {
87
90
  return reason.toString("utf8");
88
91
  }
89
92
  function isReservedEmitEvent(event) {
90
- return event === INTERNAL_HANDSHAKE_EVENT || event === READY_EVENT;
93
+ return event === INTERNAL_HANDSHAKE_EVENT || event === INTERNAL_RPC_REQUEST_EVENT || event === INTERNAL_RPC_RESPONSE_EVENT || event === READY_EVENT;
94
+ }
95
+ function isPromiseLike(value) {
96
+ return typeof value === "object" && value !== null && "then" in value;
97
+ }
98
+ function normalizeRpcTimeout(timeoutMs) {
99
+ const resolvedTimeoutMs = timeoutMs ?? DEFAULT_RPC_TIMEOUT_MS;
100
+ if (!Number.isFinite(resolvedTimeoutMs) || resolvedTimeoutMs <= 0) {
101
+ throw new Error("ACK timeoutMs must be a positive number.");
102
+ }
103
+ return resolvedTimeoutMs;
104
+ }
105
+ function parseRpcRequestPayload(data) {
106
+ if (typeof data !== "object" || data === null) {
107
+ throw new Error("Invalid RPC request payload format.");
108
+ }
109
+ const payload = data;
110
+ if (typeof payload.id !== "string" || payload.id.trim().length === 0) {
111
+ throw new Error("RPC request payload must include a non-empty id.");
112
+ }
113
+ if (typeof payload.event !== "string" || payload.event.trim().length === 0) {
114
+ throw new Error("RPC request payload must include a non-empty event.");
115
+ }
116
+ return {
117
+ id: payload.id,
118
+ event: payload.event,
119
+ data: payload.data
120
+ };
121
+ }
122
+ function parseRpcResponsePayload(data) {
123
+ if (typeof data !== "object" || data === null) {
124
+ throw new Error("Invalid RPC response payload format.");
125
+ }
126
+ const payload = data;
127
+ if (typeof payload.id !== "string" || payload.id.trim().length === 0) {
128
+ throw new Error("RPC response payload must include a non-empty id.");
129
+ }
130
+ if (typeof payload.ok !== "boolean") {
131
+ throw new Error("RPC response payload must include a boolean ok field.");
132
+ }
133
+ if (payload.error !== void 0 && typeof payload.error !== "string") {
134
+ throw new Error("RPC response payload error must be a string when provided.");
135
+ }
136
+ const parsedPayload = {
137
+ id: payload.id,
138
+ ok: payload.ok,
139
+ data: payload.data
140
+ };
141
+ if (payload.error !== void 0) {
142
+ parsedPayload.error = payload.error;
143
+ }
144
+ return parsedPayload;
145
+ }
146
+ function resolveAckArguments(callbackOrOptions, maybeCallback) {
147
+ if (callbackOrOptions === void 0 && maybeCallback === void 0) {
148
+ return {
149
+ expectsAck: false,
150
+ timeoutMs: DEFAULT_RPC_TIMEOUT_MS
151
+ };
152
+ }
153
+ if (typeof callbackOrOptions === "function") {
154
+ if (maybeCallback !== void 0) {
155
+ throw new Error("ACK callback was provided more than once.");
156
+ }
157
+ return {
158
+ expectsAck: true,
159
+ callback: callbackOrOptions,
160
+ timeoutMs: DEFAULT_RPC_TIMEOUT_MS
161
+ };
162
+ }
163
+ const options = callbackOrOptions;
164
+ if (options !== void 0 && (typeof options !== "object" || options === null)) {
165
+ throw new Error("ACK options must be an object.");
166
+ }
167
+ if (maybeCallback !== void 0 && typeof maybeCallback !== "function") {
168
+ throw new Error("ACK callback must be a function.");
169
+ }
170
+ return {
171
+ ...maybeCallback ? { callback: maybeCallback } : {},
172
+ expectsAck: true,
173
+ timeoutMs: normalizeRpcTimeout(options?.timeoutMs)
174
+ };
91
175
  }
92
176
  function createEphemeralHandshakeState() {
93
177
  const ecdh = crypto.createECDH(HANDSHAKE_CURVE);
@@ -185,6 +269,7 @@ var SecureServer = class {
185
269
  sharedSecretBySocket = /* @__PURE__ */ new WeakMap();
186
270
  encryptionKeyBySocket = /* @__PURE__ */ new WeakMap();
187
271
  pendingPayloadsBySocket = /* @__PURE__ */ new WeakMap();
272
+ pendingRpcRequestsBySocket = /* @__PURE__ */ new WeakMap();
188
273
  heartbeatStateBySocket = /* @__PURE__ */ new WeakMap();
189
274
  roomMembersByName = /* @__PURE__ */ new Map();
190
275
  roomNamesByClientId = /* @__PURE__ */ new Map();
@@ -283,7 +368,8 @@ var SecureServer = class {
283
368
  }
284
369
  return this;
285
370
  }
286
- emitTo(clientId, event, data) {
371
+ emitTo(clientId, event, data, callbackOrOptions, maybeCallback) {
372
+ const ackArgs = resolveAckArguments(callbackOrOptions, maybeCallback);
287
373
  try {
288
374
  if (isReservedEmitEvent(event)) {
289
375
  throw new Error(`The event "${event}" is reserved and cannot be emitted manually.`);
@@ -292,10 +378,37 @@ var SecureServer = class {
292
378
  if (!client) {
293
379
  throw new Error(`Client with id ${clientId} was not found.`);
294
380
  }
295
- this.sendOrQueuePayload(client.socket, { event, data });
296
- return true;
381
+ if (!ackArgs.expectsAck) {
382
+ this.sendOrQueuePayload(client.socket, { event, data });
383
+ return true;
384
+ }
385
+ const ackPromise = this.sendRpcRequest(
386
+ client.socket,
387
+ event,
388
+ data,
389
+ ackArgs.timeoutMs
390
+ );
391
+ if (ackArgs.callback) {
392
+ ackPromise.then((response) => {
393
+ ackArgs.callback?.(null, response);
394
+ }).catch((error) => {
395
+ ackArgs.callback?.(
396
+ normalizeToError(error, `ACK callback failed for client ${client.id}.`)
397
+ );
398
+ });
399
+ return true;
400
+ }
401
+ return ackPromise;
297
402
  } catch (error) {
298
- this.notifyError(normalizeToError(error, "Failed to emit event to client."));
403
+ const normalizedError = normalizeToError(error, "Failed to emit event to client.");
404
+ this.notifyError(normalizedError);
405
+ if (ackArgs.callback) {
406
+ ackArgs.callback(normalizedError);
407
+ return false;
408
+ }
409
+ if (ackArgs.expectsAck) {
410
+ return Promise.reject(normalizedError);
411
+ }
299
412
  return false;
300
413
  }
301
414
  }
@@ -318,6 +431,10 @@ var SecureServer = class {
318
431
  try {
319
432
  this.stopHeartbeatLoop();
320
433
  for (const client of this.clientsById.values()) {
434
+ this.rejectPendingRpcRequests(
435
+ client.socket,
436
+ new Error("Server closed before ACK response was received.")
437
+ );
321
438
  if (client.socket.readyState === WebSocket__default.default.OPEN || client.socket.readyState === WebSocket__default.default.CONNECTING) {
322
439
  client.socket.close(code, reason);
323
440
  }
@@ -370,9 +487,14 @@ var SecureServer = class {
370
487
  lastPingAt: 0
371
488
  };
372
489
  if (heartbeatState.awaitingPong && now - heartbeatState.lastPingAt >= this.heartbeatConfig.timeoutMs) {
490
+ this.rejectPendingRpcRequests(
491
+ socket,
492
+ new Error(`Heartbeat timeout while waiting for client ${client.id} ACK response.`)
493
+ );
373
494
  this.sharedSecretBySocket.delete(socket);
374
495
  this.encryptionKeyBySocket.delete(socket);
375
496
  this.pendingPayloadsBySocket.delete(socket);
497
+ this.pendingRpcRequestsBySocket.delete(socket);
376
498
  this.handshakeStateBySocket.delete(socket);
377
499
  this.heartbeatStateBySocket.delete(socket);
378
500
  socket.terminate();
@@ -419,6 +541,7 @@ var SecureServer = class {
419
541
  this.clientIdBySocket.set(socket, clientId);
420
542
  this.handshakeStateBySocket.set(socket, handshakeState);
421
543
  this.pendingPayloadsBySocket.set(socket, []);
544
+ this.pendingRpcRequestsBySocket.set(socket, /* @__PURE__ */ new Map());
422
545
  this.heartbeatStateBySocket.set(socket, {
423
546
  awaitingPong: false,
424
547
  lastPingAt: 0
@@ -486,6 +609,14 @@ var SecureServer = class {
486
609
  return;
487
610
  }
488
611
  const decryptedEnvelope = parseEnvelopeFromText(decryptedPayload);
612
+ if (decryptedEnvelope.event === INTERNAL_RPC_RESPONSE_EVENT) {
613
+ this.handleRpcResponse(client.socket, decryptedEnvelope.data);
614
+ return;
615
+ }
616
+ if (decryptedEnvelope.event === INTERNAL_RPC_REQUEST_EVENT) {
617
+ void this.handleRpcRequest(client, decryptedEnvelope.data);
618
+ return;
619
+ }
489
620
  this.dispatchCustomEvent(decryptedEnvelope.event, decryptedEnvelope.data, client);
490
621
  } catch (error) {
491
622
  this.notifyError(normalizeToError(error, "Failed to process incoming server message."));
@@ -500,6 +631,11 @@ var SecureServer = class {
500
631
  this.sharedSecretBySocket.delete(client.socket);
501
632
  this.encryptionKeyBySocket.delete(client.socket);
502
633
  this.pendingPayloadsBySocket.delete(client.socket);
634
+ this.rejectPendingRpcRequests(
635
+ client.socket,
636
+ new Error(`Client ${client.id} disconnected before ACK response was received.`)
637
+ );
638
+ this.pendingRpcRequestsBySocket.delete(client.socket);
503
639
  this.heartbeatStateBySocket.delete(client.socket);
504
640
  const decodedReason = decodeCloseReason(reason);
505
641
  for (const handler of this.disconnectHandlers) {
@@ -525,7 +661,17 @@ var SecureServer = class {
525
661
  }
526
662
  for (const handler of handlers) {
527
663
  try {
528
- handler(data, client);
664
+ const handlerResult = handler(data, client);
665
+ if (isPromiseLike(handlerResult)) {
666
+ void Promise.resolve(handlerResult).catch((error) => {
667
+ this.notifyError(
668
+ normalizeToError(
669
+ error,
670
+ `Server event handler failed for event ${event}.`
671
+ )
672
+ );
673
+ });
674
+ }
529
675
  } catch (error) {
530
676
  this.notifyError(
531
677
  normalizeToError(
@@ -564,6 +710,112 @@ var SecureServer = class {
564
710
  this.notifyError(normalizeToError(error, "Failed to send encrypted server payload."));
565
711
  }
566
712
  }
713
+ sendRpcRequest(socket, event, data, timeoutMs) {
714
+ if (socket.readyState !== WebSocket__default.default.OPEN && socket.readyState !== WebSocket__default.default.CONNECTING) {
715
+ throw new Error("Client socket is not connected for ACK request.");
716
+ }
717
+ const pendingRequests = this.pendingRpcRequestsBySocket.get(socket) ?? /* @__PURE__ */ new Map();
718
+ this.pendingRpcRequestsBySocket.set(socket, pendingRequests);
719
+ const requestId = crypto.randomUUID();
720
+ return new Promise((resolve, reject) => {
721
+ const timeoutHandle = setTimeout(() => {
722
+ pendingRequests.delete(requestId);
723
+ reject(new Error(`ACK response timed out after ${timeoutMs}ms for event "${event}".`));
724
+ }, timeoutMs);
725
+ timeoutHandle.unref?.();
726
+ pendingRequests.set(requestId, {
727
+ resolve,
728
+ reject,
729
+ timeoutHandle
730
+ });
731
+ this.sendOrQueuePayload(socket, {
732
+ event: INTERNAL_RPC_REQUEST_EVENT,
733
+ data: {
734
+ id: requestId,
735
+ event,
736
+ data
737
+ }
738
+ });
739
+ });
740
+ }
741
+ handleRpcResponse(socket, data) {
742
+ try {
743
+ const responsePayload = parseRpcResponsePayload(data);
744
+ const pendingRequests = this.pendingRpcRequestsBySocket.get(socket);
745
+ if (!pendingRequests) {
746
+ return;
747
+ }
748
+ const pendingRequest = pendingRequests.get(responsePayload.id);
749
+ if (!pendingRequest) {
750
+ return;
751
+ }
752
+ clearTimeout(pendingRequest.timeoutHandle);
753
+ pendingRequests.delete(responsePayload.id);
754
+ if (responsePayload.ok) {
755
+ pendingRequest.resolve(responsePayload.data);
756
+ return;
757
+ }
758
+ pendingRequest.reject(
759
+ new Error(responsePayload.error ?? "ACK request failed without an error message.")
760
+ );
761
+ } catch (error) {
762
+ this.notifyError(normalizeToError(error, "Failed to process server ACK response."));
763
+ }
764
+ }
765
+ async handleRpcRequest(client, data) {
766
+ let rpcRequestPayload;
767
+ try {
768
+ rpcRequestPayload = parseRpcRequestPayload(data);
769
+ } catch (error) {
770
+ this.notifyError(normalizeToError(error, "Invalid server ACK request payload."));
771
+ return;
772
+ }
773
+ try {
774
+ const ackResponse = await this.executeRpcRequestHandler(
775
+ rpcRequestPayload.event,
776
+ rpcRequestPayload.data,
777
+ client
778
+ );
779
+ this.sendEncryptedEnvelope(client.socket, {
780
+ event: INTERNAL_RPC_RESPONSE_EVENT,
781
+ data: {
782
+ id: rpcRequestPayload.id,
783
+ ok: true,
784
+ data: ackResponse
785
+ }
786
+ });
787
+ } catch (error) {
788
+ const normalizedError = normalizeToError(error, "Server ACK request handler failed.");
789
+ this.sendEncryptedEnvelope(client.socket, {
790
+ event: INTERNAL_RPC_RESPONSE_EVENT,
791
+ data: {
792
+ id: rpcRequestPayload.id,
793
+ ok: false,
794
+ error: normalizedError.message
795
+ }
796
+ });
797
+ this.notifyError(normalizedError);
798
+ }
799
+ }
800
+ async executeRpcRequestHandler(event, data, client) {
801
+ const handlers = this.customEventHandlers.get(event);
802
+ if (!handlers || handlers.size === 0) {
803
+ throw new Error(`No handler is registered for ACK request event "${event}".`);
804
+ }
805
+ const firstHandler = handlers.values().next().value;
806
+ return Promise.resolve(firstHandler(data, client));
807
+ }
808
+ rejectPendingRpcRequests(socket, error) {
809
+ const pendingRequests = this.pendingRpcRequestsBySocket.get(socket);
810
+ if (!pendingRequests) {
811
+ return;
812
+ }
813
+ for (const pendingRequest of pendingRequests.values()) {
814
+ clearTimeout(pendingRequest.timeoutHandle);
815
+ pendingRequest.reject(error);
816
+ }
817
+ pendingRequests.clear();
818
+ }
567
819
  notifyConnection(client) {
568
820
  for (const handler of this.connectionHandlers) {
569
821
  try {
@@ -665,6 +917,24 @@ var SecureServer = class {
665
917
  id: clientId,
666
918
  socket,
667
919
  request,
920
+ emit: (event, data, callbackOrOptions, maybeCallback) => {
921
+ if (callbackOrOptions === void 0 && maybeCallback === void 0) {
922
+ return this.emitTo(clientId, event, data);
923
+ }
924
+ if (typeof callbackOrOptions === "function") {
925
+ return this.emitTo(clientId, event, data, callbackOrOptions);
926
+ }
927
+ if (maybeCallback) {
928
+ return this.emitTo(
929
+ clientId,
930
+ event,
931
+ data,
932
+ callbackOrOptions ?? {},
933
+ maybeCallback
934
+ );
935
+ }
936
+ return this.emitTo(clientId, event, data, callbackOrOptions ?? {});
937
+ },
668
938
  join: (room) => this.joinClientToRoom(clientId, room),
669
939
  leave: (room) => this.leaveClientFromRoom(clientId, room),
670
940
  leaveAll: () => this.leaveClientFromAllRooms(clientId)
@@ -776,6 +1046,7 @@ var SecureClient = class {
776
1046
  errorHandlers = /* @__PURE__ */ new Set();
777
1047
  handshakeState = null;
778
1048
  pendingPayloadQueue = [];
1049
+ pendingRpcRequests = /* @__PURE__ */ new Map();
779
1050
  get readyState() {
780
1051
  return this.socket?.readyState ?? null;
781
1052
  }
@@ -884,7 +1155,8 @@ var SecureClient = class {
884
1155
  }
885
1156
  return this;
886
1157
  }
887
- emit(event, data) {
1158
+ emit(event, data, callbackOrOptions, maybeCallback) {
1159
+ const ackArgs = resolveAckArguments(callbackOrOptions, maybeCallback);
888
1160
  try {
889
1161
  if (isReservedEmitEvent(event)) {
890
1162
  throw new Error(`The event "${event}" is reserved and cannot be emitted manually.`);
@@ -892,6 +1164,20 @@ var SecureClient = class {
892
1164
  if (!this.socket || this.socket.readyState !== WebSocket__default.default.OPEN) {
893
1165
  throw new Error("Client socket is not connected.");
894
1166
  }
1167
+ if (ackArgs.expectsAck) {
1168
+ const ackPromise = this.sendRpcRequest(event, data, ackArgs.timeoutMs);
1169
+ if (ackArgs.callback) {
1170
+ ackPromise.then((response) => {
1171
+ ackArgs.callback?.(null, response);
1172
+ }).catch((error) => {
1173
+ ackArgs.callback?.(
1174
+ normalizeToError(error, `ACK callback failed for event "${event}".`)
1175
+ );
1176
+ });
1177
+ return true;
1178
+ }
1179
+ return ackPromise;
1180
+ }
895
1181
  const envelope = { event, data };
896
1182
  if (!this.isHandshakeReady()) {
897
1183
  this.pendingPayloadQueue.push(envelope);
@@ -900,7 +1186,15 @@ var SecureClient = class {
900
1186
  this.sendEncryptedEnvelope(envelope);
901
1187
  return true;
902
1188
  } catch (error) {
903
- this.notifyError(normalizeToError(error, "Failed to emit client event."));
1189
+ const normalizedError = normalizeToError(error, "Failed to emit client event.");
1190
+ this.notifyError(normalizedError);
1191
+ if (ackArgs.callback) {
1192
+ ackArgs.callback(normalizedError);
1193
+ return false;
1194
+ }
1195
+ if (ackArgs.expectsAck) {
1196
+ return Promise.reject(normalizedError);
1197
+ }
904
1198
  return false;
905
1199
  }
906
1200
  }
@@ -1042,6 +1336,14 @@ var SecureClient = class {
1042
1336
  return;
1043
1337
  }
1044
1338
  const decryptedEnvelope = parseEnvelopeFromText(decryptedPayload);
1339
+ if (decryptedEnvelope.event === INTERNAL_RPC_RESPONSE_EVENT) {
1340
+ this.handleRpcResponse(decryptedEnvelope.data);
1341
+ return;
1342
+ }
1343
+ if (decryptedEnvelope.event === INTERNAL_RPC_REQUEST_EVENT) {
1344
+ void this.handleRpcRequest(decryptedEnvelope.data);
1345
+ return;
1346
+ }
1045
1347
  this.dispatchCustomEvent(decryptedEnvelope.event, decryptedEnvelope.data);
1046
1348
  } catch (error) {
1047
1349
  this.notifyError(normalizeToError(error, "Failed to process incoming client message."));
@@ -1052,6 +1354,9 @@ var SecureClient = class {
1052
1354
  this.socket = null;
1053
1355
  this.handshakeState = null;
1054
1356
  this.pendingPayloadQueue = [];
1357
+ this.rejectPendingRpcRequests(
1358
+ new Error("Client disconnected before ACK response was received.")
1359
+ );
1055
1360
  const decodedReason = decodeCloseReason(reason);
1056
1361
  for (const handler of this.disconnectHandlers) {
1057
1362
  try {
@@ -1077,7 +1382,17 @@ var SecureClient = class {
1077
1382
  }
1078
1383
  for (const handler of handlers) {
1079
1384
  try {
1080
- handler(data);
1385
+ const handlerResult = handler(data);
1386
+ if (isPromiseLike(handlerResult)) {
1387
+ void Promise.resolve(handlerResult).catch((error) => {
1388
+ this.notifyError(
1389
+ normalizeToError(
1390
+ error,
1391
+ `Client event handler failed for event ${event}.`
1392
+ )
1393
+ );
1394
+ });
1395
+ }
1081
1396
  } catch (error) {
1082
1397
  this.notifyError(
1083
1398
  normalizeToError(error, `Client event handler failed for event ${event}.`)
@@ -1132,6 +1447,106 @@ var SecureClient = class {
1132
1447
  this.notifyError(normalizeToError(error, "Failed to send encrypted client payload."));
1133
1448
  }
1134
1449
  }
1450
+ sendRpcRequest(event, data, timeoutMs) {
1451
+ if (!this.socket || this.socket.readyState !== WebSocket__default.default.OPEN) {
1452
+ throw new Error("Client socket is not connected for ACK request.");
1453
+ }
1454
+ const requestId = crypto.randomUUID();
1455
+ return new Promise((resolve, reject) => {
1456
+ const timeoutHandle = setTimeout(() => {
1457
+ this.pendingRpcRequests.delete(requestId);
1458
+ reject(new Error(`ACK response timed out after ${timeoutMs}ms for event "${event}".`));
1459
+ }, timeoutMs);
1460
+ timeoutHandle.unref?.();
1461
+ this.pendingRpcRequests.set(requestId, {
1462
+ resolve,
1463
+ reject,
1464
+ timeoutHandle
1465
+ });
1466
+ const rpcRequestEnvelope = {
1467
+ event: INTERNAL_RPC_REQUEST_EVENT,
1468
+ data: {
1469
+ id: requestId,
1470
+ event,
1471
+ data
1472
+ }
1473
+ };
1474
+ if (!this.isHandshakeReady()) {
1475
+ this.pendingPayloadQueue.push(rpcRequestEnvelope);
1476
+ return;
1477
+ }
1478
+ this.sendEncryptedEnvelope(rpcRequestEnvelope);
1479
+ });
1480
+ }
1481
+ handleRpcResponse(data) {
1482
+ try {
1483
+ const responsePayload = parseRpcResponsePayload(data);
1484
+ const pendingRequest = this.pendingRpcRequests.get(responsePayload.id);
1485
+ if (!pendingRequest) {
1486
+ return;
1487
+ }
1488
+ clearTimeout(pendingRequest.timeoutHandle);
1489
+ this.pendingRpcRequests.delete(responsePayload.id);
1490
+ if (responsePayload.ok) {
1491
+ pendingRequest.resolve(responsePayload.data);
1492
+ return;
1493
+ }
1494
+ pendingRequest.reject(
1495
+ new Error(responsePayload.error ?? "ACK request failed without an error message.")
1496
+ );
1497
+ } catch (error) {
1498
+ this.notifyError(normalizeToError(error, "Failed to process client ACK response."));
1499
+ }
1500
+ }
1501
+ async handleRpcRequest(data) {
1502
+ let rpcRequestPayload;
1503
+ try {
1504
+ rpcRequestPayload = parseRpcRequestPayload(data);
1505
+ } catch (error) {
1506
+ this.notifyError(normalizeToError(error, "Invalid client ACK request payload."));
1507
+ return;
1508
+ }
1509
+ try {
1510
+ const ackResponse = await this.executeRpcRequestHandler(
1511
+ rpcRequestPayload.event,
1512
+ rpcRequestPayload.data
1513
+ );
1514
+ this.sendEncryptedEnvelope({
1515
+ event: INTERNAL_RPC_RESPONSE_EVENT,
1516
+ data: {
1517
+ id: rpcRequestPayload.id,
1518
+ ok: true,
1519
+ data: ackResponse
1520
+ }
1521
+ });
1522
+ } catch (error) {
1523
+ const normalizedError = normalizeToError(error, "Client ACK request handler failed.");
1524
+ this.sendEncryptedEnvelope({
1525
+ event: INTERNAL_RPC_RESPONSE_EVENT,
1526
+ data: {
1527
+ id: rpcRequestPayload.id,
1528
+ ok: false,
1529
+ error: normalizedError.message
1530
+ }
1531
+ });
1532
+ this.notifyError(normalizedError);
1533
+ }
1534
+ }
1535
+ async executeRpcRequestHandler(event, data) {
1536
+ const handlers = this.customEventHandlers.get(event);
1537
+ if (!handlers || handlers.size === 0) {
1538
+ throw new Error(`No handler is registered for ACK request event "${event}".`);
1539
+ }
1540
+ const firstHandler = handlers.values().next().value;
1541
+ return Promise.resolve(firstHandler(data));
1542
+ }
1543
+ rejectPendingRpcRequests(error) {
1544
+ for (const pendingRequest of this.pendingRpcRequests.values()) {
1545
+ clearTimeout(pendingRequest.timeoutHandle);
1546
+ pendingRequest.reject(error);
1547
+ }
1548
+ this.pendingRpcRequests.clear();
1549
+ }
1135
1550
  createClientHandshakeState() {
1136
1551
  const { ecdh, localPublicKey } = createEphemeralHandshakeState();
1137
1552
  return {