@replit/river 0.207.1 → 0.207.3

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.
Files changed (56) hide show
  1. package/dist/adapter-f2b6e211.d.ts +46 -0
  2. package/dist/{chunk-7LMZNSVC.js → chunk-B7REV3ZV.js} +6 -5
  3. package/dist/{chunk-7LMZNSVC.js.map → chunk-B7REV3ZV.js.map} +1 -1
  4. package/dist/{chunk-QMAVXV4Z.js → chunk-BO7MFCO6.js} +1136 -132
  5. package/dist/chunk-BO7MFCO6.js.map +1 -0
  6. package/dist/{chunk-BCCZA7SX.js → chunk-QGPYCXV4.js} +2 -2
  7. package/dist/{chunk-BCCZA7SX.js.map → chunk-QGPYCXV4.js.map} +1 -1
  8. package/dist/codec/index.cjs +157 -23
  9. package/dist/codec/index.cjs.map +1 -1
  10. package/dist/codec/index.d.cts +5 -1
  11. package/dist/codec/index.d.ts +5 -1
  12. package/dist/codec/index.js +6 -20
  13. package/dist/codec/index.js.map +1 -1
  14. package/dist/{connection-933c87b2.d.ts → connection-06d72f2e.d.ts} +3 -2
  15. package/dist/index-02554794.d.ts +37 -0
  16. package/dist/logging/index.d.cts +2 -1
  17. package/dist/logging/index.d.ts +2 -1
  18. package/dist/{message-ffacb98a.d.ts → message-01c3e85a.d.ts} +1 -35
  19. package/dist/router/index.cjs +1 -1
  20. package/dist/router/index.cjs.map +1 -1
  21. package/dist/router/index.d.cts +6 -5
  22. package/dist/router/index.d.ts +6 -5
  23. package/dist/router/index.js +1 -1
  24. package/dist/{services-4cd29829.d.ts → services-87887bc5.d.ts} +16 -11
  25. package/dist/testUtil/index.cjs +992 -829
  26. package/dist/testUtil/index.cjs.map +1 -1
  27. package/dist/testUtil/index.d.cts +4 -3
  28. package/dist/testUtil/index.d.ts +4 -3
  29. package/dist/testUtil/index.js +18 -13
  30. package/dist/testUtil/index.js.map +1 -1
  31. package/dist/transport/impls/ws/client.cjs +293 -193
  32. package/dist/transport/impls/ws/client.cjs.map +1 -1
  33. package/dist/transport/impls/ws/client.d.cts +5 -4
  34. package/dist/transport/impls/ws/client.d.ts +5 -4
  35. package/dist/transport/impls/ws/client.js +5 -7
  36. package/dist/transport/impls/ws/client.js.map +1 -1
  37. package/dist/transport/impls/ws/server.cjs +230 -117
  38. package/dist/transport/impls/ws/server.cjs.map +1 -1
  39. package/dist/transport/impls/ws/server.d.cts +5 -4
  40. package/dist/transport/impls/ws/server.d.ts +5 -4
  41. package/dist/transport/impls/ws/server.js +5 -7
  42. package/dist/transport/impls/ws/server.js.map +1 -1
  43. package/dist/transport/index.cjs +408 -259
  44. package/dist/transport/index.cjs.map +1 -1
  45. package/dist/transport/index.d.cts +7 -6
  46. package/dist/transport/index.d.ts +7 -6
  47. package/dist/transport/index.js +4 -9
  48. package/package.json +1 -1
  49. package/dist/chunk-AJGIY2UB.js +0 -56
  50. package/dist/chunk-AJGIY2UB.js.map +0 -1
  51. package/dist/chunk-CRD3HDVN.js +0 -438
  52. package/dist/chunk-CRD3HDVN.js.map +0 -1
  53. package/dist/chunk-I27WBSMZ.js +0 -377
  54. package/dist/chunk-I27WBSMZ.js.map +0 -1
  55. package/dist/chunk-QMAVXV4Z.js.map +0 -1
  56. package/dist/types-3e5768ec.d.ts +0 -20
@@ -235,23 +235,20 @@ var NaiveJsonCodec = {
235
235
  );
236
236
  },
237
237
  fromBuffer: (buff) => {
238
- try {
239
- const parsed = JSON.parse(
240
- decoder.decode(buff),
241
- function reviver(_key, val) {
242
- if (val?.$t) {
243
- return base64ToUint8Array(val.$t);
244
- } else {
245
- return val;
246
- }
238
+ const parsed = JSON.parse(
239
+ decoder.decode(buff),
240
+ function reviver(_key, val) {
241
+ if (val?.$t) {
242
+ return base64ToUint8Array(val.$t);
243
+ } else {
244
+ return val;
247
245
  }
248
- );
249
- if (typeof parsed === "object")
250
- return parsed;
251
- return null;
252
- } catch {
253
- return null;
246
+ }
247
+ );
248
+ if (typeof parsed !== "object" || parsed === null) {
249
+ throw new Error("unpacked msg is not an object");
254
250
  }
251
+ return parsed;
255
252
  }
256
253
  };
257
254
 
@@ -281,7 +278,6 @@ var defaultServerTransportOptions = {
281
278
  };
282
279
 
283
280
  // transport/sessionStateMachine/common.ts
284
- var import_value = require("@sinclair/typebox/value");
285
281
  var ERR_CONSUMED = `session state has been consumed and is no longer valid`;
286
282
  var StateMachineState = class {
287
283
  /*
@@ -341,34 +337,16 @@ var StateMachineState = class {
341
337
  var CommonSession = class extends StateMachineState {
342
338
  from;
343
339
  options;
340
+ codec;
344
341
  tracer;
345
342
  log;
346
- constructor({ from, options, log, tracer }) {
343
+ constructor({ from, options, log, tracer, codec }) {
347
344
  super();
348
345
  this.from = from;
349
346
  this.options = options;
350
347
  this.log = log;
351
348
  this.tracer = tracer;
352
- }
353
- parseMsg(msg) {
354
- const parsedMsg = this.options.codec.fromBuffer(msg);
355
- if (parsedMsg === null) {
356
- this.log?.error(
357
- `received malformed msg: ${Buffer.from(msg).toString("base64")}`,
358
- this.loggingMetadata
359
- );
360
- return null;
361
- }
362
- if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
363
- this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
364
- ...this.loggingMetadata,
365
- validationErrors: [
366
- ...import_value.Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
367
- ]
368
- });
369
- return null;
370
- }
371
- return parsedMsg;
349
+ this.codec = codec;
372
350
  }
373
351
  };
374
352
  var IdentifiedSession = class extends CommonSession {
@@ -428,9 +406,6 @@ var IdentifiedSession = class extends CommonSession {
428
406
  return metadata;
429
407
  }
430
408
  constructMsg(partialMsg) {
431
- if (this._isConsumed) {
432
- throw new Error(ERR_CONSUMED);
433
- }
434
409
  const msg = {
435
410
  ...partialMsg,
436
411
  id: generateId(),
@@ -448,7 +423,10 @@ var IdentifiedSession = class extends CommonSession {
448
423
  send(msg) {
449
424
  const constructedMsg = this.constructMsg(msg);
450
425
  this.sendBuffer.push(constructedMsg);
451
- return constructedMsg.id;
426
+ return {
427
+ ok: true,
428
+ value: constructedMsg.id
429
+ };
452
430
  }
453
431
  _handleStateExit() {
454
432
  }
@@ -480,6 +458,23 @@ var IdentifiedSessionWithGracePeriod = class extends IdentifiedSession {
480
458
  super._handleClose();
481
459
  }
482
460
  };
461
+ function sendMessage(conn, codec, msg) {
462
+ const buff = codec.toBuffer(msg);
463
+ if (!buff.ok) {
464
+ return buff;
465
+ }
466
+ const sent = conn.send(buff.value);
467
+ if (!sent) {
468
+ return {
469
+ ok: false,
470
+ reason: "failed to send message"
471
+ };
472
+ }
473
+ return {
474
+ ok: true,
475
+ value: msg.id
476
+ };
477
+ }
483
478
 
484
479
  // transport/sessionStateMachine/SessionConnecting.ts
485
480
  var SessionConnecting = class extends IdentifiedSessionWithGracePeriod {
@@ -532,8 +527,8 @@ var SessionConnecting = class extends IdentifiedSessionWithGracePeriod {
532
527
  }
533
528
  }
534
529
  _handleClose() {
535
- this.bestEffortClose();
536
530
  super._handleClose();
531
+ this.bestEffortClose();
537
532
  }
538
533
  };
539
534
 
@@ -560,7 +555,7 @@ function coerceErrorString(err) {
560
555
  }
561
556
 
562
557
  // package.json
563
- var version = "0.207.1";
558
+ var version = "0.207.3";
564
559
 
565
560
  // tracing/index.ts
566
561
  function getPropagationContext(ctx) {
@@ -632,18 +627,18 @@ var SessionWaitingForHandshake = class extends CommonSession {
632
627
  };
633
628
  }
634
629
  onHandshakeData = (msg) => {
635
- const parsedMsg = this.parseMsg(msg);
636
- if (parsedMsg === null) {
630
+ const parsedMsgRes = this.codec.fromBuffer(msg);
631
+ if (!parsedMsgRes.ok) {
637
632
  this.listeners.onInvalidHandshake(
638
- "could not parse message",
633
+ `could not parse handshake message: ${parsedMsgRes.reason}`,
639
634
  "MALFORMED_HANDSHAKE"
640
635
  );
641
636
  return;
642
637
  }
643
- this.listeners.onHandshake(parsedMsg);
638
+ this.listeners.onHandshake(parsedMsgRes.value);
644
639
  };
645
640
  sendHandshake(msg) {
646
- return this.conn.send(this.options.codec.toBuffer(msg));
641
+ return sendMessage(this.conn, this.codec, msg);
647
642
  }
648
643
  _handleStateExit() {
649
644
  this.conn.removeDataListener(this.onHandshakeData);
@@ -681,18 +676,18 @@ var SessionHandshaking = class extends IdentifiedSessionWithGracePeriod {
681
676
  };
682
677
  }
683
678
  onHandshakeData = (msg) => {
684
- const parsedMsg = this.parseMsg(msg);
685
- if (parsedMsg === null) {
679
+ const parsedMsgRes = this.codec.fromBuffer(msg);
680
+ if (!parsedMsgRes.ok) {
686
681
  this.listeners.onInvalidHandshake(
687
- "could not parse message",
682
+ `could not parse handshake message: ${parsedMsgRes.reason}`,
688
683
  "MALFORMED_HANDSHAKE"
689
684
  );
690
685
  return;
691
686
  }
692
- this.listeners.onHandshake(parsedMsg);
687
+ this.listeners.onHandshake(parsedMsgRes.value);
693
688
  };
694
689
  sendHandshake(msg) {
695
- return this.conn.send(this.options.codec.toBuffer(msg));
690
+ return sendMessage(this.conn, this.codec, msg);
696
691
  }
697
692
  _handleStateExit() {
698
693
  super._handleStateExit();
@@ -717,12 +712,15 @@ var SessionConnected = class extends IdentifiedSession {
717
712
  conn;
718
713
  listeners;
719
714
  heartbeatHandle;
720
- heartbeatMisses = 0;
721
- isActivelyHeartbeating;
715
+ heartbeatMissTimeout;
716
+ isActivelyHeartbeating = false;
722
717
  updateBookkeeping(ack, seq) {
723
718
  this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
724
719
  this.ack = seq + 1;
725
- this.heartbeatMisses = 0;
720
+ if (this.heartbeatMissTimeout) {
721
+ clearTimeout(this.heartbeatMissTimeout);
722
+ }
723
+ this.startMissingHeartbeatTimeout();
726
724
  }
727
725
  assertSendOrdering(constructedMsg) {
728
726
  if (constructedMsg.seq > this.seqSent + 1) {
@@ -739,9 +737,13 @@ var SessionConnected = class extends IdentifiedSession {
739
737
  const constructedMsg = this.constructMsg(msg);
740
738
  this.assertSendOrdering(constructedMsg);
741
739
  this.sendBuffer.push(constructedMsg);
742
- this.conn.send(this.options.codec.toBuffer(constructedMsg));
740
+ const res = sendMessage(this.conn, this.codec, constructedMsg);
741
+ if (!res.ok) {
742
+ this.listeners.onMessageSendFailure(constructedMsg, res.reason);
743
+ return res;
744
+ }
743
745
  this.seqSent = constructedMsg.seq;
744
- return constructedMsg.id;
746
+ return res;
745
747
  }
746
748
  constructor(props) {
747
749
  super(props);
@@ -750,6 +752,8 @@ var SessionConnected = class extends IdentifiedSession {
750
752
  this.conn.addDataListener(this.onMessageData);
751
753
  this.conn.addCloseListener(this.listeners.onConnectionClosed);
752
754
  this.conn.addErrorListener(this.listeners.onConnectionErrored);
755
+ }
756
+ sendBufferedMessages() {
753
757
  if (this.sendBuffer.length > 0) {
754
758
  this.log?.info(
755
759
  `sending ${this.sendBuffer.length} buffered messages, starting at seq ${this.nextSeq()}`,
@@ -757,30 +761,15 @@ var SessionConnected = class extends IdentifiedSession {
757
761
  );
758
762
  for (const msg of this.sendBuffer) {
759
763
  this.assertSendOrdering(msg);
760
- this.conn.send(this.options.codec.toBuffer(msg));
764
+ const res = sendMessage(this.conn, this.codec, msg);
765
+ if (!res.ok) {
766
+ this.listeners.onMessageSendFailure(msg, res.reason);
767
+ return res;
768
+ }
761
769
  this.seqSent = msg.seq;
762
770
  }
763
771
  }
764
- this.isActivelyHeartbeating = false;
765
- this.heartbeatHandle = setInterval(() => {
766
- const misses = this.heartbeatMisses;
767
- const missDuration = misses * this.options.heartbeatIntervalMs;
768
- if (misses >= this.options.heartbeatsUntilDead) {
769
- this.log?.info(
770
- `closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
771
- this.loggingMetadata
772
- );
773
- this.telemetry.span.addEvent("closing connection due to inactivity");
774
- this.conn.close();
775
- clearInterval(this.heartbeatHandle);
776
- this.heartbeatHandle = void 0;
777
- return;
778
- }
779
- if (this.isActivelyHeartbeating) {
780
- this.sendHeartbeat();
781
- }
782
- this.heartbeatMisses++;
783
- }, this.options.heartbeatIntervalMs);
772
+ return { ok: true, value: void 0 };
784
773
  }
785
774
  get loggingMetadata() {
786
775
  return {
@@ -788,31 +777,46 @@ var SessionConnected = class extends IdentifiedSession {
788
777
  ...this.conn.loggingMetadata
789
778
  };
790
779
  }
780
+ startMissingHeartbeatTimeout() {
781
+ const maxMisses = this.options.heartbeatsUntilDead;
782
+ const missDuration = maxMisses * this.options.heartbeatIntervalMs;
783
+ this.heartbeatMissTimeout = setTimeout(() => {
784
+ this.log?.info(
785
+ `closing connection to ${this.to} due to inactivity (missed ${maxMisses} heartbeats which is ${missDuration}ms)`,
786
+ this.loggingMetadata
787
+ );
788
+ this.telemetry.span.addEvent(
789
+ "closing connection due to missing heartbeat"
790
+ );
791
+ this.conn.close();
792
+ }, missDuration);
793
+ }
791
794
  startActiveHeartbeat() {
792
795
  this.isActivelyHeartbeating = true;
796
+ this.heartbeatHandle = setInterval(() => {
797
+ this.sendHeartbeat();
798
+ }, this.options.heartbeatIntervalMs);
793
799
  }
794
800
  sendHeartbeat() {
795
801
  this.log?.debug("sending heartbeat", this.loggingMetadata);
796
- this.send({
802
+ const heartbeat = {
797
803
  streamId: "heartbeat",
798
804
  controlFlags: 1 /* AckBit */,
799
805
  payload: {
800
806
  type: "ACK"
801
807
  }
802
- });
803
- }
804
- closeConnection() {
805
- this.conn.removeDataListener(this.onMessageData);
806
- this.conn.removeCloseListener(this.listeners.onConnectionClosed);
807
- this.conn.removeErrorListener(this.listeners.onConnectionErrored);
808
- this.conn.close();
808
+ };
809
+ this.send(heartbeat);
809
810
  }
810
811
  onMessageData = (msg) => {
811
- const parsedMsg = this.parseMsg(msg);
812
- if (parsedMsg === null) {
813
- this.listeners.onInvalidMessage("could not parse message");
812
+ const parsedMsgRes = this.codec.fromBuffer(msg);
813
+ if (!parsedMsgRes.ok) {
814
+ this.listeners.onInvalidMessage(
815
+ `could not parse message: ${parsedMsgRes.reason}`
816
+ );
814
817
  return;
815
818
  }
819
+ const parsedMsg = parsedMsgRes.value;
816
820
  if (parsedMsg.seq !== this.ack) {
817
821
  if (parsedMsg.seq < this.ack) {
818
822
  this.log?.debug(
@@ -833,7 +837,7 @@ var SessionConnected = class extends IdentifiedSession {
833
837
  code: import_api2.SpanStatusCode.ERROR,
834
838
  message: reason
835
839
  });
836
- this.closeConnection();
840
+ this.conn.close();
837
841
  }
838
842
  return;
839
843
  }
@@ -851,9 +855,7 @@ var SessionConnected = class extends IdentifiedSession {
851
855
  transportMessage: parsedMsg
852
856
  });
853
857
  if (!this.isActivelyHeartbeating) {
854
- void Promise.resolve().then(() => {
855
- this.sendHeartbeat();
856
- });
858
+ this.sendHeartbeat();
857
859
  }
858
860
  };
859
861
  _handleStateExit() {
@@ -865,6 +867,10 @@ var SessionConnected = class extends IdentifiedSession {
865
867
  clearInterval(this.heartbeatHandle);
866
868
  this.heartbeatHandle = void 0;
867
869
  }
870
+ if (this.heartbeatMissTimeout) {
871
+ clearTimeout(this.heartbeatMissTimeout);
872
+ this.heartbeatMissTimeout = void 0;
873
+ }
868
874
  }
869
875
  _handleClose() {
870
876
  super._handleClose();
@@ -896,485 +902,108 @@ var SessionBackingOff = class extends IdentifiedSessionWithGracePeriod {
896
902
  }
897
903
  };
898
904
 
899
- // transport/sessionStateMachine/transitions.ts
900
- function inheritSharedSession(session) {
901
- return {
902
- id: session.id,
903
- from: session.from,
904
- to: session.to,
905
- seq: session.seq,
906
- ack: session.ack,
907
- seqSent: session.seqSent,
908
- sendBuffer: session.sendBuffer,
909
- telemetry: session.telemetry,
910
- options: session.options,
911
- log: session.log,
912
- tracer: session.tracer,
913
- protocolVersion: session.protocolVersion
914
- };
915
- }
916
- function inheritSharedSessionWithGrace(session) {
917
- return {
918
- ...inheritSharedSession(session),
919
- graceExpiryTime: session.graceExpiryTime
905
+ // codec/adapter.ts
906
+ var import_value3 = require("@sinclair/typebox/value");
907
+
908
+ // logging/log.ts
909
+ var import_api3 = require("@opentelemetry/api");
910
+ var LoggingLevels = {
911
+ debug: -1,
912
+ info: 0,
913
+ warn: 1,
914
+ error: 2
915
+ };
916
+ var cleanedLogFn = (log) => {
917
+ return (msg, metadata) => {
918
+ if (metadata && !metadata.telemetry) {
919
+ const span = import_api3.trace.getSpan(import_api3.context.active());
920
+ if (span) {
921
+ metadata.telemetry = {
922
+ traceId: span.spanContext().traceId,
923
+ spanId: span.spanContext().spanId
924
+ };
925
+ }
926
+ }
927
+ if (!metadata?.transportMessage) {
928
+ log(msg, metadata);
929
+ return;
930
+ }
931
+ const { payload, ...rest } = metadata.transportMessage;
932
+ metadata.transportMessage = rest;
933
+ log(msg, metadata);
920
934
  };
921
- }
922
- var SessionStateGraph = {
923
- entrypoints: {
924
- NoConnection: (to, from, listeners, options, protocolVersion, tracer, log) => {
925
- const id = `session-${generateId()}`;
926
- const telemetry = createSessionTelemetryInfo(tracer, id, to, from);
927
- const sendBuffer = [];
928
- const session = new SessionNoConnection({
929
- listeners,
930
- id,
931
- from,
932
- to,
933
- seq: 0,
934
- ack: 0,
935
- seqSent: 0,
936
- graceExpiryTime: Date.now() + options.sessionDisconnectGraceMs,
937
- sendBuffer,
938
- telemetry,
939
- options,
940
- protocolVersion,
941
- tracer,
942
- log
943
- });
944
- session.log?.info(`session ${session.id} created in NoConnection state`, {
945
- ...session.loggingMetadata,
946
- tags: ["state-transition"]
947
- });
948
- return session;
949
- },
950
- WaitingForHandshake: (from, conn, listeners, options, tracer, log) => {
951
- const session = new SessionWaitingForHandshake({
952
- conn,
953
- listeners,
954
- from,
955
- options,
956
- tracer,
957
- log
958
- });
959
- session.log?.info(`session created in WaitingForHandshake state`, {
960
- ...session.loggingMetadata,
961
- tags: ["state-transition"]
962
- });
963
- return session;
935
+ };
936
+ var BaseLogger = class {
937
+ minLevel;
938
+ output;
939
+ constructor(output, minLevel = "info") {
940
+ this.minLevel = minLevel;
941
+ this.output = output;
942
+ }
943
+ debug(msg, metadata) {
944
+ if (LoggingLevels[this.minLevel] <= LoggingLevels.debug) {
945
+ this.output(msg, metadata ?? {}, "debug");
964
946
  }
965
- },
966
- // All of the transitions 'move'/'consume' the old session and return a new one.
967
- // After a session is transitioned, any usage of the old session will throw.
968
- transition: {
969
- // happy path transitions
970
- NoConnectionToBackingOff: (oldSession, backoffMs, listeners) => {
971
- const carriedState = inheritSharedSessionWithGrace(oldSession);
972
- oldSession._handleStateExit();
973
- const session = new SessionBackingOff({
974
- backoffMs,
975
- listeners,
976
- ...carriedState
977
- });
978
- session.log?.info(
979
- `session ${session.id} transition from NoConnection to BackingOff`,
980
- {
981
- ...session.loggingMetadata,
982
- tags: ["state-transition"]
983
- }
984
- );
985
- return session;
986
- },
987
- BackingOffToConnecting: (oldSession, connPromise, listeners) => {
988
- const carriedState = inheritSharedSessionWithGrace(oldSession);
989
- oldSession._handleStateExit();
990
- const session = new SessionConnecting({
991
- connPromise,
992
- listeners,
993
- ...carriedState
994
- });
995
- session.log?.info(
996
- `session ${session.id} transition from BackingOff to Connecting`,
997
- {
998
- ...session.loggingMetadata,
999
- tags: ["state-transition"]
1000
- }
1001
- );
1002
- return session;
1003
- },
1004
- ConnectingToHandshaking: (oldSession, conn, listeners) => {
1005
- const carriedState = inheritSharedSessionWithGrace(oldSession);
1006
- oldSession._handleStateExit();
1007
- const session = new SessionHandshaking({
1008
- conn,
1009
- listeners,
1010
- ...carriedState
1011
- });
1012
- conn.telemetry = createConnectionTelemetryInfo(
1013
- session.tracer,
1014
- conn,
1015
- session.telemetry
1016
- );
1017
- session.log?.info(
1018
- `session ${session.id} transition from Connecting to Handshaking`,
1019
- {
1020
- ...session.loggingMetadata,
1021
- tags: ["state-transition"]
1022
- }
1023
- );
1024
- return session;
1025
- },
1026
- HandshakingToConnected: (oldSession, listeners) => {
1027
- const carriedState = inheritSharedSession(oldSession);
1028
- const conn = oldSession.conn;
1029
- oldSession._handleStateExit();
1030
- const session = new SessionConnected({
1031
- conn,
1032
- listeners,
1033
- ...carriedState
1034
- });
1035
- session.log?.info(
1036
- `session ${session.id} transition from Handshaking to Connected`,
1037
- {
1038
- ...session.loggingMetadata,
1039
- tags: ["state-transition"]
1040
- }
1041
- );
1042
- return session;
1043
- },
1044
- WaitingForHandshakeToConnected: (pendingSession, oldSession, sessionId, to, propagationCtx, listeners, protocolVersion) => {
1045
- const conn = pendingSession.conn;
1046
- const { from, options } = pendingSession;
1047
- const carriedState = oldSession ? (
1048
- // old session exists, inherit state
1049
- inheritSharedSession(oldSession)
1050
- ) : (
1051
- // old session does not exist, create new state
1052
- {
1053
- id: sessionId,
1054
- from,
1055
- to,
1056
- seq: 0,
1057
- ack: 0,
1058
- seqSent: 0,
1059
- sendBuffer: [],
1060
- telemetry: createSessionTelemetryInfo(
1061
- pendingSession.tracer,
1062
- sessionId,
1063
- to,
1064
- from,
1065
- propagationCtx
1066
- ),
1067
- options,
1068
- tracer: pendingSession.tracer,
1069
- log: pendingSession.log,
1070
- protocolVersion
1071
- }
1072
- );
1073
- pendingSession._handleStateExit();
1074
- oldSession?._handleStateExit();
1075
- const session = new SessionConnected({
1076
- conn,
1077
- listeners,
1078
- ...carriedState
1079
- });
1080
- conn.telemetry = createConnectionTelemetryInfo(
1081
- session.tracer,
1082
- conn,
1083
- session.telemetry
1084
- );
1085
- session.log?.info(
1086
- `session ${session.id} transition from WaitingForHandshake to Connected`,
1087
- {
1088
- ...session.loggingMetadata,
1089
- tags: ["state-transition"]
1090
- }
1091
- );
1092
- return session;
1093
- },
1094
- // disconnect paths
1095
- BackingOffToNoConnection: (oldSession, listeners) => {
1096
- const carriedState = inheritSharedSessionWithGrace(oldSession);
1097
- oldSession._handleStateExit();
1098
- const session = new SessionNoConnection({
1099
- listeners,
1100
- ...carriedState
1101
- });
1102
- session.log?.info(
1103
- `session ${session.id} transition from BackingOff to NoConnection`,
1104
- {
1105
- ...session.loggingMetadata,
1106
- tags: ["state-transition"]
1107
- }
1108
- );
1109
- return session;
1110
- },
1111
- ConnectingToNoConnection: (oldSession, listeners) => {
1112
- const carriedState = inheritSharedSessionWithGrace(oldSession);
1113
- oldSession.bestEffortClose();
1114
- oldSession._handleStateExit();
1115
- const session = new SessionNoConnection({
1116
- listeners,
1117
- ...carriedState
1118
- });
1119
- session.log?.info(
1120
- `session ${session.id} transition from Connecting to NoConnection`,
1121
- {
1122
- ...session.loggingMetadata,
1123
- tags: ["state-transition"]
1124
- }
1125
- );
1126
- return session;
1127
- },
1128
- HandshakingToNoConnection: (oldSession, listeners) => {
1129
- const carriedState = inheritSharedSessionWithGrace(oldSession);
1130
- oldSession.conn.close();
1131
- oldSession._handleStateExit();
1132
- const session = new SessionNoConnection({
1133
- listeners,
1134
- ...carriedState
1135
- });
1136
- session.log?.info(
1137
- `session ${session.id} transition from Handshaking to NoConnection`,
1138
- {
1139
- ...session.loggingMetadata,
1140
- tags: ["state-transition"]
1141
- }
1142
- );
1143
- return session;
1144
- },
1145
- ConnectedToNoConnection: (oldSession, listeners) => {
1146
- const carriedState = inheritSharedSession(oldSession);
1147
- const graceExpiryTime = Date.now() + oldSession.options.sessionDisconnectGraceMs;
1148
- oldSession.conn.close();
1149
- oldSession._handleStateExit();
1150
- const session = new SessionNoConnection({
1151
- listeners,
1152
- graceExpiryTime,
1153
- ...carriedState
1154
- });
1155
- session.log?.info(
1156
- `session ${session.id} transition from Connected to NoConnection`,
1157
- {
1158
- ...session.loggingMetadata,
1159
- tags: ["state-transition"]
1160
- }
1161
- );
1162
- return session;
947
+ }
948
+ info(msg, metadata) {
949
+ if (LoggingLevels[this.minLevel] <= LoggingLevels.info) {
950
+ this.output(msg, metadata ?? {}, "info");
1163
951
  }
1164
952
  }
1165
- };
1166
- var transitions = SessionStateGraph.transition;
1167
- var ClientSessionStateGraph = {
1168
- entrypoint: SessionStateGraph.entrypoints.NoConnection,
1169
- transition: {
1170
- // happy paths
1171
- // NoConnection -> BackingOff: attempt to connect
1172
- NoConnectionToBackingOff: transitions.NoConnectionToBackingOff,
1173
- // BackingOff -> Connecting: backoff period elapsed, start connection
1174
- BackingOffToConnecting: transitions.BackingOffToConnecting,
1175
- // Connecting -> Handshaking: connection established, start handshake
1176
- ConnectingToHandshaking: transitions.ConnectingToHandshaking,
1177
- // Handshaking -> Connected: handshake complete, session ready
1178
- HandshakingToConnected: transitions.HandshakingToConnected,
1179
- // disconnect paths
1180
- // BackingOff -> NoConnection: unused
1181
- BackingOffToNoConnection: transitions.BackingOffToNoConnection,
1182
- // Connecting -> NoConnection: connection failed or connection timeout
1183
- ConnectingToNoConnection: transitions.ConnectingToNoConnection,
1184
- // Handshaking -> NoConnection: connection closed or handshake timeout
1185
- HandshakingToNoConnection: transitions.HandshakingToNoConnection,
1186
- // Connected -> NoConnection: connection closed
1187
- ConnectedToNoConnection: transitions.ConnectedToNoConnection
1188
- // destroy/close paths
1189
- // NoConnection -> x: grace period elapsed
1190
- // BackingOff -> x: grace period elapsed
1191
- // Connecting -> x: grace period elapsed
1192
- // Handshaking -> x: grace period elapsed or invalid handshake message or handshake rejection
1193
- // Connected -> x: grace period elapsed or invalid message
953
+ warn(msg, metadata) {
954
+ if (LoggingLevels[this.minLevel] <= LoggingLevels.warn) {
955
+ this.output(msg, metadata ?? {}, "warn");
956
+ }
1194
957
  }
1195
- };
1196
- var ServerSessionStateGraph = {
1197
- entrypoint: SessionStateGraph.entrypoints.WaitingForHandshake,
1198
- transition: {
1199
- // happy paths
1200
- // WaitingForHandshake -> Connected: handshake complete, session ready
1201
- WaitingForHandshakeToConnected: transitions.WaitingForHandshakeToConnected,
1202
- // disconnect paths
1203
- // Connected -> NoConnection: connection closed
1204
- ConnectedToNoConnection: transitions.ConnectedToNoConnection
1205
- // destroy/close paths
1206
- // WaitingForHandshake -> x: handshake timeout elapsed or invalid handshake message or handshake rejection or connection closed
958
+ error(msg, metadata) {
959
+ if (LoggingLevels[this.minLevel] <= LoggingLevels.error) {
960
+ this.output(msg, metadata ?? {}, "error");
961
+ }
1207
962
  }
1208
963
  };
964
+ var createLogProxy = (log) => ({
965
+ debug: cleanedLogFn(log.debug.bind(log)),
966
+ info: cleanedLogFn(log.info.bind(log)),
967
+ warn: cleanedLogFn(log.warn.bind(log)),
968
+ error: cleanedLogFn(log.error.bind(log))
969
+ });
1209
970
 
1210
- // transport/client.ts
1211
- var import_api4 = require("@opentelemetry/api");
1212
-
1213
- // transport/rateLimit.ts
1214
- var LeakyBucketRateLimit = class {
1215
- budgetConsumed;
1216
- intervalHandle;
1217
- options;
1218
- constructor(options) {
1219
- this.options = options;
1220
- this.budgetConsumed = 0;
1221
- }
1222
- getBackoffMs() {
1223
- if (this.getBudgetConsumed() === 0) {
1224
- return 0;
1225
- }
1226
- const exponent = Math.max(0, this.getBudgetConsumed() - 1);
1227
- const jitter = Math.floor(Math.random() * this.options.maxJitterMs);
1228
- const backoffMs = Math.min(
1229
- this.options.baseIntervalMs * 2 ** exponent,
1230
- this.options.maxBackoffMs
1231
- );
1232
- return backoffMs + jitter;
1233
- }
1234
- get totalBudgetRestoreTime() {
1235
- return this.options.budgetRestoreIntervalMs * this.options.attemptBudgetCapacity;
1236
- }
1237
- consumeBudget() {
1238
- this.stopLeak();
1239
- this.budgetConsumed = this.getBudgetConsumed() + 1;
1240
- }
1241
- getBudgetConsumed() {
1242
- return this.budgetConsumed;
971
+ // transport/events.ts
972
+ var ProtocolError = {
973
+ RetriesExceeded: "conn_retry_exceeded",
974
+ HandshakeFailed: "handshake_failed",
975
+ MessageOrderingViolated: "message_ordering_violated",
976
+ InvalidMessage: "invalid_message",
977
+ MessageSendFailure: "message_send_failure"
978
+ };
979
+ var EventDispatcher = class {
980
+ eventListeners = {};
981
+ removeAllListeners() {
982
+ this.eventListeners = {};
1243
983
  }
1244
- hasBudget() {
1245
- return this.getBudgetConsumed() < this.options.attemptBudgetCapacity;
984
+ numberOfListeners(eventType) {
985
+ return this.eventListeners[eventType]?.size ?? 0;
1246
986
  }
1247
- startRestoringBudget() {
1248
- if (this.intervalHandle) {
1249
- return;
987
+ addEventListener(eventType, handler) {
988
+ if (!this.eventListeners[eventType]) {
989
+ this.eventListeners[eventType] = /* @__PURE__ */ new Set();
1250
990
  }
1251
- const restoreBudgetForUser = () => {
1252
- const currentBudget = this.budgetConsumed;
1253
- if (!currentBudget) {
1254
- this.stopLeak();
1255
- return;
1256
- }
1257
- const newBudget = currentBudget - 1;
1258
- if (newBudget === 0) {
1259
- return;
1260
- }
1261
- this.budgetConsumed = newBudget;
1262
- };
1263
- this.intervalHandle = setInterval(
1264
- restoreBudgetForUser,
1265
- this.options.budgetRestoreIntervalMs
1266
- );
991
+ this.eventListeners[eventType]?.add(handler);
1267
992
  }
1268
- stopLeak() {
1269
- if (!this.intervalHandle) {
1270
- return;
993
+ removeEventListener(eventType, handler) {
994
+ const handlers = this.eventListeners[eventType];
995
+ if (handlers) {
996
+ this.eventListeners[eventType]?.delete(handler);
1271
997
  }
1272
- clearInterval(this.intervalHandle);
1273
- this.intervalHandle = void 0;
1274
998
  }
1275
- close() {
1276
- this.stopLeak();
1277
- }
1278
- };
1279
-
1280
- // logging/log.ts
1281
- var import_api3 = require("@opentelemetry/api");
1282
- var LoggingLevels = {
1283
- debug: -1,
1284
- info: 0,
1285
- warn: 1,
1286
- error: 2
1287
- };
1288
- var cleanedLogFn = (log) => {
1289
- return (msg, metadata) => {
1290
- if (metadata && !metadata.telemetry) {
1291
- const span = import_api3.trace.getSpan(import_api3.context.active());
1292
- if (span) {
1293
- metadata.telemetry = {
1294
- traceId: span.spanContext().traceId,
1295
- spanId: span.spanContext().spanId
1296
- };
1297
- }
1298
- }
1299
- if (!metadata?.transportMessage) {
1300
- log(msg, metadata);
1301
- return;
1302
- }
1303
- const { payload, ...rest } = metadata.transportMessage;
1304
- metadata.transportMessage = rest;
1305
- log(msg, metadata);
1306
- };
1307
- };
1308
- var BaseLogger = class {
1309
- minLevel;
1310
- output;
1311
- constructor(output, minLevel = "info") {
1312
- this.minLevel = minLevel;
1313
- this.output = output;
1314
- }
1315
- debug(msg, metadata) {
1316
- if (LoggingLevels[this.minLevel] <= LoggingLevels.debug) {
1317
- this.output(msg, metadata ?? {}, "debug");
1318
- }
1319
- }
1320
- info(msg, metadata) {
1321
- if (LoggingLevels[this.minLevel] <= LoggingLevels.info) {
1322
- this.output(msg, metadata ?? {}, "info");
1323
- }
1324
- }
1325
- warn(msg, metadata) {
1326
- if (LoggingLevels[this.minLevel] <= LoggingLevels.warn) {
1327
- this.output(msg, metadata ?? {}, "warn");
1328
- }
1329
- }
1330
- error(msg, metadata) {
1331
- if (LoggingLevels[this.minLevel] <= LoggingLevels.error) {
1332
- this.output(msg, metadata ?? {}, "error");
1333
- }
1334
- }
1335
- };
1336
- var createLogProxy = (log) => ({
1337
- debug: cleanedLogFn(log.debug.bind(log)),
1338
- info: cleanedLogFn(log.info.bind(log)),
1339
- warn: cleanedLogFn(log.warn.bind(log)),
1340
- error: cleanedLogFn(log.error.bind(log))
1341
- });
1342
-
1343
- // transport/events.ts
1344
- var ProtocolError = {
1345
- RetriesExceeded: "conn_retry_exceeded",
1346
- HandshakeFailed: "handshake_failed",
1347
- MessageOrderingViolated: "message_ordering_violated",
1348
- InvalidMessage: "invalid_message"
1349
- };
1350
- var EventDispatcher = class {
1351
- eventListeners = {};
1352
- removeAllListeners() {
1353
- this.eventListeners = {};
1354
- }
1355
- numberOfListeners(eventType) {
1356
- return this.eventListeners[eventType]?.size ?? 0;
1357
- }
1358
- addEventListener(eventType, handler) {
1359
- if (!this.eventListeners[eventType]) {
1360
- this.eventListeners[eventType] = /* @__PURE__ */ new Set();
1361
- }
1362
- this.eventListeners[eventType]?.add(handler);
1363
- }
1364
- removeEventListener(eventType, handler) {
1365
- const handlers = this.eventListeners[eventType];
1366
- if (handlers) {
1367
- this.eventListeners[eventType]?.delete(handler);
1368
- }
1369
- }
1370
- dispatchEvent(eventType, event) {
1371
- const handlers = this.eventListeners[eventType];
1372
- if (handlers) {
1373
- const copy = [...handlers];
1374
- for (const handler of copy) {
1375
- handler(event);
1376
- }
1377
- }
999
+ dispatchEvent(eventType, event) {
1000
+ const handlers = this.eventListeners[eventType];
1001
+ if (handlers) {
1002
+ const copy = [...handlers];
1003
+ for (const handler of copy) {
1004
+ handler(event);
1005
+ }
1006
+ }
1378
1007
  }
1379
1008
  };
1380
1009
 
@@ -1588,18 +1217,92 @@ var Transport = class {
1588
1217
  );
1589
1218
  }
1590
1219
  const sameSession = session.id === sessionId;
1591
- if (!sameSession) {
1220
+ if (!sameSession || session._isConsumed) {
1592
1221
  throw new Error(
1593
1222
  `session scope for ${sessionId} has ended (transition), can't send`
1594
1223
  );
1595
1224
  }
1596
- return session.send(msg);
1225
+ const res = session.send(msg);
1226
+ if (!res.ok) {
1227
+ throw new Error(res.reason);
1228
+ }
1229
+ return res.value;
1597
1230
  };
1598
1231
  }
1599
1232
  };
1600
1233
 
1601
1234
  // transport/client.ts
1602
- var import_value2 = require("@sinclair/typebox/value");
1235
+ var import_api4 = require("@opentelemetry/api");
1236
+
1237
+ // transport/rateLimit.ts
1238
+ var LeakyBucketRateLimit = class {
1239
+ budgetConsumed;
1240
+ intervalHandle;
1241
+ options;
1242
+ constructor(options) {
1243
+ this.options = options;
1244
+ this.budgetConsumed = 0;
1245
+ }
1246
+ getBackoffMs() {
1247
+ if (this.getBudgetConsumed() === 0) {
1248
+ return 0;
1249
+ }
1250
+ const exponent = Math.max(0, this.getBudgetConsumed() - 1);
1251
+ const jitter = Math.floor(Math.random() * this.options.maxJitterMs);
1252
+ const backoffMs = Math.min(
1253
+ this.options.baseIntervalMs * 2 ** exponent,
1254
+ this.options.maxBackoffMs
1255
+ );
1256
+ return backoffMs + jitter;
1257
+ }
1258
+ get totalBudgetRestoreTime() {
1259
+ return this.options.budgetRestoreIntervalMs * this.options.attemptBudgetCapacity;
1260
+ }
1261
+ consumeBudget() {
1262
+ this.stopLeak();
1263
+ this.budgetConsumed = this.getBudgetConsumed() + 1;
1264
+ }
1265
+ getBudgetConsumed() {
1266
+ return this.budgetConsumed;
1267
+ }
1268
+ hasBudget() {
1269
+ return this.getBudgetConsumed() < this.options.attemptBudgetCapacity;
1270
+ }
1271
+ startRestoringBudget() {
1272
+ if (this.intervalHandle) {
1273
+ return;
1274
+ }
1275
+ const restoreBudgetForUser = () => {
1276
+ const currentBudget = this.budgetConsumed;
1277
+ if (!currentBudget) {
1278
+ this.stopLeak();
1279
+ return;
1280
+ }
1281
+ const newBudget = currentBudget - 1;
1282
+ if (newBudget === 0) {
1283
+ return;
1284
+ }
1285
+ this.budgetConsumed = newBudget;
1286
+ };
1287
+ this.intervalHandle = setInterval(
1288
+ restoreBudgetForUser,
1289
+ this.options.budgetRestoreIntervalMs
1290
+ );
1291
+ }
1292
+ stopLeak() {
1293
+ if (!this.intervalHandle) {
1294
+ return;
1295
+ }
1296
+ clearInterval(this.intervalHandle);
1297
+ this.intervalHandle = void 0;
1298
+ }
1299
+ close() {
1300
+ this.stopLeak();
1301
+ }
1302
+ };
1303
+
1304
+ // transport/client.ts
1305
+ var import_value = require("@sinclair/typebox/value");
1603
1306
  var ClientTransport = class extends Transport {
1604
1307
  /**
1605
1308
  * The options for this transport.
@@ -1730,19 +1433,19 @@ var ClientTransport = class extends Transport {
1730
1433
  this.deleteSession(session, { unhealthy: true });
1731
1434
  }
1732
1435
  onHandshakeResponse(session, msg) {
1733
- if (!import_value2.Value.Check(ControlMessageHandshakeResponseSchema, msg.payload)) {
1436
+ if (!import_value.Value.Check(ControlMessageHandshakeResponseSchema, msg.payload)) {
1734
1437
  const reason = `received invalid handshake response`;
1735
1438
  this.rejectHandshakeResponse(session, reason, {
1736
1439
  ...session.loggingMetadata,
1737
1440
  transportMessage: msg,
1738
1441
  validationErrors: [
1739
- ...import_value2.Value.Errors(ControlMessageHandshakeResponseSchema, msg.payload)
1442
+ ...import_value.Value.Errors(ControlMessageHandshakeResponseSchema, msg.payload)
1740
1443
  ]
1741
1444
  });
1742
1445
  return;
1743
1446
  }
1744
1447
  if (!msg.payload.status.ok) {
1745
- const retriable = import_value2.Value.Check(
1448
+ const retriable = import_value.Value.Check(
1746
1449
  HandshakeErrorRetriableResponseCodes,
1747
1450
  msg.payload.status.code
1748
1451
  );
@@ -1794,13 +1497,41 @@ var ClientTransport = class extends Transport {
1794
1497
  this.handleMsg(msg2);
1795
1498
  },
1796
1499
  onInvalidMessage: (reason) => {
1797
- this.deleteSession(connectedSession, { unhealthy: true });
1500
+ this.log?.error(`invalid message: ${reason}`, {
1501
+ ...connectedSession.loggingMetadata,
1502
+ transportMessage: msg
1503
+ });
1798
1504
  this.protocolError({
1799
1505
  type: ProtocolError.InvalidMessage,
1800
1506
  message: reason
1801
1507
  });
1508
+ this.deleteSession(connectedSession, { unhealthy: true });
1509
+ },
1510
+ onMessageSendFailure: (msg2, reason) => {
1511
+ this.log?.error(`failed to send message: ${reason}`, {
1512
+ ...connectedSession.loggingMetadata,
1513
+ transportMessage: msg2
1514
+ });
1515
+ this.protocolError({
1516
+ type: ProtocolError.MessageSendFailure,
1517
+ message: reason
1518
+ });
1519
+ this.deleteSession(connectedSession, { unhealthy: true });
1802
1520
  }
1803
1521
  });
1522
+ const res = connectedSession.sendBufferedMessages();
1523
+ if (!res.ok) {
1524
+ this.log?.error(`failed to send buffered messages: ${res.reason}`, {
1525
+ ...connectedSession.loggingMetadata,
1526
+ transportMessage: msg
1527
+ });
1528
+ this.protocolError({
1529
+ type: ProtocolError.MessageSendFailure,
1530
+ message: res.reason
1531
+ });
1532
+ this.deleteSession(connectedSession, { unhealthy: true });
1533
+ return;
1534
+ }
1804
1535
  this.updateSession(connectedSession);
1805
1536
  this.retryBudget.startRestoringBudget();
1806
1537
  }
@@ -1939,7 +1670,18 @@ var ClientTransport = class extends Transport {
1939
1670
  ...session.loggingMetadata,
1940
1671
  transportMessage: requestMsg
1941
1672
  });
1942
- session.sendHandshake(requestMsg);
1673
+ const res = session.sendHandshake(requestMsg);
1674
+ if (!res.ok) {
1675
+ this.log?.error(`failed to send handshake request: ${res.reason}`, {
1676
+ ...session.loggingMetadata,
1677
+ transportMessage: requestMsg
1678
+ });
1679
+ this.protocolError({
1680
+ type: ProtocolError.MessageSendFailure,
1681
+ message: res.reason
1682
+ });
1683
+ this.deleteSession(session, { unhealthy: true });
1684
+ }
1943
1685
  }
1944
1686
  close() {
1945
1687
  this.retryBudget.close();
@@ -1947,103 +1689,16 @@ var ClientTransport = class extends Transport {
1947
1689
  }
1948
1690
  };
1949
1691
 
1950
- // transport/connection.ts
1951
- var Connection = class {
1952
- id;
1953
- telemetry;
1954
- constructor() {
1955
- this.id = `conn-${generateId()}`;
1956
- }
1957
- get loggingMetadata() {
1958
- const metadata = { connId: this.id };
1959
- if (this.telemetry?.span.isRecording()) {
1960
- const spanContext = this.telemetry.span.spanContext();
1961
- metadata.telemetry = {
1962
- traceId: spanContext.traceId,
1963
- spanId: spanContext.spanId
1964
- };
1965
- }
1966
- return metadata;
1967
- }
1968
- // can't use event emitter because we need this to work in both node + browser
1969
- _dataListeners = /* @__PURE__ */ new Set();
1970
- _closeListeners = /* @__PURE__ */ new Set();
1971
- _errorListeners = /* @__PURE__ */ new Set();
1972
- get dataListeners() {
1973
- return [...this._dataListeners];
1974
- }
1975
- get closeListeners() {
1976
- return [...this._closeListeners];
1977
- }
1978
- get errorListeners() {
1979
- return [...this._errorListeners];
1980
- }
1981
- onData(msg) {
1982
- for (const cb of this.dataListeners) {
1983
- cb(msg);
1984
- }
1985
- }
1986
- onError(err) {
1987
- for (const cb of this.errorListeners) {
1988
- cb(err);
1989
- }
1990
- }
1991
- onClose() {
1992
- for (const cb of this.closeListeners) {
1993
- cb();
1994
- }
1995
- this.telemetry?.span.end();
1996
- }
1692
+ // transport/server.ts
1693
+ var import_api5 = require("@opentelemetry/api");
1694
+ var import_value2 = require("@sinclair/typebox/value");
1695
+ var ServerTransport = class extends Transport {
1997
1696
  /**
1998
- * Handle adding a callback for when a message is received.
1999
- * @param msg The message that was received.
1697
+ * The options for this transport.
2000
1698
  */
2001
- addDataListener(cb) {
2002
- this._dataListeners.add(cb);
2003
- }
2004
- removeDataListener(cb) {
2005
- this._dataListeners.delete(cb);
2006
- }
1699
+ options;
2007
1700
  /**
2008
- * Handle adding a callback for when the connection is closed.
2009
- * This should also be called if an error happens and after notifying all the error listeners.
2010
- * @param cb The callback to call when the connection is closed.
2011
- */
2012
- addCloseListener(cb) {
2013
- this._closeListeners.add(cb);
2014
- }
2015
- removeCloseListener(cb) {
2016
- this._closeListeners.delete(cb);
2017
- }
2018
- /**
2019
- * Handle adding a callback for when an error is received.
2020
- * This should only be used for this.logging errors, all cleanup
2021
- * should be delegated to addCloseListener.
2022
- *
2023
- * The implementer should take care such that the implemented
2024
- * connection will call both the close and error callbacks
2025
- * on an error.
2026
- *
2027
- * @param cb The callback to call when an error is received.
2028
- */
2029
- addErrorListener(cb) {
2030
- this._errorListeners.add(cb);
2031
- }
2032
- removeErrorListener(cb) {
2033
- this._errorListeners.delete(cb);
2034
- }
2035
- };
2036
-
2037
- // transport/server.ts
2038
- var import_api5 = require("@opentelemetry/api");
2039
- var import_value3 = require("@sinclair/typebox/value");
2040
- var ServerTransport = class extends Transport {
2041
- /**
2042
- * The options for this transport.
2043
- */
2044
- options;
2045
- /**
2046
- * Optional handshake options for the server.
1701
+ * Optional handshake options for the server.
2047
1702
  */
2048
1703
  handshakeExtensions;
2049
1704
  /**
@@ -2150,17 +1805,28 @@ var ServerTransport = class extends Transport {
2150
1805
  message: reason
2151
1806
  });
2152
1807
  this.log?.warn(reason, metadata);
2153
- session.sendHandshake(
2154
- handshakeResponseMessage({
2155
- from: this.clientId,
2156
- to,
2157
- status: {
2158
- ok: false,
2159
- code,
2160
- reason
2161
- }
2162
- })
2163
- );
1808
+ const responseMsg = handshakeResponseMessage({
1809
+ from: this.clientId,
1810
+ to,
1811
+ status: {
1812
+ ok: false,
1813
+ code,
1814
+ reason
1815
+ }
1816
+ });
1817
+ const res = session.sendHandshake(responseMsg);
1818
+ if (!res.ok) {
1819
+ this.log?.error(`failed to send handshake response: ${res.reason}`, {
1820
+ ...session.loggingMetadata,
1821
+ transportMessage: responseMsg
1822
+ });
1823
+ this.protocolError({
1824
+ type: ProtocolError.MessageSendFailure,
1825
+ message: res.reason
1826
+ });
1827
+ this.deletePendingSession(session);
1828
+ return;
1829
+ }
2164
1830
  this.protocolError({
2165
1831
  type: ProtocolError.HandshakeFailed,
2166
1832
  code,
@@ -2169,7 +1835,7 @@ var ServerTransport = class extends Transport {
2169
1835
  this.deletePendingSession(session);
2170
1836
  }
2171
1837
  async onHandshakeRequest(session, msg) {
2172
- if (!import_value3.Value.Check(ControlMessageHandshakeRequestSchema, msg.payload)) {
1838
+ if (!import_value2.Value.Check(ControlMessageHandshakeRequestSchema, msg.payload)) {
2173
1839
  this.rejectHandshakeRequest(
2174
1840
  session,
2175
1841
  msg.from,
@@ -2180,7 +1846,7 @@ var ServerTransport = class extends Transport {
2180
1846
  transportMessage: msg,
2181
1847
  connectedTo: msg.from,
2182
1848
  validationErrors: [
2183
- ...import_value3.Value.Errors(ControlMessageHandshakeRequestSchema, msg.payload)
1849
+ ...import_value2.Value.Errors(ControlMessageHandshakeRequestSchema, msg.payload)
2184
1850
  ]
2185
1851
  }
2186
1852
  );
@@ -2203,7 +1869,7 @@ var ServerTransport = class extends Transport {
2203
1869
  }
2204
1870
  let parsedMetadata = {};
2205
1871
  if (this.handshakeExtensions) {
2206
- if (!import_value3.Value.Check(this.handshakeExtensions.schema, msg.payload.metadata)) {
1872
+ if (!import_value2.Value.Check(this.handshakeExtensions.schema, msg.payload.metadata)) {
2207
1873
  this.rejectHandshakeRequest(
2208
1874
  session,
2209
1875
  msg.from,
@@ -2213,7 +1879,7 @@ var ServerTransport = class extends Transport {
2213
1879
  ...session.loggingMetadata,
2214
1880
  connectedTo: msg.from,
2215
1881
  validationErrors: [
2216
- ...import_value3.Value.Errors(
1882
+ ...import_value2.Value.Errors(
2217
1883
  this.handshakeExtensions.schema,
2218
1884
  msg.payload.metadata
2219
1885
  )
@@ -2229,165 +1895,652 @@ var ServerTransport = class extends Transport {
2229
1895
  msg.payload.metadata,
2230
1896
  previousParsedMetadata
2231
1897
  );
2232
- if (session._isConsumed) {
2233
- return;
2234
- }
2235
- if (import_value3.Value.Check(
2236
- HandshakeErrorCustomHandlerFatalResponseCodes,
2237
- parsedMetadataOrFailureCode
2238
- )) {
2239
- this.rejectHandshakeRequest(
2240
- session,
2241
- msg.from,
2242
- "rejected by handshake handler",
2243
- parsedMetadataOrFailureCode,
2244
- {
2245
- ...session.loggingMetadata,
2246
- connectedTo: msg.from,
2247
- clientId: this.clientId
2248
- }
2249
- );
2250
- return;
2251
- }
2252
- parsedMetadata = parsedMetadataOrFailureCode;
2253
- }
2254
- let connectCase = "new session";
2255
- const clientNextExpectedSeq = msg.payload.expectedSessionState.nextExpectedSeq;
2256
- const clientNextSentSeq = msg.payload.expectedSessionState.nextSentSeq;
2257
- let oldSession = this.sessions.get(msg.from);
2258
- if (this.options.enableTransparentSessionReconnects && oldSession && oldSession.id === msg.payload.sessionId) {
2259
- connectCase = "transparent reconnection";
2260
- const ourNextSeq = oldSession.nextSeq();
2261
- const ourAck = oldSession.ack;
2262
- if (clientNextSentSeq > ourAck) {
2263
- this.rejectHandshakeRequest(
2264
- session,
2265
- msg.from,
2266
- `client is in the future: server wanted next message to be ${ourAck} but client would have sent ${clientNextSentSeq}`,
2267
- "SESSION_STATE_MISMATCH",
2268
- {
2269
- ...session.loggingMetadata,
2270
- connectedTo: msg.from,
2271
- transportMessage: msg
2272
- }
2273
- );
2274
- return;
2275
- }
2276
- if (ourNextSeq > clientNextExpectedSeq) {
2277
- this.rejectHandshakeRequest(
2278
- session,
2279
- msg.from,
2280
- `server is in the future: client wanted next message to be ${clientNextExpectedSeq} but server would have sent ${ourNextSeq}`,
2281
- "SESSION_STATE_MISMATCH",
2282
- {
2283
- ...session.loggingMetadata,
2284
- connectedTo: msg.from,
2285
- transportMessage: msg
2286
- }
2287
- );
2288
- return;
2289
- }
2290
- if (oldSession.state !== "NoConnection" /* NoConnection */) {
2291
- const noConnectionSession = ServerSessionStateGraph.transition.ConnectedToNoConnection(
2292
- oldSession,
2293
- {
2294
- onSessionGracePeriodElapsed: () => {
2295
- this.onSessionGracePeriodElapsed(noConnectionSession);
2296
- }
2297
- }
2298
- );
2299
- oldSession = noConnectionSession;
2300
- this.updateSession(oldSession);
2301
- }
2302
- } else if (oldSession) {
2303
- connectCase = "hard reconnection";
2304
- this.log?.info(
2305
- `client is reconnecting to a new session (${msg.payload.sessionId}) with an old session (${oldSession.id}) already existing, closing old session`,
1898
+ if (session._isConsumed) {
1899
+ return;
1900
+ }
1901
+ if (import_value2.Value.Check(
1902
+ HandshakeErrorCustomHandlerFatalResponseCodes,
1903
+ parsedMetadataOrFailureCode
1904
+ )) {
1905
+ this.rejectHandshakeRequest(
1906
+ session,
1907
+ msg.from,
1908
+ "rejected by handshake handler",
1909
+ parsedMetadataOrFailureCode,
1910
+ {
1911
+ ...session.loggingMetadata,
1912
+ connectedTo: msg.from,
1913
+ clientId: this.clientId
1914
+ }
1915
+ );
1916
+ return;
1917
+ }
1918
+ parsedMetadata = parsedMetadataOrFailureCode;
1919
+ }
1920
+ let connectCase = "new session";
1921
+ const clientNextExpectedSeq = msg.payload.expectedSessionState.nextExpectedSeq;
1922
+ const clientNextSentSeq = msg.payload.expectedSessionState.nextSentSeq;
1923
+ let oldSession = this.sessions.get(msg.from);
1924
+ if (this.options.enableTransparentSessionReconnects && oldSession && oldSession.id === msg.payload.sessionId) {
1925
+ connectCase = "transparent reconnection";
1926
+ const ourNextSeq = oldSession.nextSeq();
1927
+ const ourAck = oldSession.ack;
1928
+ if (clientNextSentSeq > ourAck) {
1929
+ this.rejectHandshakeRequest(
1930
+ session,
1931
+ msg.from,
1932
+ `client is in the future: server wanted next message to be ${ourAck} but client would have sent ${clientNextSentSeq}`,
1933
+ "SESSION_STATE_MISMATCH",
1934
+ {
1935
+ ...session.loggingMetadata,
1936
+ connectedTo: msg.from,
1937
+ transportMessage: msg
1938
+ }
1939
+ );
1940
+ return;
1941
+ }
1942
+ if (ourNextSeq > clientNextExpectedSeq) {
1943
+ this.rejectHandshakeRequest(
1944
+ session,
1945
+ msg.from,
1946
+ `server is in the future: client wanted next message to be ${clientNextExpectedSeq} but server would have sent ${ourNextSeq}`,
1947
+ "SESSION_STATE_MISMATCH",
1948
+ {
1949
+ ...session.loggingMetadata,
1950
+ connectedTo: msg.from,
1951
+ transportMessage: msg
1952
+ }
1953
+ );
1954
+ return;
1955
+ }
1956
+ if (oldSession.state !== "NoConnection" /* NoConnection */) {
1957
+ const noConnectionSession = ServerSessionStateGraph.transition.ConnectedToNoConnection(
1958
+ oldSession,
1959
+ {
1960
+ onSessionGracePeriodElapsed: () => {
1961
+ this.onSessionGracePeriodElapsed(noConnectionSession);
1962
+ }
1963
+ }
1964
+ );
1965
+ oldSession = noConnectionSession;
1966
+ this.updateSession(oldSession);
1967
+ }
1968
+ } else if (oldSession) {
1969
+ connectCase = "hard reconnection";
1970
+ this.log?.info(
1971
+ `client is reconnecting to a new session (${msg.payload.sessionId}) with an old session (${oldSession.id}) already existing, closing old session`,
1972
+ {
1973
+ ...session.loggingMetadata,
1974
+ connectedTo: msg.from,
1975
+ sessionId: msg.payload.sessionId
1976
+ }
1977
+ );
1978
+ this.deleteSession(oldSession);
1979
+ oldSession = void 0;
1980
+ }
1981
+ if (!oldSession && (clientNextSentSeq > 0 || clientNextExpectedSeq > 0)) {
1982
+ connectCase = "unknown session";
1983
+ const rejectionMessage = this.options.enableTransparentSessionReconnects ? `client is trying to reconnect to a session the server don't know about: ${msg.payload.sessionId}` : `client is attempting a transparent reconnect to a session but the server does not support it: ${msg.payload.sessionId}`;
1984
+ this.rejectHandshakeRequest(
1985
+ session,
1986
+ msg.from,
1987
+ rejectionMessage,
1988
+ "SESSION_STATE_MISMATCH",
1989
+ {
1990
+ ...session.loggingMetadata,
1991
+ connectedTo: msg.from,
1992
+ transportMessage: msg
1993
+ }
1994
+ );
1995
+ return;
1996
+ }
1997
+ const sessionId = msg.payload.sessionId;
1998
+ this.log?.info(
1999
+ `handshake from ${msg.from} ok (${connectCase}), responding with handshake success`,
2000
+ {
2001
+ ...session.loggingMetadata,
2002
+ connectedTo: msg.from
2003
+ }
2004
+ );
2005
+ const responseMsg = handshakeResponseMessage({
2006
+ from: this.clientId,
2007
+ to: msg.from,
2008
+ status: {
2009
+ ok: true,
2010
+ sessionId
2011
+ }
2012
+ });
2013
+ const res = session.sendHandshake(responseMsg);
2014
+ if (!res.ok) {
2015
+ this.log?.error(`failed to send handshake response: ${res.reason}`, {
2016
+ ...session.loggingMetadata,
2017
+ transportMessage: responseMsg
2018
+ });
2019
+ this.protocolError({
2020
+ type: ProtocolError.MessageSendFailure,
2021
+ message: res.reason
2022
+ });
2023
+ this.deletePendingSession(session);
2024
+ return;
2025
+ }
2026
+ this.pendingSessions.delete(session);
2027
+ const connectedSession = ServerSessionStateGraph.transition.WaitingForHandshakeToConnected(
2028
+ session,
2029
+ // by this point oldSession is either no connection or we dont have an old session
2030
+ oldSession,
2031
+ sessionId,
2032
+ msg.from,
2033
+ msg.tracing,
2034
+ {
2035
+ onConnectionErrored: (err) => {
2036
+ const errStr = coerceErrorString(err);
2037
+ this.log?.warn(
2038
+ `connection to ${connectedSession.to} errored: ${errStr}`,
2039
+ connectedSession.loggingMetadata
2040
+ );
2041
+ },
2042
+ onConnectionClosed: () => {
2043
+ this.log?.info(
2044
+ `connection to ${connectedSession.to} closed`,
2045
+ connectedSession.loggingMetadata
2046
+ );
2047
+ this.onConnClosed(connectedSession);
2048
+ },
2049
+ onMessage: (msg2) => {
2050
+ this.handleMsg(msg2);
2051
+ },
2052
+ onInvalidMessage: (reason) => {
2053
+ this.log?.error(`invalid message: ${reason}`, {
2054
+ ...connectedSession.loggingMetadata,
2055
+ transportMessage: msg
2056
+ });
2057
+ this.protocolError({
2058
+ type: ProtocolError.InvalidMessage,
2059
+ message: reason
2060
+ });
2061
+ this.deleteSession(connectedSession, { unhealthy: true });
2062
+ },
2063
+ onMessageSendFailure: (msg2, reason) => {
2064
+ this.log?.error(`failed to send message: ${reason}`, {
2065
+ ...connectedSession.loggingMetadata,
2066
+ transportMessage: msg2
2067
+ });
2068
+ this.protocolError({
2069
+ type: ProtocolError.MessageSendFailure,
2070
+ message: reason
2071
+ });
2072
+ this.deleteSession(connectedSession, { unhealthy: true });
2073
+ }
2074
+ },
2075
+ gotVersion
2076
+ );
2077
+ const bufferSendRes = connectedSession.sendBufferedMessages();
2078
+ if (!bufferSendRes.ok) {
2079
+ this.log?.error(
2080
+ `failed to send buffered messages: ${bufferSendRes.reason}`,
2081
+ {
2082
+ ...connectedSession.loggingMetadata,
2083
+ transportMessage: msg
2084
+ }
2085
+ );
2086
+ this.protocolError({
2087
+ type: ProtocolError.MessageSendFailure,
2088
+ message: bufferSendRes.reason
2089
+ });
2090
+ this.deleteSession(connectedSession, { unhealthy: true });
2091
+ return;
2092
+ }
2093
+ this.sessionHandshakeMetadata.set(connectedSession.to, parsedMetadata);
2094
+ if (oldSession) {
2095
+ this.updateSession(connectedSession);
2096
+ } else {
2097
+ this.createSession(connectedSession);
2098
+ }
2099
+ connectedSession.startActiveHeartbeat();
2100
+ }
2101
+ };
2102
+
2103
+ // transport/connection.ts
2104
+ var Connection = class {
2105
+ id;
2106
+ telemetry;
2107
+ constructor() {
2108
+ this.id = `conn-${generateId()}`;
2109
+ }
2110
+ get loggingMetadata() {
2111
+ const metadata = { connId: this.id };
2112
+ if (this.telemetry?.span.isRecording()) {
2113
+ const spanContext = this.telemetry.span.spanContext();
2114
+ metadata.telemetry = {
2115
+ traceId: spanContext.traceId,
2116
+ spanId: spanContext.spanId
2117
+ };
2118
+ }
2119
+ return metadata;
2120
+ }
2121
+ // can't use event emitter because we need this to work in both node + browser
2122
+ _dataListeners = /* @__PURE__ */ new Set();
2123
+ _closeListeners = /* @__PURE__ */ new Set();
2124
+ _errorListeners = /* @__PURE__ */ new Set();
2125
+ get dataListeners() {
2126
+ return [...this._dataListeners];
2127
+ }
2128
+ get closeListeners() {
2129
+ return [...this._closeListeners];
2130
+ }
2131
+ get errorListeners() {
2132
+ return [...this._errorListeners];
2133
+ }
2134
+ onData(msg) {
2135
+ for (const cb of this.dataListeners) {
2136
+ cb(msg);
2137
+ }
2138
+ }
2139
+ onError(err) {
2140
+ for (const cb of this.errorListeners) {
2141
+ cb(err);
2142
+ }
2143
+ }
2144
+ onClose() {
2145
+ for (const cb of this.closeListeners) {
2146
+ cb();
2147
+ }
2148
+ this.telemetry?.span.end();
2149
+ }
2150
+ /**
2151
+ * Handle adding a callback for when a message is received.
2152
+ * @param msg The message that was received.
2153
+ */
2154
+ addDataListener(cb) {
2155
+ this._dataListeners.add(cb);
2156
+ }
2157
+ removeDataListener(cb) {
2158
+ this._dataListeners.delete(cb);
2159
+ }
2160
+ /**
2161
+ * Handle adding a callback for when the connection is closed.
2162
+ * This should also be called if an error happens and after notifying all the error listeners.
2163
+ * @param cb The callback to call when the connection is closed.
2164
+ */
2165
+ addCloseListener(cb) {
2166
+ this._closeListeners.add(cb);
2167
+ }
2168
+ removeCloseListener(cb) {
2169
+ this._closeListeners.delete(cb);
2170
+ }
2171
+ /**
2172
+ * Handle adding a callback for when an error is received.
2173
+ * This should only be used for this.logging errors, all cleanup
2174
+ * should be delegated to addCloseListener.
2175
+ *
2176
+ * The implementer should take care such that the implemented
2177
+ * connection will call both the close and error callbacks
2178
+ * on an error.
2179
+ *
2180
+ * @param cb The callback to call when an error is received.
2181
+ */
2182
+ addErrorListener(cb) {
2183
+ this._errorListeners.add(cb);
2184
+ }
2185
+ removeErrorListener(cb) {
2186
+ this._errorListeners.delete(cb);
2187
+ }
2188
+ };
2189
+
2190
+ // codec/adapter.ts
2191
+ var CodecMessageAdapter = class {
2192
+ constructor(codec) {
2193
+ this.codec = codec;
2194
+ }
2195
+ toBuffer(msg) {
2196
+ try {
2197
+ return {
2198
+ ok: true,
2199
+ value: this.codec.toBuffer(msg)
2200
+ };
2201
+ } catch (e) {
2202
+ return {
2203
+ ok: false,
2204
+ reason: coerceErrorString(e)
2205
+ };
2206
+ }
2207
+ }
2208
+ fromBuffer(buf) {
2209
+ try {
2210
+ const parsedMsg = this.codec.fromBuffer(buf);
2211
+ if (!import_value3.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
2212
+ return {
2213
+ ok: false,
2214
+ reason: "transport message schema mismatch"
2215
+ };
2216
+ }
2217
+ return {
2218
+ ok: true,
2219
+ value: parsedMsg
2220
+ };
2221
+ } catch (e) {
2222
+ return {
2223
+ ok: false,
2224
+ reason: coerceErrorString(e)
2225
+ };
2226
+ }
2227
+ }
2228
+ };
2229
+
2230
+ // transport/sessionStateMachine/transitions.ts
2231
+ function inheritSharedSession(session) {
2232
+ return {
2233
+ id: session.id,
2234
+ from: session.from,
2235
+ to: session.to,
2236
+ seq: session.seq,
2237
+ ack: session.ack,
2238
+ seqSent: session.seqSent,
2239
+ sendBuffer: session.sendBuffer,
2240
+ telemetry: session.telemetry,
2241
+ options: session.options,
2242
+ log: session.log,
2243
+ tracer: session.tracer,
2244
+ protocolVersion: session.protocolVersion,
2245
+ codec: session.codec
2246
+ };
2247
+ }
2248
+ function inheritSharedSessionWithGrace(session) {
2249
+ return {
2250
+ ...inheritSharedSession(session),
2251
+ graceExpiryTime: session.graceExpiryTime
2252
+ };
2253
+ }
2254
+ var SessionStateGraph = {
2255
+ entrypoints: {
2256
+ NoConnection: (to, from, listeners, options, protocolVersion, tracer, log) => {
2257
+ const id = `session-${generateId()}`;
2258
+ const telemetry = createSessionTelemetryInfo(tracer, id, to, from);
2259
+ const sendBuffer = [];
2260
+ const session = new SessionNoConnection({
2261
+ listeners,
2262
+ id,
2263
+ from,
2264
+ to,
2265
+ seq: 0,
2266
+ ack: 0,
2267
+ seqSent: 0,
2268
+ graceExpiryTime: Date.now() + options.sessionDisconnectGraceMs,
2269
+ sendBuffer,
2270
+ telemetry,
2271
+ options,
2272
+ protocolVersion,
2273
+ tracer,
2274
+ log,
2275
+ codec: new CodecMessageAdapter(options.codec)
2276
+ });
2277
+ session.log?.info(`session ${session.id} created in NoConnection state`, {
2278
+ ...session.loggingMetadata,
2279
+ tags: ["state-transition"]
2280
+ });
2281
+ return session;
2282
+ },
2283
+ WaitingForHandshake: (from, conn, listeners, options, tracer, log) => {
2284
+ const session = new SessionWaitingForHandshake({
2285
+ conn,
2286
+ listeners,
2287
+ from,
2288
+ options,
2289
+ tracer,
2290
+ log,
2291
+ codec: new CodecMessageAdapter(options.codec)
2292
+ });
2293
+ session.log?.info(`session created in WaitingForHandshake state`, {
2294
+ ...session.loggingMetadata,
2295
+ tags: ["state-transition"]
2296
+ });
2297
+ return session;
2298
+ }
2299
+ },
2300
+ // All of the transitions 'move'/'consume' the old session and return a new one.
2301
+ // After a session is transitioned, any usage of the old session will throw.
2302
+ transition: {
2303
+ // happy path transitions
2304
+ NoConnectionToBackingOff: (oldSession, backoffMs, listeners) => {
2305
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2306
+ oldSession._handleStateExit();
2307
+ const session = new SessionBackingOff({
2308
+ backoffMs,
2309
+ listeners,
2310
+ ...carriedState
2311
+ });
2312
+ session.log?.info(
2313
+ `session ${session.id} transition from NoConnection to BackingOff`,
2314
+ {
2315
+ ...session.loggingMetadata,
2316
+ tags: ["state-transition"]
2317
+ }
2318
+ );
2319
+ return session;
2320
+ },
2321
+ BackingOffToConnecting: (oldSession, connPromise, listeners) => {
2322
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2323
+ oldSession._handleStateExit();
2324
+ const session = new SessionConnecting({
2325
+ connPromise,
2326
+ listeners,
2327
+ ...carriedState
2328
+ });
2329
+ session.log?.info(
2330
+ `session ${session.id} transition from BackingOff to Connecting`,
2331
+ {
2332
+ ...session.loggingMetadata,
2333
+ tags: ["state-transition"]
2334
+ }
2335
+ );
2336
+ return session;
2337
+ },
2338
+ ConnectingToHandshaking: (oldSession, conn, listeners) => {
2339
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2340
+ oldSession._handleStateExit();
2341
+ const session = new SessionHandshaking({
2342
+ conn,
2343
+ listeners,
2344
+ ...carriedState
2345
+ });
2346
+ conn.telemetry = createConnectionTelemetryInfo(
2347
+ session.tracer,
2348
+ conn,
2349
+ session.telemetry
2350
+ );
2351
+ session.log?.info(
2352
+ `session ${session.id} transition from Connecting to Handshaking`,
2353
+ {
2354
+ ...session.loggingMetadata,
2355
+ tags: ["state-transition"]
2356
+ }
2357
+ );
2358
+ return session;
2359
+ },
2360
+ HandshakingToConnected: (oldSession, listeners) => {
2361
+ const carriedState = inheritSharedSession(oldSession);
2362
+ const conn = oldSession.conn;
2363
+ oldSession._handleStateExit();
2364
+ const session = new SessionConnected({
2365
+ conn,
2366
+ listeners,
2367
+ ...carriedState
2368
+ });
2369
+ session.startMissingHeartbeatTimeout();
2370
+ session.log?.info(
2371
+ `session ${session.id} transition from Handshaking to Connected`,
2372
+ {
2373
+ ...session.loggingMetadata,
2374
+ tags: ["state-transition"]
2375
+ }
2376
+ );
2377
+ return session;
2378
+ },
2379
+ WaitingForHandshakeToConnected: (pendingSession, oldSession, sessionId, to, propagationCtx, listeners, protocolVersion) => {
2380
+ const conn = pendingSession.conn;
2381
+ const { from, options } = pendingSession;
2382
+ const carriedState = oldSession ? (
2383
+ // old session exists, inherit state
2384
+ inheritSharedSession(oldSession)
2385
+ ) : (
2386
+ // old session does not exist, create new state
2387
+ {
2388
+ id: sessionId,
2389
+ from,
2390
+ to,
2391
+ seq: 0,
2392
+ ack: 0,
2393
+ seqSent: 0,
2394
+ sendBuffer: [],
2395
+ telemetry: createSessionTelemetryInfo(
2396
+ pendingSession.tracer,
2397
+ sessionId,
2398
+ to,
2399
+ from,
2400
+ propagationCtx
2401
+ ),
2402
+ options,
2403
+ tracer: pendingSession.tracer,
2404
+ log: pendingSession.log,
2405
+ protocolVersion,
2406
+ codec: new CodecMessageAdapter(options.codec)
2407
+ }
2408
+ );
2409
+ pendingSession._handleStateExit();
2410
+ oldSession?._handleStateExit();
2411
+ const session = new SessionConnected({
2412
+ conn,
2413
+ listeners,
2414
+ ...carriedState
2415
+ });
2416
+ session.startMissingHeartbeatTimeout();
2417
+ conn.telemetry = createConnectionTelemetryInfo(
2418
+ session.tracer,
2419
+ conn,
2420
+ session.telemetry
2421
+ );
2422
+ session.log?.info(
2423
+ `session ${session.id} transition from WaitingForHandshake to Connected`,
2424
+ {
2425
+ ...session.loggingMetadata,
2426
+ tags: ["state-transition"]
2427
+ }
2428
+ );
2429
+ return session;
2430
+ },
2431
+ // disconnect paths
2432
+ BackingOffToNoConnection: (oldSession, listeners) => {
2433
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2434
+ oldSession._handleStateExit();
2435
+ const session = new SessionNoConnection({
2436
+ listeners,
2437
+ ...carriedState
2438
+ });
2439
+ session.log?.info(
2440
+ `session ${session.id} transition from BackingOff to NoConnection`,
2441
+ {
2442
+ ...session.loggingMetadata,
2443
+ tags: ["state-transition"]
2444
+ }
2445
+ );
2446
+ return session;
2447
+ },
2448
+ ConnectingToNoConnection: (oldSession, listeners) => {
2449
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2450
+ oldSession.bestEffortClose();
2451
+ oldSession._handleStateExit();
2452
+ const session = new SessionNoConnection({
2453
+ listeners,
2454
+ ...carriedState
2455
+ });
2456
+ session.log?.info(
2457
+ `session ${session.id} transition from Connecting to NoConnection`,
2306
2458
  {
2307
2459
  ...session.loggingMetadata,
2308
- connectedTo: msg.from,
2309
- sessionId: msg.payload.sessionId
2460
+ tags: ["state-transition"]
2310
2461
  }
2311
2462
  );
2312
- this.deleteSession(oldSession);
2313
- oldSession = void 0;
2314
- }
2315
- if (!oldSession && (clientNextSentSeq > 0 || clientNextExpectedSeq > 0)) {
2316
- connectCase = "unknown session";
2317
- const rejectionMessage = this.options.enableTransparentSessionReconnects ? `client is trying to reconnect to a session the server don't know about: ${msg.payload.sessionId}` : `client is attempting a transparent reconnect to a session but the server does not support it: ${msg.payload.sessionId}`;
2318
- this.rejectHandshakeRequest(
2319
- session,
2320
- msg.from,
2321
- rejectionMessage,
2322
- "SESSION_STATE_MISMATCH",
2463
+ return session;
2464
+ },
2465
+ HandshakingToNoConnection: (oldSession, listeners) => {
2466
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
2467
+ oldSession.conn.close();
2468
+ oldSession._handleStateExit();
2469
+ const session = new SessionNoConnection({
2470
+ listeners,
2471
+ ...carriedState
2472
+ });
2473
+ session.log?.info(
2474
+ `session ${session.id} transition from Handshaking to NoConnection`,
2323
2475
  {
2324
2476
  ...session.loggingMetadata,
2325
- connectedTo: msg.from,
2326
- transportMessage: msg
2477
+ tags: ["state-transition"]
2327
2478
  }
2328
2479
  );
2329
- return;
2330
- }
2331
- const sessionId = msg.payload.sessionId;
2332
- this.log?.info(
2333
- `handshake from ${msg.from} ok (${connectCase}), responding with handshake success`,
2334
- {
2335
- ...session.loggingMetadata,
2336
- connectedTo: msg.from
2337
- }
2338
- );
2339
- const responseMsg = handshakeResponseMessage({
2340
- from: this.clientId,
2341
- to: msg.from,
2342
- status: {
2343
- ok: true,
2344
- sessionId
2345
- }
2346
- });
2347
- session.sendHandshake(responseMsg);
2348
- const connectedSession = ServerSessionStateGraph.transition.WaitingForHandshakeToConnected(
2349
- session,
2350
- // by this point oldSession is either no connection or we dont have an old session
2351
- oldSession,
2352
- sessionId,
2353
- msg.from,
2354
- msg.tracing,
2355
- {
2356
- onConnectionErrored: (err) => {
2357
- const errStr = coerceErrorString(err);
2358
- this.log?.warn(
2359
- `connection to ${connectedSession.to} errored: ${errStr}`,
2360
- connectedSession.loggingMetadata
2361
- );
2362
- },
2363
- onConnectionClosed: () => {
2364
- this.log?.info(
2365
- `connection to ${connectedSession.to} closed`,
2366
- connectedSession.loggingMetadata
2367
- );
2368
- this.onConnClosed(connectedSession);
2369
- },
2370
- onMessage: (msg2) => {
2371
- this.handleMsg(msg2);
2372
- },
2373
- onInvalidMessage: (reason) => {
2374
- this.protocolError({
2375
- type: ProtocolError.InvalidMessage,
2376
- message: reason
2377
- });
2378
- this.deleteSession(connectedSession, { unhealthy: true });
2480
+ return session;
2481
+ },
2482
+ ConnectedToNoConnection: (oldSession, listeners) => {
2483
+ const carriedState = inheritSharedSession(oldSession);
2484
+ const graceExpiryTime = Date.now() + oldSession.options.sessionDisconnectGraceMs;
2485
+ oldSession.conn.close();
2486
+ oldSession._handleStateExit();
2487
+ const session = new SessionNoConnection({
2488
+ listeners,
2489
+ graceExpiryTime,
2490
+ ...carriedState
2491
+ });
2492
+ session.log?.info(
2493
+ `session ${session.id} transition from Connected to NoConnection`,
2494
+ {
2495
+ ...session.loggingMetadata,
2496
+ tags: ["state-transition"]
2379
2497
  }
2380
- },
2381
- gotVersion
2382
- );
2383
- this.sessionHandshakeMetadata.set(connectedSession.to, parsedMetadata);
2384
- if (oldSession) {
2385
- this.updateSession(connectedSession);
2386
- } else {
2387
- this.createSession(connectedSession);
2498
+ );
2499
+ return session;
2388
2500
  }
2389
- this.pendingSessions.delete(session);
2390
- connectedSession.startActiveHeartbeat();
2501
+ }
2502
+ };
2503
+ var transitions = SessionStateGraph.transition;
2504
+ var ClientSessionStateGraph = {
2505
+ entrypoint: SessionStateGraph.entrypoints.NoConnection,
2506
+ transition: {
2507
+ // happy paths
2508
+ // NoConnection -> BackingOff: attempt to connect
2509
+ NoConnectionToBackingOff: transitions.NoConnectionToBackingOff,
2510
+ // BackingOff -> Connecting: backoff period elapsed, start connection
2511
+ BackingOffToConnecting: transitions.BackingOffToConnecting,
2512
+ // Connecting -> Handshaking: connection established, start handshake
2513
+ ConnectingToHandshaking: transitions.ConnectingToHandshaking,
2514
+ // Handshaking -> Connected: handshake complete, session ready
2515
+ HandshakingToConnected: transitions.HandshakingToConnected,
2516
+ // disconnect paths
2517
+ // BackingOff -> NoConnection: unused
2518
+ BackingOffToNoConnection: transitions.BackingOffToNoConnection,
2519
+ // Connecting -> NoConnection: connection failed or connection timeout
2520
+ ConnectingToNoConnection: transitions.ConnectingToNoConnection,
2521
+ // Handshaking -> NoConnection: connection closed or handshake timeout
2522
+ HandshakingToNoConnection: transitions.HandshakingToNoConnection,
2523
+ // Connected -> NoConnection: connection closed
2524
+ ConnectedToNoConnection: transitions.ConnectedToNoConnection
2525
+ // destroy/close paths
2526
+ // NoConnection -> x: grace period elapsed
2527
+ // BackingOff -> x: grace period elapsed
2528
+ // Connecting -> x: grace period elapsed
2529
+ // Handshaking -> x: grace period elapsed or invalid handshake message or handshake rejection
2530
+ // Connected -> x: grace period elapsed or invalid message
2531
+ }
2532
+ };
2533
+ var ServerSessionStateGraph = {
2534
+ entrypoint: SessionStateGraph.entrypoints.WaitingForHandshake,
2535
+ transition: {
2536
+ // happy paths
2537
+ // WaitingForHandshake -> Connected: handshake complete, session ready
2538
+ WaitingForHandshakeToConnected: transitions.WaitingForHandshakeToConnected,
2539
+ // disconnect paths
2540
+ // Connected -> NoConnection: connection closed
2541
+ ConnectedToNoConnection: transitions.ConnectedToNoConnection
2542
+ // destroy/close paths
2543
+ // WaitingForHandshake -> x: handshake timeout elapsed or invalid handshake message or handshake rejection or connection closed
2391
2544
  }
2392
2545
  };
2393
2546
 
@@ -2477,6 +2630,16 @@ function duplexPair() {
2477
2630
  const side1 = new DuplexSide();
2478
2631
  side0[kInitOtherSide](side1);
2479
2632
  side1[kInitOtherSide](side0);
2633
+ side0.on("close", () => {
2634
+ setImmediate(() => {
2635
+ side1.destroy();
2636
+ });
2637
+ });
2638
+ side1.on("close", () => {
2639
+ setImmediate(() => {
2640
+ side0.destroy();
2641
+ });
2642
+ });
2480
2643
  return [side0, side1];
2481
2644
  }
2482
2645
 
@@ -2589,14 +2752,14 @@ function createMockTransportNetwork(opts) {
2589
2752
  transport.close();
2590
2753
  }
2591
2754
  for (const conn of Object.values(connections.get())) {
2592
- conn.serverToClient.end();
2593
- conn.clientToServer.end();
2755
+ conn.serverToClient.destroy();
2756
+ conn.clientToServer.destroy();
2594
2757
  }
2595
2758
  },
2596
2759
  cleanup() {
2597
2760
  for (const conn of Object.values(connections.get())) {
2598
- conn.serverToClient.end();
2599
- conn.clientToServer.end();
2761
+ conn.serverToClient.destroy();
2762
+ conn.clientToServer.destroy();
2600
2763
  }
2601
2764
  }
2602
2765
  };