@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/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 serializeEnvelope(event, data) {
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
- this.sendOrQueuePayload(client.socket, { event, data });
296
- return true;
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
- this.notifyError(normalizeToError(error, "Failed to emit event to client."));
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
- if (socket.readyState !== WebSocket__default.default.OPEN) {
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 encryptionKey = this.encryptionKeyBySocket.get(socket);
555
- if (!encryptionKey) {
556
- throw new Error("Missing encryption key for connected socket.");
858
+ const pendingRequest = pendingRequests.get(responsePayload.id);
859
+ if (!pendingRequest) {
860
+ return;
557
861
  }
558
- const encryptedPayload = encryptSerializedEnvelope(
559
- serializeEnvelope(envelope.event, envelope.data),
560
- encryptionKey
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 send encrypted server payload."));
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
- serializeEnvelope(INTERNAL_HANDSHAKE_EVENT, {
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
- this.notifyError(normalizeToError(error, "Failed to emit client event."));
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
- if (!this.socket || this.socket.readyState !== WebSocket__default.default.OPEN) {
1120
- throw new Error("Client socket is not connected.");
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
- const encryptionKey = this.handshakeState?.encryptionKey;
1123
- if (!encryptionKey) {
1124
- throw new Error("Missing encryption key for client payload encryption.");
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
- const encryptedPayload = encryptSerializedEnvelope(
1127
- serializeEnvelope(envelope.event, envelope.data),
1128
- encryptionKey
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 send encrypted client payload."));
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
- serializeEnvelope(INTERNAL_HANDSHAKE_EVENT, {
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
  };