@replit/river 0.16.1 → 0.17.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.
Files changed (39) hide show
  1. package/README.md +25 -11
  2. package/dist/{chunk-Q6WPGM3K.js → chunk-7IQO434V.js} +11 -3
  3. package/dist/{chunk-7SPCAA6Q.js → chunk-LQMPJI3S.js} +101 -97
  4. package/dist/{chunk-GFRAOY75.js → chunk-VH3NGOXQ.js} +8 -17
  5. package/dist/{chunk-L65XWBX2.js → chunk-VJRLJ3JU.js} +1 -1
  6. package/dist/{chunk-XLJGKNV2.js → chunk-Y6DLSCKU.js} +1 -1
  7. package/dist/{connection-94896f3b.d.ts → connection-0767dc6b.d.ts} +1 -1
  8. package/dist/{connection-99346822.d.ts → connection-f31edbcd.d.ts} +1 -1
  9. package/dist/{index-2e402bb8.d.ts → index-8df0bdfb.d.ts} +27 -31
  10. package/dist/{procedures-f0226890.d.ts → procedures-b5ddb54d.d.ts} +1 -1
  11. package/dist/router/index.cjs +12 -4
  12. package/dist/router/index.d.cts +8 -4
  13. package/dist/router/index.d.ts +8 -4
  14. package/dist/router/index.js +2 -2
  15. package/dist/transport/impls/uds/client.cjs +80 -69
  16. package/dist/transport/impls/uds/client.d.cts +2 -2
  17. package/dist/transport/impls/uds/client.d.ts +2 -2
  18. package/dist/transport/impls/uds/client.js +3 -3
  19. package/dist/transport/impls/uds/server.cjs +82 -94
  20. package/dist/transport/impls/uds/server.d.cts +2 -2
  21. package/dist/transport/impls/uds/server.d.ts +2 -2
  22. package/dist/transport/impls/uds/server.js +3 -3
  23. package/dist/transport/impls/ws/client.cjs +80 -69
  24. package/dist/transport/impls/ws/client.d.cts +2 -2
  25. package/dist/transport/impls/ws/client.d.ts +2 -2
  26. package/dist/transport/impls/ws/client.js +3 -3
  27. package/dist/transport/impls/ws/server.cjs +82 -94
  28. package/dist/transport/impls/ws/server.d.cts +2 -2
  29. package/dist/transport/impls/ws/server.d.ts +2 -2
  30. package/dist/transport/impls/ws/server.js +3 -3
  31. package/dist/transport/index.cjs +107 -115
  32. package/dist/transport/index.d.cts +1 -1
  33. package/dist/transport/index.d.ts +1 -1
  34. package/dist/transport/index.js +2 -2
  35. package/dist/util/testHelpers.cjs +45 -18
  36. package/dist/util/testHelpers.d.cts +4 -9
  37. package/dist/util/testHelpers.d.ts +4 -9
  38. package/dist/util/testHelpers.js +8 -14
  39. package/package.json +1 -1
@@ -55,18 +55,18 @@ var ControlMessageAckSchema = import_typebox.Type.Object({
55
55
  var ControlMessageCloseSchema = import_typebox.Type.Object({
56
56
  type: import_typebox.Type.Literal("CLOSE")
57
57
  });
58
- var PROTOCOL_VERSION = "v1";
58
+ var PROTOCOL_VERSION = "v1.1";
59
59
  var ControlMessageHandshakeRequestSchema = import_typebox.Type.Object({
60
60
  type: import_typebox.Type.Literal("HANDSHAKE_REQ"),
61
61
  protocolVersion: import_typebox.Type.String(),
62
- instanceId: import_typebox.Type.String()
62
+ sessionId: import_typebox.Type.String()
63
63
  });
64
64
  var ControlMessageHandshakeResponseSchema = import_typebox.Type.Object({
65
65
  type: import_typebox.Type.Literal("HANDSHAKE_RESP"),
66
66
  status: import_typebox.Type.Union([
67
67
  import_typebox.Type.Object({
68
68
  ok: import_typebox.Type.Literal(true),
69
- instanceId: import_typebox.Type.String()
69
+ sessionId: import_typebox.Type.String()
70
70
  }),
71
71
  import_typebox.Type.Object({
72
72
  ok: import_typebox.Type.Literal(false),
@@ -83,7 +83,7 @@ var ControlMessagePayloadSchema = import_typebox.Type.Union([
83
83
  var OpaqueTransportMessageSchema = TransportMessageSchema(
84
84
  import_typebox.Type.Unknown()
85
85
  );
86
- function handshakeRequestMessage(from, to, instanceId) {
86
+ function handshakeRequestMessage(from, to, sessionId) {
87
87
  return {
88
88
  id: (0, import_nanoid.nanoid)(),
89
89
  from,
@@ -95,11 +95,11 @@ function handshakeRequestMessage(from, to, instanceId) {
95
95
  payload: {
96
96
  type: "HANDSHAKE_REQ",
97
97
  protocolVersion: PROTOCOL_VERSION,
98
- instanceId
98
+ sessionId
99
99
  }
100
100
  };
101
101
  }
102
- function handshakeResponseMessage(from, instanceId, to, ok, reason) {
102
+ function handshakeResponseMessage(from, to, status) {
103
103
  return {
104
104
  id: (0, import_nanoid.nanoid)(),
105
105
  from,
@@ -108,18 +108,9 @@ function handshakeResponseMessage(from, instanceId, to, ok, reason) {
108
108
  ack: 0,
109
109
  streamId: (0, import_nanoid.nanoid)(),
110
110
  controlFlags: 0,
111
- payload: ok ? {
112
- type: "HANDSHAKE_RESP",
113
- status: {
114
- ok: true,
115
- instanceId
116
- }
117
- } : {
111
+ payload: {
118
112
  type: "HANDSHAKE_RESP",
119
- status: {
120
- ok: false,
121
- reason: reason ?? "Unknown reason"
122
- }
113
+ status
123
114
  }
124
115
  };
125
116
  }
@@ -134,7 +125,8 @@ var log;
134
125
  var ProtocolError = {
135
126
  RetriesExceeded: "conn_retry_exceeded",
136
127
  HandshakeFailed: "handshake_failed",
137
- UseAfterDestroy: "use_after_destroy"
128
+ UseAfterDestroy: "use_after_destroy",
129
+ MessageOrderingViolated: "message_ordering_violated"
138
130
  };
139
131
  var EventDispatcher = class {
140
132
  eventListeners = {};
@@ -189,7 +181,12 @@ var Session = class {
189
181
  /**
190
182
  * The unique ID of this session.
191
183
  */
192
- debugId;
184
+ id;
185
+ /**
186
+ * What the other side advertised as their session ID
187
+ * for this session.
188
+ */
189
+ advertisedSessionId;
193
190
  /**
194
191
  * Number of messages we've sent along this session (excluding handshake and acks)
195
192
  */
@@ -211,11 +208,11 @@ var Session = class {
211
208
  * The interval for sending heartbeats.
212
209
  */
213
210
  heartbeat;
214
- constructor(from, connectedTo, conn, options) {
211
+ constructor(conn, from, to, options) {
212
+ this.id = `session-${nanoid2(12)}`;
215
213
  this.options = options;
216
- this.debugId = `sess-${unsafeId()}`;
217
214
  this.from = from;
218
- this.to = connectedTo;
215
+ this.to = to;
219
216
  this.connection = conn;
220
217
  this.codec = options.codec;
221
218
  this.heartbeatMisses = 0;
@@ -255,7 +252,7 @@ var Session = class {
255
252
  log?.info(
256
253
  `${this.from} -- closing connection (id: ${this.connection.debugId}) to ${this.to} due to inactivity`
257
254
  );
258
- this.closeStaleConnection(this.connection);
255
+ this.closeStaleConnection();
259
256
  }
260
257
  return;
261
258
  }
@@ -286,33 +283,37 @@ var Session = class {
286
283
  log?.debug(`${this.from} -- resending ${msg.id} (seq: ${msg.seq})`);
287
284
  const ok = this.connection.send(this.codec.toBuffer(msg));
288
285
  if (!ok) {
289
- const msg2 = `${this.from} -- failed to send buffered message to ${this.to} in session (id: ${this.debugId}) (if you hit this code path something is seriously wrong)`;
286
+ const msg2 = `${this.from} -- failed to send buffered message to ${this.to} in session (id: ${this.id}) (if you hit this code path something is seriously wrong)`;
290
287
  log?.error(msg2);
291
288
  throw new Error(msg2);
292
289
  }
293
290
  }
294
291
  }
295
292
  updateBookkeeping(ack, seq) {
293
+ if (seq + 1 < this.ack) {
294
+ log?.error(`${this.from} -- received stale seq ${seq} + 1 < ${this.ack}`);
295
+ return;
296
+ }
296
297
  this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq > ack);
297
298
  this.ack = seq + 1;
298
299
  }
299
300
  closeStaleConnection(conn) {
300
- if (!this.connection || this.connection !== conn)
301
+ if (this.connection === void 0 || this.connection === conn)
301
302
  return;
302
303
  log?.info(
303
- `${this.from} -- closing old inner connection (id: ${this.connection.debugId}) from session (id: ${this.debugId}) to ${this.to}`
304
+ `${this.from} -- closing old inner connection (id: ${this.connection.debugId}) from session (id: ${this.id}) to ${this.to}`
304
305
  );
305
306
  this.connection.close();
306
307
  this.connection = void 0;
307
308
  }
308
309
  replaceWithNewConnection(newConn) {
309
- this.closeStaleConnection(this.connection);
310
+ this.closeStaleConnection(newConn);
310
311
  this.cancelGrace();
311
312
  this.connection = newConn;
312
313
  }
313
314
  beginGrace(cb) {
314
315
  log?.info(
315
- `${this.from} -- starting ${this.options.sessionDisconnectGraceMs}ms grace period until session (id: ${this.debugId}) to ${this.to} is closed`
316
+ `${this.from} -- starting ${this.options.sessionDisconnectGraceMs}ms grace period until session (id: ${this.id}) to ${this.to} is closed`
316
317
  );
317
318
  this.disconnectionGrace = setTimeout(() => {
318
319
  this.close();
@@ -323,11 +324,12 @@ var Session = class {
323
324
  cancelGrace() {
324
325
  this.heartbeatMisses = 0;
325
326
  clearTimeout(this.disconnectionGrace);
327
+ this.disconnectionGrace = void 0;
326
328
  }
327
329
  // closed when we want to discard the whole session
328
330
  // (i.e. shutdown or session disconnect)
329
331
  close() {
330
- this.closeStaleConnection(this.connection);
332
+ this.closeStaleConnection();
331
333
  this.cancelGrace();
332
334
  this.resetBufferedMessages();
333
335
  clearInterval(this.heartbeat);
@@ -356,9 +358,6 @@ var Session = class {
356
358
  }
357
359
  };
358
360
 
359
- // transport/transport.ts
360
- var import_nanoid3 = require("nanoid");
361
-
362
361
  // util/stringify.ts
363
362
  function coerceErrorString(err) {
364
363
  if (err instanceof Error) {
@@ -505,17 +504,10 @@ var defaultConnectionRetryOptions = {
505
504
  budgetRestoreIntervalMs: 200
506
505
  };
507
506
  var defaultClientTransportOptions = {
508
- connectionRetryOptions: defaultConnectionRetryOptions,
509
- ...defaultTransportOptions
507
+ ...defaultTransportOptions,
508
+ ...defaultConnectionRetryOptions
510
509
  };
511
510
  var Transport = class {
512
- /**
513
- * Unique per instance of the transport.
514
- * This allows us to distinguish reconnects to different
515
- * transports.
516
- */
517
- instanceId = (0, import_nanoid3.nanoid)();
518
- connectedInstanceIds = /* @__PURE__ */ new Map();
519
511
  /**
520
512
  * A flag indicating whether the transport has been destroyed.
521
513
  * A destroyed transport will not attempt to reconnect and cannot be used again.
@@ -568,41 +560,41 @@ var Transport = class {
568
560
  * and we know the identity of the connected client.
569
561
  * @param conn The connection object.
570
562
  */
571
- onConnect(conn, connectedTo, instanceId) {
563
+ onConnect(conn, connectedTo, advertisedSessionId) {
572
564
  this.eventDispatcher.dispatchEvent("connectionStatus", {
573
565
  status: "connect",
574
566
  conn
575
567
  });
576
568
  let oldSession = this.sessions.get(connectedTo);
577
- const lastInstanceId = this.connectedInstanceIds.get(connectedTo);
578
- if (oldSession && lastInstanceId !== void 0 && lastInstanceId !== instanceId) {
569
+ if (oldSession?.advertisedSessionId && oldSession.advertisedSessionId !== advertisedSessionId) {
579
570
  log?.warn(
580
- `${this.clientId} -- connection from ${connectedTo} is a different instance (got: ${instanceId}, last connected to: ${lastInstanceId}), starting a new session`
571
+ `${this.clientId} -- connection from ${connectedTo} is a different session (id: ${advertisedSessionId}, last connected to: ${oldSession.advertisedSessionId}), starting a new session`
581
572
  );
582
573
  oldSession.close();
583
574
  this.deleteSession(oldSession);
584
575
  oldSession = void 0;
585
576
  }
586
- this.connectedInstanceIds.set(connectedTo, instanceId);
587
577
  if (oldSession === void 0) {
588
578
  const newSession = this.createSession(connectedTo, conn);
579
+ newSession.advertisedSessionId = advertisedSessionId;
589
580
  log?.info(
590
- `${this.clientId} -- new connection (id: ${conn.debugId}) for new session (id: ${newSession.debugId}) to ${connectedTo}`
581
+ `${this.clientId} -- new connection (id: ${conn.debugId}) for new session (id: ${newSession.id}) to ${connectedTo}`
591
582
  );
592
583
  return newSession;
593
584
  }
594
585
  log?.info(
595
- `${this.clientId} -- new connection (id: ${conn.debugId}) for existing session (id: ${oldSession.debugId}) to ${connectedTo}`
586
+ `${this.clientId} -- new connection (id: ${conn.debugId}) for existing session (id: ${oldSession.id}) to ${connectedTo}`
596
587
  );
597
588
  oldSession.replaceWithNewConnection(conn);
598
589
  oldSession.sendBufferedMessages();
590
+ oldSession.advertisedSessionId = advertisedSessionId;
599
591
  return oldSession;
600
592
  }
601
- createSession(connectedTo, conn) {
593
+ createSession(to, conn) {
602
594
  const session = new Session(
603
- this.clientId,
604
- connectedTo,
605
595
  conn,
596
+ this.clientId,
597
+ to,
606
598
  this.options
607
599
  );
608
600
  this.sessions.set(session.to, session);
@@ -612,10 +604,20 @@ var Transport = class {
612
604
  });
613
605
  return session;
614
606
  }
607
+ getOrCreateSession(to, conn) {
608
+ let session = this.sessions.get(to);
609
+ if (!session) {
610
+ session = this.createSession(to, conn);
611
+ log?.info(
612
+ `${this.clientId} -- no session for ${to}, created a new one (id: ${session.id})`
613
+ );
614
+ }
615
+ return session;
616
+ }
615
617
  deleteSession(session) {
616
618
  this.sessions.delete(session.to);
617
619
  log?.info(
618
- `${this.clientId} -- session ${session.debugId} disconnect from ${session.to}`
620
+ `${this.clientId} -- session ${session.id} disconnect from ${session.to}`
619
621
  );
620
622
  this.eventDispatcher.dispatchEvent("sessionStatus", {
621
623
  status: "disconnect",
@@ -683,11 +685,14 @@ var Transport = class {
683
685
  )}`
684
686
  );
685
687
  } else {
688
+ const errMsg = `received out-of-order msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq})`;
686
689
  log?.error(
687
- `${this.clientId} -- received out-of-order msg (got: ${msg.seq}, wanted: ${session.nextExpectedSeq}), marking connection as dead: ${JSON.stringify(msg)}`
690
+ `${this.clientId} -- fatal: ${errMsg}, marking connection as dead: ${JSON.stringify(
691
+ msg
692
+ )}`
688
693
  );
694
+ this.protocolError(ProtocolError.MessageOrderingViolated, errMsg);
689
695
  session.close();
690
- this.deleteSession(session);
691
696
  }
692
697
  return;
693
698
  }
@@ -734,14 +739,7 @@ var Transport = class {
734
739
  );
735
740
  return void 0;
736
741
  }
737
- let session = this.sessions.get(to);
738
- if (!session) {
739
- session = this.createSession(to, void 0);
740
- log?.info(
741
- `${this.clientId} -- no session for ${to}, created a new one (id: ${session.debugId})`
742
- );
743
- }
744
- return session.send(msg);
742
+ return this.getOrCreateSession(to).send(msg);
745
743
  }
746
744
  // control helpers
747
745
  sendCloseStream(to, streamId) {
@@ -793,7 +791,13 @@ var ClientTransport = class extends Transport {
793
791
  */
794
792
  inflightConnectionPromises;
795
793
  retryBudget;
796
- tryReconnecting = true;
794
+ /**
795
+ * A flag indicating whether the transport should automatically reconnect
796
+ * when a connection is dropped.
797
+ * Realistically, this should always be true for clients unless you are writing
798
+ * tests or a special case where you don't want to reconnect.
799
+ */
800
+ reconnectOnConnectionDrop = true;
797
801
  constructor(clientId, providedOptions) {
798
802
  super(clientId, providedOptions);
799
803
  this.options = {
@@ -801,21 +805,20 @@ var ClientTransport = class extends Transport {
801
805
  ...providedOptions
802
806
  };
803
807
  this.inflightConnectionPromises = /* @__PURE__ */ new Map();
804
- this.retryBudget = new LeakyBucketRateLimit(
805
- this.options.connectionRetryOptions
806
- );
808
+ this.retryBudget = new LeakyBucketRateLimit(this.options);
807
809
  }
808
810
  handleConnection(conn, to) {
809
811
  if (this.state !== "open")
810
812
  return;
811
813
  let session = void 0;
812
814
  const handshakeHandler = (data) => {
813
- const handshake = this.receiveHandshakeResponseMessage(data);
814
- if (!handshake) {
815
+ const maybeSession = this.receiveHandshakeResponseMessage(data, conn);
816
+ if (!maybeSession) {
815
817
  conn.close();
816
818
  return;
819
+ } else {
820
+ session = maybeSession;
817
821
  }
818
- session = this.onConnect(conn, handshake.from, handshake.instanceId);
819
822
  conn.removeDataListener(handshakeHandler);
820
823
  conn.addDataListener((data2) => {
821
824
  const parsed = this.parseMsg(data2);
@@ -835,7 +838,7 @@ var ClientTransport = class extends Transport {
835
838
  `${this.clientId} -- connection (id: ${conn.debugId}) to ${to} disconnected`
836
839
  );
837
840
  this.inflightConnectionPromises.delete(to);
838
- if (this.tryReconnecting) {
841
+ if (this.reconnectOnConnectionDrop) {
839
842
  void this.connect(to);
840
843
  }
841
844
  });
@@ -845,7 +848,7 @@ var ClientTransport = class extends Transport {
845
848
  );
846
849
  });
847
850
  }
848
- receiveHandshakeResponseMessage(data) {
851
+ receiveHandshakeResponseMessage(data, conn) {
849
852
  const parsed = this.parseMsg(data);
850
853
  if (!parsed) {
851
854
  this.protocolError(
@@ -878,12 +881,14 @@ var ClientTransport = class extends Transport {
878
881
  );
879
882
  return false;
880
883
  }
881
- const instanceId = parsed.payload.status.instanceId;
882
- log?.debug(
883
- `${this.clientId} -- handshake from ${parsed.from} ok (instance: ${instanceId})`
884
+ log?.debug(`${this.clientId} -- handshake from ${parsed.from} ok`);
885
+ const session = this.onConnect(
886
+ conn,
887
+ parsed.from,
888
+ parsed.payload.status.sessionId
884
889
  );
885
890
  this.retryBudget.startRestoringBudget(parsed.from);
886
- return { instanceId, from: parsed.from };
891
+ return session;
887
892
  }
888
893
  /**
889
894
  * Manually attempts to connect to a client.
@@ -941,7 +946,7 @@ var ClientTransport = class extends Transport {
941
946
  } catch (error) {
942
947
  this.inflightConnectionPromises.delete(to);
943
948
  const errStr = coerceErrorString(error);
944
- if (!this.tryReconnecting || !canProceedWithConnection()) {
949
+ if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {
945
950
  log?.warn(`${this.clientId} -- connection to ${to} failed (${errStr})`);
946
951
  } else {
947
952
  log?.warn(
@@ -952,11 +957,8 @@ var ClientTransport = class extends Transport {
952
957
  }
953
958
  }
954
959
  sendHandshake(to, conn) {
955
- const requestMsg = handshakeRequestMessage(
956
- this.clientId,
957
- to,
958
- this.instanceId
959
- );
960
+ const session = this.getOrCreateSession(to, conn);
961
+ const requestMsg = handshakeRequestMessage(this.clientId, to, session.id);
960
962
  log?.debug(`${this.clientId} -- sending handshake request to ${to}`);
961
963
  conn.send(this.codec.toBuffer(requestMsg));
962
964
  }
@@ -969,7 +971,7 @@ var ServerTransport = class extends Transport {
969
971
  constructor(clientId, providedOptions) {
970
972
  super(clientId, providedOptions);
971
973
  log?.info(
972
- `${this.clientId} -- initiated server transport (instance id: ${this.instanceId}, protocol: ${PROTOCOL_VERSION})`
974
+ `${this.clientId} -- initiated server transport (protocol: ${PROTOCOL_VERSION})`
973
975
  );
974
976
  }
975
977
  handleConnection(conn) {
@@ -981,12 +983,13 @@ var ServerTransport = class extends Transport {
981
983
  let session = void 0;
982
984
  const client = () => session?.to ?? "unknown";
983
985
  const handshakeHandler = (data) => {
984
- const handshake = this.receiveHandshakeRequestMessage(data, conn);
985
- if (!handshake) {
986
+ const maybeSession = this.receiveHandshakeRequestMessage(data, conn);
987
+ if (!maybeSession) {
986
988
  conn.close();
987
989
  return;
990
+ } else {
991
+ session = maybeSession;
988
992
  }
989
- session = this.onConnect(conn, handshake.from, handshake.instanceId);
990
993
  conn.removeDataListener(handshakeHandler);
991
994
  conn.addDataListener((data2) => {
992
995
  const parsed = this.parseMsg(data2);
@@ -1024,18 +1027,13 @@ var ServerTransport = class extends Transport {
1024
1027
  return false;
1025
1028
  }
1026
1029
  if (!import_value.Value.Check(ControlMessageHandshakeRequestSchema, parsed.payload)) {
1027
- const responseMsg2 = handshakeResponseMessage(
1028
- this.clientId,
1029
- this.instanceId,
1030
- parsed.from,
1031
- false
1032
- );
1030
+ const reason = "received invalid handshake msg";
1031
+ const responseMsg2 = handshakeResponseMessage(this.clientId, parsed.from, {
1032
+ ok: false,
1033
+ reason
1034
+ });
1033
1035
  conn.send(this.codec.toBuffer(responseMsg2));
1034
- log?.warn(
1035
- `${this.clientId} -- received invalid handshake msg: ${JSON.stringify(
1036
- parsed
1037
- )}`
1038
- );
1036
+ log?.warn(`${this.clientId} -- ${reason}: ${JSON.stringify(parsed)}`);
1039
1037
  this.protocolError(
1040
1038
  ProtocolError.HandshakeFailed,
1041
1039
  "invalid handshake request"
@@ -1044,34 +1042,28 @@ var ServerTransport = class extends Transport {
1044
1042
  }
1045
1043
  const gotVersion = parsed.payload.protocolVersion;
1046
1044
  if (gotVersion !== PROTOCOL_VERSION) {
1047
- const responseMsg2 = handshakeResponseMessage(
1048
- this.clientId,
1049
- this.instanceId,
1050
- parsed.from,
1051
- false
1052
- );
1045
+ const reason = `incorrect version (got: ${gotVersion} wanted ${PROTOCOL_VERSION})`;
1046
+ const responseMsg2 = handshakeResponseMessage(this.clientId, parsed.from, {
1047
+ ok: false,
1048
+ reason
1049
+ });
1053
1050
  conn.send(this.codec.toBuffer(responseMsg2));
1054
1051
  log?.warn(
1055
1052
  `${this.clientId} -- received handshake msg with incompatible protocol version (got: ${gotVersion}, expected: ${PROTOCOL_VERSION})`
1056
1053
  );
1057
- this.protocolError(
1058
- ProtocolError.HandshakeFailed,
1059
- `incorrect version (got: ${gotVersion} wanted ${PROTOCOL_VERSION})`
1060
- );
1054
+ this.protocolError(ProtocolError.HandshakeFailed, reason);
1061
1055
  return false;
1062
1056
  }
1063
- const instanceId = parsed.payload.instanceId;
1057
+ const session = this.getOrCreateSession(parsed.from, conn);
1064
1058
  log?.debug(
1065
- `${this.clientId} -- handshake from ${parsed.from} ok (instance id: ${instanceId}), responding with handshake success`
1066
- );
1067
- const responseMsg = handshakeResponseMessage(
1068
- this.clientId,
1069
- this.instanceId,
1070
- parsed.from,
1071
- true
1059
+ `${this.clientId} -- handshake from ${parsed.from} ok, responding with handshake success`
1072
1060
  );
1061
+ const responseMsg = handshakeResponseMessage(this.clientId, parsed.from, {
1062
+ ok: true,
1063
+ sessionId: session.id
1064
+ });
1073
1065
  conn.send(this.codec.toBuffer(responseMsg));
1074
- return { instanceId, from: parsed.from };
1066
+ return this.onConnect(conn, parsed.from, parsed.payload.sessionId);
1075
1067
  }
1076
1068
  };
1077
1069
  // Annotate the CommonJS export names for ESM import in node:
@@ -1,3 +1,3 @@
1
- export { a as ClientTransport, c as ClientTransportOptions, C as Connection, m as EventHandler, E as EventMap, l as EventTypes, O as OpaqueTransportMessage, h as OpaqueTransportMessageSchema, n as ProtocolError, o as ProtocolErrorType, S as ServerTransport, e as Session, T as Transport, b as TransportClientId, i as TransportMessage, g as TransportMessageSchema, d as TransportOptions, f as TransportStatus, k as isStreamClose, j as isStreamOpen } from '../index-2e402bb8.js';
1
+ export { a as ClientTransport, c as ClientTransportOptions, C as Connection, n as EventHandler, E as EventMap, m as EventTypes, O as OpaqueTransportMessage, i as OpaqueTransportMessageSchema, o as ProtocolError, p as ProtocolErrorType, d as ServerTransport, f as Session, T as Transport, b as TransportClientId, j as TransportMessage, h as TransportMessageSchema, e as TransportOptions, g as TransportStatus, l as isStreamClose, k as isStreamOpen } from '../index-8df0bdfb.js';
2
2
  import '../types-3e5768ec.js';
3
3
  import '@sinclair/typebox';
@@ -1,3 +1,3 @@
1
- export { a as ClientTransport, c as ClientTransportOptions, C as Connection, m as EventHandler, E as EventMap, l as EventTypes, O as OpaqueTransportMessage, h as OpaqueTransportMessageSchema, n as ProtocolError, o as ProtocolErrorType, S as ServerTransport, e as Session, T as Transport, b as TransportClientId, i as TransportMessage, g as TransportMessageSchema, d as TransportOptions, f as TransportStatus, k as isStreamClose, j as isStreamOpen } from '../index-2e402bb8.js';
1
+ export { a as ClientTransport, c as ClientTransportOptions, C as Connection, n as EventHandler, E as EventMap, m as EventTypes, O as OpaqueTransportMessage, i as OpaqueTransportMessageSchema, o as ProtocolError, p as ProtocolErrorType, d as ServerTransport, f as Session, T as Transport, b as TransportClientId, j as TransportMessage, h as TransportMessageSchema, e as TransportOptions, g as TransportStatus, l as isStreamClose, k as isStreamOpen } from '../index-8df0bdfb.js';
2
2
  import '../types-3e5768ec.js';
3
3
  import '@sinclair/typebox';
@@ -6,11 +6,11 @@ import {
6
6
  ServerTransport,
7
7
  Session,
8
8
  Transport
9
- } from "../chunk-7SPCAA6Q.js";
9
+ } from "../chunk-LQMPJI3S.js";
10
10
  import {
11
11
  OpaqueTransportMessageSchema,
12
12
  TransportMessageSchema
13
- } from "../chunk-GFRAOY75.js";
13
+ } from "../chunk-VH3NGOXQ.js";
14
14
  import "../chunk-H4BYJELI.js";
15
15
  import "../chunk-GZ7HCLLM.js";
16
16
  export {
@@ -49,6 +49,9 @@ module.exports = __toCommonJS(testHelpers_exports);
49
49
  var import_isomorphic_ws = __toESM(require("isomorphic-ws"), 1);
50
50
  var import_ws = require("ws");
51
51
 
52
+ // transport/transport.ts
53
+ var import_value = require("@sinclair/typebox/value");
54
+
52
55
  // logging/index.ts
53
56
  var log;
54
57
 
@@ -72,7 +75,12 @@ var Session = class {
72
75
  /**
73
76
  * The unique ID of this session.
74
77
  */
75
- debugId;
78
+ id;
79
+ /**
80
+ * What the other side advertised as their session ID
81
+ * for this session.
82
+ */
83
+ advertisedSessionId;
76
84
  /**
77
85
  * Number of messages we've sent along this session (excluding handshake and acks)
78
86
  */
@@ -94,11 +102,11 @@ var Session = class {
94
102
  * The interval for sending heartbeats.
95
103
  */
96
104
  heartbeat;
97
- constructor(from, connectedTo, conn, options) {
105
+ constructor(conn, from, to, options) {
106
+ this.id = `session-${nanoid(12)}`;
98
107
  this.options = options;
99
- this.debugId = `sess-${unsafeId()}`;
100
108
  this.from = from;
101
- this.to = connectedTo;
109
+ this.to = to;
102
110
  this.connection = conn;
103
111
  this.codec = options.codec;
104
112
  this.heartbeatMisses = 0;
@@ -138,7 +146,7 @@ var Session = class {
138
146
  log?.info(
139
147
  `${this.from} -- closing connection (id: ${this.connection.debugId}) to ${this.to} due to inactivity`
140
148
  );
141
- this.closeStaleConnection(this.connection);
149
+ this.closeStaleConnection();
142
150
  }
143
151
  return;
144
152
  }
@@ -169,33 +177,37 @@ var Session = class {
169
177
  log?.debug(`${this.from} -- resending ${msg.id} (seq: ${msg.seq})`);
170
178
  const ok = this.connection.send(this.codec.toBuffer(msg));
171
179
  if (!ok) {
172
- const msg2 = `${this.from} -- failed to send buffered message to ${this.to} in session (id: ${this.debugId}) (if you hit this code path something is seriously wrong)`;
180
+ const msg2 = `${this.from} -- failed to send buffered message to ${this.to} in session (id: ${this.id}) (if you hit this code path something is seriously wrong)`;
173
181
  log?.error(msg2);
174
182
  throw new Error(msg2);
175
183
  }
176
184
  }
177
185
  }
178
186
  updateBookkeeping(ack, seq) {
187
+ if (seq + 1 < this.ack) {
188
+ log?.error(`${this.from} -- received stale seq ${seq} + 1 < ${this.ack}`);
189
+ return;
190
+ }
179
191
  this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq > ack);
180
192
  this.ack = seq + 1;
181
193
  }
182
194
  closeStaleConnection(conn) {
183
- if (!this.connection || this.connection !== conn)
195
+ if (this.connection === void 0 || this.connection === conn)
184
196
  return;
185
197
  log?.info(
186
- `${this.from} -- closing old inner connection (id: ${this.connection.debugId}) from session (id: ${this.debugId}) to ${this.to}`
198
+ `${this.from} -- closing old inner connection (id: ${this.connection.debugId}) from session (id: ${this.id}) to ${this.to}`
187
199
  );
188
200
  this.connection.close();
189
201
  this.connection = void 0;
190
202
  }
191
203
  replaceWithNewConnection(newConn) {
192
- this.closeStaleConnection(this.connection);
204
+ this.closeStaleConnection(newConn);
193
205
  this.cancelGrace();
194
206
  this.connection = newConn;
195
207
  }
196
208
  beginGrace(cb) {
197
209
  log?.info(
198
- `${this.from} -- starting ${this.options.sessionDisconnectGraceMs}ms grace period until session (id: ${this.debugId}) to ${this.to} is closed`
210
+ `${this.from} -- starting ${this.options.sessionDisconnectGraceMs}ms grace period until session (id: ${this.id}) to ${this.to} is closed`
199
211
  );
200
212
  this.disconnectionGrace = setTimeout(() => {
201
213
  this.close();
@@ -206,11 +218,12 @@ var Session = class {
206
218
  cancelGrace() {
207
219
  this.heartbeatMisses = 0;
208
220
  clearTimeout(this.disconnectionGrace);
221
+ this.disconnectionGrace = void 0;
209
222
  }
210
223
  // closed when we want to discard the whole session
211
224
  // (i.e. shutdown or session disconnect)
212
225
  close() {
213
- this.closeStaleConnection(this.connection);
226
+ this.closeStaleConnection();
214
227
  this.cancelGrace();
215
228
  this.resetBufferedMessages();
216
229
  clearInterval(this.heartbeat);
@@ -299,6 +312,25 @@ var NaiveJsonCodec = {
299
312
  }
300
313
  };
301
314
 
315
+ // transport/transport.ts
316
+ var defaultTransportOptions = {
317
+ heartbeatIntervalMs: 1e3,
318
+ heartbeatsUntilDead: 2,
319
+ sessionDisconnectGraceMs: 5e3,
320
+ codec: NaiveJsonCodec
321
+ };
322
+ var defaultConnectionRetryOptions = {
323
+ baseIntervalMs: 250,
324
+ maxJitterMs: 200,
325
+ maxBackoffMs: 32e3,
326
+ attemptBudgetCapacity: 5,
327
+ budgetRestoreIntervalMs: 200
328
+ };
329
+ var defaultClientTransportOptions = {
330
+ ...defaultTransportOptions,
331
+ ...defaultConnectionRetryOptions
332
+ };
333
+
302
334
  // node_modules/p-defer/index.js
303
335
  function pDefer() {
304
336
  const deferred = {};
@@ -663,17 +695,12 @@ function catchProcError(err) {
663
695
  }
664
696
  };
665
697
  }
666
- var testingSessionOptions = {
667
- heartbeatIntervalMs: 1e3,
668
- heartbeatsUntilDead: 2,
669
- sessionDisconnectGraceMs: 5e3,
670
- codec: NaiveJsonCodec
671
- };
698
+ var testingSessionOptions = defaultTransportOptions;
672
699
  function dummyCtx(state, extendedContext) {
673
700
  const session = new Session(
701
+ void 0,
674
702
  "client",
675
703
  "SERVER",
676
- void 0,
677
704
  testingSessionOptions
678
705
  );
679
706
  return {