@replit/river 0.23.16 → 0.24.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 (85) hide show
  1. package/README.md +21 -20
  2. package/dist/{chunk-UDXM64QK.js → chunk-AASMR3CQ.js} +24 -18
  3. package/dist/chunk-AASMR3CQ.js.map +1 -0
  4. package/dist/chunk-JA57I7MG.js +653 -0
  5. package/dist/chunk-JA57I7MG.js.map +1 -0
  6. package/dist/chunk-KX5PQRVN.js +382 -0
  7. package/dist/chunk-KX5PQRVN.js.map +1 -0
  8. package/dist/{chunk-LTSLICON.js → chunk-KYYB4DUR.js} +68 -519
  9. package/dist/chunk-KYYB4DUR.js.map +1 -0
  10. package/dist/chunk-NLQPPDOT.js +399 -0
  11. package/dist/chunk-NLQPPDOT.js.map +1 -0
  12. package/dist/{chunk-TXSQRTZB.js → chunk-PJGGC3LV.js} +55 -41
  13. package/dist/chunk-PJGGC3LV.js.map +1 -0
  14. package/dist/chunk-RXJLI2OP.js +50 -0
  15. package/dist/chunk-RXJLI2OP.js.map +1 -0
  16. package/dist/{chunk-6LCL2ZZF.js → chunk-TAH2GVTJ.js} +1 -1
  17. package/dist/chunk-TAH2GVTJ.js.map +1 -0
  18. package/dist/chunk-ZAT3R4CU.js +277 -0
  19. package/dist/chunk-ZAT3R4CU.js.map +1 -0
  20. package/dist/{client-0926d3d6.d.ts → client-ba0d3315.d.ts} +12 -15
  21. package/dist/{connection-99a67d3e.d.ts → connection-c3a96d09.d.ts} +1 -5
  22. package/dist/connection-d33e3246.d.ts +11 -0
  23. package/dist/{handshake-75d0124f.d.ts → handshake-cdead82a.d.ts} +149 -180
  24. package/dist/logging/index.cjs.map +1 -1
  25. package/dist/logging/index.d.cts +1 -1
  26. package/dist/logging/index.d.ts +1 -1
  27. package/dist/logging/index.js +1 -1
  28. package/dist/{index-ea74cdbb.d.ts → message-e6c560fd.d.ts} +2 -2
  29. package/dist/router/index.cjs +107 -530
  30. package/dist/router/index.cjs.map +1 -1
  31. package/dist/router/index.d.cts +12 -50
  32. package/dist/router/index.d.ts +12 -50
  33. package/dist/router/index.js +2 -4
  34. package/dist/server-2ef5e6ec.d.ts +42 -0
  35. package/dist/{services-75e84a9f.d.ts → services-e1417b33.d.ts} +7 -7
  36. package/dist/transport/impls/uds/client.cjs +1242 -1223
  37. package/dist/transport/impls/uds/client.cjs.map +1 -1
  38. package/dist/transport/impls/uds/client.d.cts +4 -4
  39. package/dist/transport/impls/uds/client.d.ts +4 -4
  40. package/dist/transport/impls/uds/client.js +7 -13
  41. package/dist/transport/impls/uds/client.js.map +1 -1
  42. package/dist/transport/impls/uds/server.cjs +1301 -1151
  43. package/dist/transport/impls/uds/server.cjs.map +1 -1
  44. package/dist/transport/impls/uds/server.d.cts +4 -4
  45. package/dist/transport/impls/uds/server.d.ts +4 -4
  46. package/dist/transport/impls/uds/server.js +6 -6
  47. package/dist/transport/impls/ws/client.cjs +980 -969
  48. package/dist/transport/impls/ws/client.cjs.map +1 -1
  49. package/dist/transport/impls/ws/client.d.cts +4 -4
  50. package/dist/transport/impls/ws/client.d.ts +4 -4
  51. package/dist/transport/impls/ws/client.js +6 -7
  52. package/dist/transport/impls/ws/client.js.map +1 -1
  53. package/dist/transport/impls/ws/server.cjs +1182 -1047
  54. package/dist/transport/impls/ws/server.cjs.map +1 -1
  55. package/dist/transport/impls/ws/server.d.cts +4 -4
  56. package/dist/transport/impls/ws/server.d.ts +4 -4
  57. package/dist/transport/impls/ws/server.js +6 -6
  58. package/dist/transport/index.cjs +1434 -1360
  59. package/dist/transport/index.cjs.map +1 -1
  60. package/dist/transport/index.d.cts +4 -4
  61. package/dist/transport/index.d.ts +4 -4
  62. package/dist/transport/index.js +9 -9
  63. package/dist/util/testHelpers.cjs +743 -309
  64. package/dist/util/testHelpers.cjs.map +1 -1
  65. package/dist/util/testHelpers.d.cts +10 -7
  66. package/dist/util/testHelpers.d.ts +10 -7
  67. package/dist/util/testHelpers.js +33 -10
  68. package/dist/util/testHelpers.js.map +1 -1
  69. package/package.json +1 -1
  70. package/dist/chunk-6LCL2ZZF.js.map +0 -1
  71. package/dist/chunk-JA7XGTAL.js +0 -476
  72. package/dist/chunk-JA7XGTAL.js.map +0 -1
  73. package/dist/chunk-LTSLICON.js.map +0 -1
  74. package/dist/chunk-MQCGG6KL.js +0 -335
  75. package/dist/chunk-MQCGG6KL.js.map +0 -1
  76. package/dist/chunk-R47IZD67.js +0 -59
  77. package/dist/chunk-R47IZD67.js.map +0 -1
  78. package/dist/chunk-TXSQRTZB.js.map +0 -1
  79. package/dist/chunk-UDXM64QK.js.map +0 -1
  80. package/dist/chunk-WN77AT67.js +0 -476
  81. package/dist/chunk-WN77AT67.js.map +0 -1
  82. package/dist/chunk-YXDAOVP7.js +0 -347
  83. package/dist/chunk-YXDAOVP7.js.map +0 -1
  84. package/dist/connection-d738cc08.d.ts +0 -17
  85. package/dist/server-3740c5d9.d.ts +0 -24
@@ -25,18 +25,189 @@ __export(transport_exports, {
25
25
  OpaqueTransportMessageSchema: () => OpaqueTransportMessageSchema,
26
26
  ProtocolError: () => ProtocolError,
27
27
  ServerTransport: () => ServerTransport,
28
- Session: () => Session,
28
+ SessionState: () => SessionState,
29
29
  Transport: () => Transport,
30
30
  TransportMessageSchema: () => TransportMessageSchema
31
31
  });
32
32
  module.exports = __toCommonJS(transport_exports);
33
33
 
34
- // transport/transport.ts
35
- var import_value = require("@sinclair/typebox/value");
34
+ // logging/log.ts
35
+ var LoggingLevels = {
36
+ debug: -1,
37
+ info: 0,
38
+ warn: 1,
39
+ error: 2
40
+ };
41
+ var cleanedLogFn = (log) => {
42
+ return (msg, metadata) => {
43
+ if (!metadata?.transportMessage) {
44
+ log(msg, metadata);
45
+ return;
46
+ }
47
+ const { payload, ...rest } = metadata.transportMessage;
48
+ metadata.transportMessage = rest;
49
+ log(msg, metadata);
50
+ };
51
+ };
52
+ var BaseLogger = class {
53
+ minLevel;
54
+ output;
55
+ constructor(output, minLevel = "info") {
56
+ this.minLevel = minLevel;
57
+ this.output = output;
58
+ }
59
+ debug(msg, metadata) {
60
+ if (LoggingLevels[this.minLevel] <= LoggingLevels.debug) {
61
+ this.output(msg, metadata ?? {}, "debug");
62
+ }
63
+ }
64
+ info(msg, metadata) {
65
+ if (LoggingLevels[this.minLevel] <= LoggingLevels.info) {
66
+ this.output(msg, metadata ?? {}, "info");
67
+ }
68
+ }
69
+ warn(msg, metadata) {
70
+ if (LoggingLevels[this.minLevel] <= LoggingLevels.warn) {
71
+ this.output(msg, metadata ?? {}, "warn");
72
+ }
73
+ }
74
+ error(msg, metadata) {
75
+ if (LoggingLevels[this.minLevel] <= LoggingLevels.error) {
76
+ this.output(msg, metadata ?? {}, "error");
77
+ }
78
+ }
79
+ };
80
+ var createLogProxy = (log) => ({
81
+ debug: cleanedLogFn(log.debug.bind(log)),
82
+ info: cleanedLogFn(log.info.bind(log)),
83
+ warn: cleanedLogFn(log.warn.bind(log)),
84
+ error: cleanedLogFn(log.error.bind(log))
85
+ });
86
+
87
+ // transport/events.ts
88
+ var ProtocolError = {
89
+ RetriesExceeded: "conn_retry_exceeded",
90
+ HandshakeFailed: "handshake_failed",
91
+ MessageOrderingViolated: "message_ordering_violated"
92
+ };
93
+ var EventDispatcher = class {
94
+ eventListeners = {};
95
+ removeAllListeners() {
96
+ this.eventListeners = {};
97
+ }
98
+ numberOfListeners(eventType) {
99
+ return this.eventListeners[eventType]?.size ?? 0;
100
+ }
101
+ addEventListener(eventType, handler) {
102
+ if (!this.eventListeners[eventType]) {
103
+ this.eventListeners[eventType] = /* @__PURE__ */ new Set();
104
+ }
105
+ this.eventListeners[eventType]?.add(handler);
106
+ }
107
+ removeEventListener(eventType, handler) {
108
+ const handlers = this.eventListeners[eventType];
109
+ if (handlers) {
110
+ this.eventListeners[eventType]?.delete(handler);
111
+ }
112
+ }
113
+ dispatchEvent(eventType, event) {
114
+ const handlers = this.eventListeners[eventType];
115
+ if (handlers) {
116
+ const copy = [...handlers];
117
+ for (const handler of copy) {
118
+ handler(event);
119
+ }
120
+ }
121
+ }
122
+ };
123
+
124
+ // codec/json.ts
125
+ var encoder = new TextEncoder();
126
+ var decoder = new TextDecoder();
127
+ function uint8ArrayToBase64(uint8Array) {
128
+ let binary = "";
129
+ uint8Array.forEach((byte) => {
130
+ binary += String.fromCharCode(byte);
131
+ });
132
+ return btoa(binary);
133
+ }
134
+ function base64ToUint8Array(base64) {
135
+ const binaryString = atob(base64);
136
+ const uint8Array = new Uint8Array(binaryString.length);
137
+ for (let i = 0; i < binaryString.length; i++) {
138
+ uint8Array[i] = binaryString.charCodeAt(i);
139
+ }
140
+ return uint8Array;
141
+ }
142
+ var NaiveJsonCodec = {
143
+ toBuffer: (obj) => {
144
+ return encoder.encode(
145
+ JSON.stringify(obj, function replacer(key) {
146
+ const val = this[key];
147
+ if (val instanceof Uint8Array) {
148
+ return { $t: uint8ArrayToBase64(val) };
149
+ } else {
150
+ return val;
151
+ }
152
+ })
153
+ );
154
+ },
155
+ fromBuffer: (buff) => {
156
+ try {
157
+ const parsed = JSON.parse(
158
+ decoder.decode(buff),
159
+ function reviver(_key, val) {
160
+ if (val?.$t) {
161
+ return base64ToUint8Array(val.$t);
162
+ } else {
163
+ return val;
164
+ }
165
+ }
166
+ );
167
+ if (typeof parsed === "object")
168
+ return parsed;
169
+ return null;
170
+ } catch {
171
+ return null;
172
+ }
173
+ }
174
+ };
175
+
176
+ // transport/options.ts
177
+ var defaultTransportOptions = {
178
+ heartbeatIntervalMs: 1e3,
179
+ heartbeatsUntilDead: 2,
180
+ sessionDisconnectGraceMs: 5e3,
181
+ connectionTimeoutMs: 2e3,
182
+ handshakeTimeoutMs: 1e3,
183
+ codec: NaiveJsonCodec
184
+ };
185
+ var defaultConnectionRetryOptions = {
186
+ baseIntervalMs: 250,
187
+ maxJitterMs: 200,
188
+ maxBackoffMs: 32e3,
189
+ attemptBudgetCapacity: 5,
190
+ budgetRestoreIntervalMs: 200
191
+ };
192
+ var defaultClientTransportOptions = {
193
+ ...defaultTransportOptions,
194
+ ...defaultConnectionRetryOptions
195
+ };
196
+ var defaultServerTransportOptions = {
197
+ ...defaultTransportOptions
198
+ };
36
199
 
37
200
  // transport/message.ts
38
201
  var import_typebox = require("@sinclair/typebox");
202
+
203
+ // transport/id.ts
39
204
  var import_nanoid = require("nanoid");
205
+ var alphabet = (0, import_nanoid.customAlphabet)(
206
+ "1234567890abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ"
207
+ );
208
+ var generateId = () => alphabet(12);
209
+
210
+ // transport/message.ts
40
211
  var TransportMessageSchema = (t) => import_typebox.Type.Object({
41
212
  id: import_typebox.Type.String(),
42
213
  from: import_typebox.Type.String(),
@@ -71,18 +242,29 @@ var ControlMessageHandshakeRequestSchema = import_typebox.Type.Object({
71
242
  * used by the server to know whether this is a new or a reestablished connection, and whether it
72
243
  * is compatible with what it already has.
73
244
  */
74
- expectedSessionState: import_typebox.Type.Optional(
75
- import_typebox.Type.Object({
76
- /**
77
- * reconnect is set to true if the client explicitly wants to reestablish an existing
78
- * connection.
79
- */
80
- reconnect: import_typebox.Type.Boolean(),
81
- nextExpectedSeq: import_typebox.Type.Integer()
82
- })
83
- ),
245
+ expectedSessionState: import_typebox.Type.Object({
246
+ // what the client expects the server to send next
247
+ nextExpectedSeq: import_typebox.Type.Integer(),
248
+ // TODO: remove optional once we know all servers
249
+ // are nextSentSeq here
250
+ // what the server expects the client to send next
251
+ nextSentSeq: import_typebox.Type.Optional(import_typebox.Type.Integer())
252
+ }),
84
253
  metadata: import_typebox.Type.Optional(import_typebox.Type.Unknown())
85
254
  });
255
+ var HandshakeErrorRetriableResponseCodes = import_typebox.Type.Union([
256
+ import_typebox.Type.Literal("SESSION_STATE_MISMATCH")
257
+ ]);
258
+ var HandshakeErrorFatalResponseCodes = import_typebox.Type.Union([
259
+ import_typebox.Type.Literal("MALFORMED_HANDSHAKE_META"),
260
+ import_typebox.Type.Literal("MALFORMED_HANDSHAKE"),
261
+ import_typebox.Type.Literal("PROTOCOL_VERSION_MISMATCH"),
262
+ import_typebox.Type.Literal("REJECTED_BY_CUSTOM_HANDLER")
263
+ ]);
264
+ var HandshakeErrorResponseCodes = import_typebox.Type.Union([
265
+ HandshakeErrorRetriableResponseCodes,
266
+ HandshakeErrorFatalResponseCodes
267
+ ]);
86
268
  var ControlMessageHandshakeResponseSchema = import_typebox.Type.Object({
87
269
  type: import_typebox.Type.Literal("HANDSHAKE_RESP"),
88
270
  status: import_typebox.Type.Union([
@@ -92,7 +274,10 @@ var ControlMessageHandshakeResponseSchema = import_typebox.Type.Object({
92
274
  }),
93
275
  import_typebox.Type.Object({
94
276
  ok: import_typebox.Type.Literal(false),
95
- reason: import_typebox.Type.String()
277
+ reason: import_typebox.Type.String(),
278
+ // TODO: remove optional once we know all servers
279
+ // are sending code here
280
+ code: import_typebox.Type.Optional(HandshakeErrorResponseCodes)
96
281
  })
97
282
  ])
98
283
  });
@@ -114,12 +299,12 @@ function handshakeRequestMessage({
114
299
  tracing
115
300
  }) {
116
301
  return {
117
- id: (0, import_nanoid.nanoid)(),
302
+ id: generateId(),
118
303
  from,
119
304
  to,
120
305
  seq: 0,
121
306
  ack: 0,
122
- streamId: (0, import_nanoid.nanoid)(),
307
+ streamId: generateId(),
123
308
  controlFlags: 0,
124
309
  tracing,
125
310
  payload: {
@@ -131,19 +316,18 @@ function handshakeRequestMessage({
131
316
  }
132
317
  };
133
318
  }
134
- var SESSION_STATE_MISMATCH = "session state mismatch";
135
319
  function handshakeResponseMessage({
136
320
  from,
137
321
  to,
138
322
  status
139
323
  }) {
140
324
  return {
141
- id: (0, import_nanoid.nanoid)(),
325
+ id: generateId(),
142
326
  from,
143
327
  to,
144
328
  seq: 0,
145
329
  ack: 0,
146
- streamId: (0, import_nanoid.nanoid)(),
330
+ streamId: generateId(),
147
331
  controlFlags: 0,
148
332
  payload: {
149
333
  type: "HANDSHAKE_RESP",
@@ -155,104 +339,230 @@ function isAck(controlFlag) {
155
339
  return (controlFlag & 1 /* AckBit */) === 1 /* AckBit */;
156
340
  }
157
341
 
158
- // logging/log.ts
159
- var LoggingLevels = {
160
- debug: -1,
161
- info: 0,
162
- warn: 1,
163
- error: 2
164
- };
165
- var cleanedLogFn = (log) => {
166
- return (msg, metadata) => {
167
- if (!metadata?.transportMessage) {
168
- log(msg, metadata);
169
- return;
170
- }
171
- const { payload, ...rest } = metadata.transportMessage;
172
- metadata.transportMessage = rest;
173
- log(msg, metadata);
174
- };
175
- };
176
- var BaseLogger = class {
177
- minLevel;
178
- output;
179
- constructor(output, minLevel = "info") {
180
- this.minLevel = minLevel;
181
- this.output = output;
342
+ // transport/sessionStateMachine/common.ts
343
+ var import_value = require("@sinclair/typebox/value");
344
+ var SessionState = /* @__PURE__ */ ((SessionState2) => {
345
+ SessionState2["NoConnection"] = "NoConnection";
346
+ SessionState2["Connecting"] = "Connecting";
347
+ SessionState2["Handshaking"] = "Handshaking";
348
+ SessionState2["Connected"] = "Connected";
349
+ SessionState2["WaitingForHandshake"] = "WaitingForHandshake";
350
+ return SessionState2;
351
+ })(SessionState || {});
352
+ var ERR_CONSUMED = `session state has been consumed and is no longer valid`;
353
+ var StateMachineState = class {
354
+ /*
355
+ * Whether this state has been consumed
356
+ * and we've moved on to another state
357
+ */
358
+ _isConsumed;
359
+ close() {
360
+ this._handleClose();
182
361
  }
183
- debug(msg, metadata) {
184
- if (LoggingLevels[this.minLevel] <= LoggingLevels.debug) {
185
- this.output(msg, metadata ?? {}, "debug");
186
- }
362
+ constructor() {
363
+ this._isConsumed = false;
364
+ return new Proxy(this, {
365
+ get(target, prop) {
366
+ if (prop === "_isConsumed" || prop === "id" || prop === "state") {
367
+ return Reflect.get(target, prop);
368
+ }
369
+ if (prop === "_handleStateExit") {
370
+ return () => {
371
+ target._isConsumed = true;
372
+ target._handleStateExit();
373
+ };
374
+ }
375
+ if (prop === "_handleClose") {
376
+ return () => {
377
+ target._handleStateExit();
378
+ target._handleClose();
379
+ };
380
+ }
381
+ if (target._isConsumed) {
382
+ throw new Error(
383
+ `${ERR_CONSUMED}: getting ${prop.toString()} on consumed state`
384
+ );
385
+ }
386
+ return Reflect.get(target, prop);
387
+ },
388
+ set(target, prop, value) {
389
+ if (target._isConsumed) {
390
+ throw new Error(
391
+ `${ERR_CONSUMED}: setting ${prop.toString()} on consumed state`
392
+ );
393
+ }
394
+ return Reflect.set(target, prop, value);
395
+ }
396
+ });
187
397
  }
188
- info(msg, metadata) {
189
- if (LoggingLevels[this.minLevel] <= LoggingLevels.info) {
190
- this.output(msg, metadata ?? {}, "info");
191
- }
398
+ };
399
+ var CommonSession = class extends StateMachineState {
400
+ from;
401
+ options;
402
+ log;
403
+ constructor(from, options, log) {
404
+ super();
405
+ this.from = from;
406
+ this.options = options;
407
+ this.log = log;
192
408
  }
193
- warn(msg, metadata) {
194
- if (LoggingLevels[this.minLevel] <= LoggingLevels.warn) {
195
- this.output(msg, metadata ?? {}, "warn");
409
+ parseMsg(msg) {
410
+ const parsedMsg = this.options.codec.fromBuffer(msg);
411
+ if (parsedMsg === null) {
412
+ const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));
413
+ this.log?.error(
414
+ `received malformed msg: ${decodedBuffer}`,
415
+ this.loggingMetadata
416
+ );
417
+ return null;
196
418
  }
197
- }
198
- error(msg, metadata) {
199
- if (LoggingLevels[this.minLevel] <= LoggingLevels.error) {
200
- this.output(msg, metadata ?? {}, "error");
419
+ if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
420
+ this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
421
+ ...this.loggingMetadata,
422
+ validationErrors: [
423
+ ...import_value.Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
424
+ ]
425
+ });
426
+ return null;
201
427
  }
428
+ return parsedMsg;
202
429
  }
203
430
  };
204
- var createLogProxy = (log) => ({
205
- debug: cleanedLogFn(log.debug.bind(log)),
206
- info: cleanedLogFn(log.info.bind(log)),
207
- warn: cleanedLogFn(log.warn.bind(log)),
208
- error: cleanedLogFn(log.error.bind(log))
209
- });
210
-
211
- // transport/events.ts
212
- var ProtocolError = {
213
- RetriesExceeded: "conn_retry_exceeded",
214
- HandshakeFailed: "handshake_failed",
215
- MessageOrderingViolated: "message_ordering_violated"
216
- };
217
- var EventDispatcher = class {
218
- eventListeners = {};
219
- removeAllListeners() {
220
- this.eventListeners = {};
431
+ var IdentifiedSession = class extends CommonSession {
432
+ id;
433
+ telemetry;
434
+ to;
435
+ /**
436
+ * Index of the message we will send next (excluding handshake)
437
+ */
438
+ seq;
439
+ /**
440
+ * Number of unique messages we've received this session (excluding handshake)
441
+ */
442
+ ack;
443
+ sendBuffer;
444
+ constructor(id, from, to, seq, ack, sendBuffer, telemetry, options, log) {
445
+ super(from, options, log);
446
+ this.id = id;
447
+ this.to = to;
448
+ this.seq = seq;
449
+ this.ack = ack;
450
+ this.sendBuffer = sendBuffer;
451
+ this.telemetry = telemetry;
452
+ this.log = log;
221
453
  }
222
- numberOfListeners(eventType) {
223
- return this.eventListeners[eventType]?.size ?? 0;
454
+ get loggingMetadata() {
455
+ const spanContext = this.telemetry.span.spanContext();
456
+ return {
457
+ clientId: this.from,
458
+ connectedTo: this.to,
459
+ sessionId: this.id,
460
+ telemetry: {
461
+ traceId: spanContext.traceId,
462
+ spanId: spanContext.spanId
463
+ }
464
+ };
224
465
  }
225
- addEventListener(eventType, handler) {
226
- if (!this.eventListeners[eventType]) {
227
- this.eventListeners[eventType] = /* @__PURE__ */ new Set();
228
- }
229
- this.eventListeners[eventType]?.add(handler);
466
+ constructMsg(partialMsg) {
467
+ const msg = {
468
+ ...partialMsg,
469
+ id: generateId(),
470
+ to: this.to,
471
+ from: this.from,
472
+ seq: this.seq,
473
+ ack: this.ack
474
+ };
475
+ this.seq++;
476
+ return msg;
230
477
  }
231
- removeEventListener(eventType, handler) {
232
- const handlers = this.eventListeners[eventType];
233
- if (handlers) {
234
- this.eventListeners[eventType]?.delete(handler);
235
- }
478
+ nextSeq() {
479
+ return this.sendBuffer.length > 0 ? this.sendBuffer[0].seq : this.seq;
236
480
  }
237
- dispatchEvent(eventType, event) {
238
- const handlers = this.eventListeners[eventType];
239
- if (handlers) {
240
- const copy = [...handlers];
241
- for (const handler of copy) {
242
- handler(event);
481
+ send(msg) {
482
+ const constructedMsg = this.constructMsg(msg);
483
+ this.sendBuffer.push(constructedMsg);
484
+ return constructedMsg.id;
485
+ }
486
+ _handleStateExit() {
487
+ }
488
+ _handleClose() {
489
+ this.sendBuffer.length = 0;
490
+ this.telemetry.span.end();
491
+ }
492
+ };
493
+
494
+ // transport/sessionStateMachine/SessionConnecting.ts
495
+ var SessionConnecting = class extends IdentifiedSession {
496
+ state = "Connecting" /* Connecting */;
497
+ connPromise;
498
+ listeners;
499
+ connectionTimeout;
500
+ constructor(connPromise, listeners, ...args) {
501
+ super(...args);
502
+ this.connPromise = connPromise;
503
+ this.listeners = listeners;
504
+ this.connectionTimeout = setTimeout(() => {
505
+ listeners.onConnectionTimeout();
506
+ }, this.options.connectionTimeoutMs);
507
+ connPromise.then(
508
+ (conn) => {
509
+ if (this._isConsumed)
510
+ return;
511
+ listeners.onConnectionEstablished(conn);
512
+ },
513
+ (err) => {
514
+ if (this._isConsumed)
515
+ return;
516
+ listeners.onConnectionFailed(err);
243
517
  }
244
- }
518
+ );
519
+ }
520
+ // close a pending connection if it resolves, ignore errors if the promise
521
+ // ends up rejected anyways
522
+ bestEffortClose() {
523
+ void this.connPromise.then((conn) => conn.close()).catch(() => {
524
+ });
525
+ }
526
+ _handleStateExit() {
527
+ super._handleStateExit();
528
+ clearTimeout(this.connectionTimeout);
529
+ this.connectionTimeout = void 0;
530
+ }
531
+ _handleClose() {
532
+ this.bestEffortClose();
533
+ super._handleClose();
245
534
  }
246
535
  };
247
536
 
248
- // transport/session.ts
249
- var import_nanoid2 = require("nanoid");
537
+ // transport/sessionStateMachine/SessionNoConnection.ts
538
+ var SessionNoConnection = class extends IdentifiedSession {
539
+ state = "NoConnection" /* NoConnection */;
540
+ listeners;
541
+ gracePeriodTimeout;
542
+ constructor(listeners, ...args) {
543
+ super(...args);
544
+ this.listeners = listeners;
545
+ this.gracePeriodTimeout = setTimeout(() => {
546
+ this.listeners.onSessionGracePeriodElapsed();
547
+ }, this.options.sessionDisconnectGraceMs);
548
+ }
549
+ _handleClose() {
550
+ super._handleClose();
551
+ }
552
+ _handleStateExit() {
553
+ super._handleStateExit();
554
+ if (this.gracePeriodTimeout) {
555
+ clearTimeout(this.gracePeriodTimeout);
556
+ this.gracePeriodTimeout = void 0;
557
+ }
558
+ }
559
+ };
250
560
 
251
561
  // tracing/index.ts
252
562
  var import_api = require("@opentelemetry/api");
253
563
 
254
564
  // package.json
255
- var version = "0.23.16";
565
+ var version = "0.24.0";
256
566
 
257
567
  // tracing/index.ts
258
568
  function getPropagationContext(ctx) {
@@ -263,16 +573,16 @@ function getPropagationContext(ctx) {
263
573
  import_api.propagation.inject(ctx, tracing);
264
574
  return tracing;
265
575
  }
266
- function createSessionTelemetryInfo(session, propagationCtx) {
576
+ function createSessionTelemetryInfo(sessionId, to, from, propagationCtx) {
267
577
  const parentCtx = propagationCtx ? import_api.propagation.extract(import_api.context.active(), propagationCtx) : import_api.context.active();
268
578
  const span = tracer.startSpan(
269
- `session ${session.id}`,
579
+ `session ${sessionId}`,
270
580
  {
271
581
  attributes: {
272
582
  component: "river",
273
- "river.session.id": session.id,
274
- "river.session.to": session.to,
275
- "river.session.from": session.from
583
+ "river.session.id": sessionId,
584
+ "river.session.to": to,
585
+ "river.session.from": from
276
586
  }
277
587
  },
278
588
  parentCtx
@@ -280,173 +590,155 @@ function createSessionTelemetryInfo(session, propagationCtx) {
280
590
  const ctx = import_api.trace.setSpan(parentCtx, span);
281
591
  return { span, ctx };
282
592
  }
283
- function createConnectionTelemetryInfo(connection, info) {
284
- const span = tracer.startSpan(
285
- `connection ${connection.id}`,
286
- {
287
- attributes: {
288
- component: "river",
289
- "river.connection.id": connection.id
290
- },
291
- links: [{ context: info.span.spanContext() }]
292
- },
293
- info.ctx
294
- );
295
- const ctx = import_api.trace.setSpan(info.ctx, span);
296
- return { span, ctx };
297
- }
298
593
  var tracer = import_api.trace.getTracer("river", version);
299
594
  var tracing_default = tracer;
300
595
 
301
- // transport/session.ts
302
- var import_api2 = require("@opentelemetry/api");
303
- var nanoid2 = (0, import_nanoid2.customAlphabet)("1234567890abcdefghijklmnopqrstuvxyz", 6);
304
- var unsafeId = () => nanoid2();
305
- var Connection = class {
306
- id;
307
- telemetry;
308
- constructor() {
309
- this.id = `conn-${nanoid2(12)}`;
310
- }
311
- get loggingMetadata() {
312
- const metadata = { connId: this.id };
313
- const spanContext = this.telemetry?.span.spanContext();
314
- if (this.telemetry?.span.isRecording() && spanContext) {
315
- metadata.telemetry = {
316
- traceId: spanContext.traceId,
317
- spanId: spanContext.spanId
318
- };
596
+ // transport/sessionStateMachine/SessionWaitingForHandshake.ts
597
+ var SessionWaitingForHandshake = class extends CommonSession {
598
+ state = "WaitingForHandshake" /* WaitingForHandshake */;
599
+ conn;
600
+ listeners;
601
+ handshakeTimeout;
602
+ constructor(conn, listeners, ...args) {
603
+ super(...args);
604
+ this.conn = conn;
605
+ this.listeners = listeners;
606
+ this.handshakeTimeout = setTimeout(() => {
607
+ listeners.onHandshakeTimeout();
608
+ }, this.options.handshakeTimeoutMs);
609
+ this.conn.addDataListener(this.onHandshakeData);
610
+ this.conn.addErrorListener(listeners.onConnectionErrored);
611
+ this.conn.addCloseListener(listeners.onConnectionClosed);
612
+ }
613
+ onHandshakeData = (msg) => {
614
+ const parsedMsg = this.parseMsg(msg);
615
+ if (parsedMsg === null) {
616
+ this.listeners.onInvalidHandshake("could not parse message");
617
+ return;
319
618
  }
320
- return metadata;
321
- }
322
- };
323
- var Session = class {
324
- codec;
325
- options;
326
- telemetry;
327
- /**
328
- * The buffer of messages that have been sent but not yet acknowledged.
329
- */
330
- sendBuffer = [];
331
- /**
332
- * The active connection associated with this session
333
- */
334
- connection;
335
- /**
336
- * A connection that is currently undergoing handshaking. Used to distinguish between the active
337
- * connection, but still be able to close it if needed.
338
- */
339
- handshakingConnection;
340
- from;
341
- to;
342
- /**
343
- * The unique ID of this session.
344
- */
345
- id;
346
- /**
347
- * What the other side advertised as their session ID
348
- * for this session.
349
- */
350
- advertisedSessionId;
351
- /**
352
- * Number of messages we've sent along this session (excluding handshake and acks)
353
- */
354
- seq = 0;
355
- /**
356
- * Number of unique messages we've received this session (excluding handshake and acks)
357
- */
358
- ack = 0;
359
- /**
360
- * The grace period between when the inner connection is disconnected
361
- * and when we should consider the entire session disconnected.
362
- */
363
- disconnectionGrace;
364
- /**
365
- * Number of heartbeats we've sent without a response.
366
- */
367
- heartbeatMisses;
368
- /**
369
- * The interval for sending heartbeats.
370
- */
371
- heartbeat;
372
- log;
373
- constructor(conn, from, to, options, propagationCtx) {
374
- this.id = `session-${nanoid2(12)}`;
375
- this.options = options;
376
- this.from = from;
377
- this.to = to;
378
- this.connection = conn;
379
- this.codec = options.codec;
380
- this.heartbeatMisses = 0;
381
- this.heartbeat = setInterval(
382
- () => this.sendHeartbeat(),
383
- options.heartbeatIntervalMs
384
- );
385
- this.telemetry = createSessionTelemetryInfo(this, propagationCtx);
386
- }
387
- bindLogger(log) {
388
- this.log = log;
389
- }
619
+ this.listeners.onHandshake(parsedMsg);
620
+ };
390
621
  get loggingMetadata() {
391
- const spanContext = this.telemetry.span.spanContext();
392
622
  return {
393
623
  clientId: this.from,
394
- connectedTo: this.to,
395
- sessionId: this.id,
396
- connId: this.connection?.id,
397
- telemetry: {
398
- traceId: spanContext.traceId,
399
- spanId: spanContext.spanId
400
- }
624
+ connId: this.conn.id
401
625
  };
402
626
  }
403
- /**
404
- * Sends a message over the session's connection.
405
- * If the connection is not ready or the message fails to send, the message can be buffered for retry unless skipped.
406
- *
407
- * @param msg The partial message to be sent, which will be constructed into a full message.
408
- * @param addToSendBuff Whether to add the message to the send buffer for retry.
409
- * @returns The full transport ID of the message that was attempted to be sent.
410
- */
627
+ sendHandshake(msg) {
628
+ return this.conn.send(this.options.codec.toBuffer(msg));
629
+ }
630
+ _handleStateExit() {
631
+ this.conn.removeDataListener(this.onHandshakeData);
632
+ this.conn.removeErrorListener(this.listeners.onConnectionErrored);
633
+ this.conn.removeCloseListener(this.listeners.onConnectionClosed);
634
+ clearTimeout(this.handshakeTimeout);
635
+ this.handshakeTimeout = void 0;
636
+ }
637
+ _handleClose() {
638
+ this.conn.close();
639
+ }
640
+ };
641
+
642
+ // transport/sessionStateMachine/SessionHandshaking.ts
643
+ var SessionHandshaking = class extends IdentifiedSession {
644
+ state = "Handshaking" /* Handshaking */;
645
+ conn;
646
+ listeners;
647
+ handshakeTimeout;
648
+ constructor(conn, listeners, ...args) {
649
+ super(...args);
650
+ this.conn = conn;
651
+ this.listeners = listeners;
652
+ this.handshakeTimeout = setTimeout(() => {
653
+ listeners.onHandshakeTimeout();
654
+ }, this.options.handshakeTimeoutMs);
655
+ this.conn.addDataListener(this.onHandshakeData);
656
+ this.conn.addErrorListener(listeners.onConnectionErrored);
657
+ this.conn.addCloseListener(listeners.onConnectionClosed);
658
+ }
659
+ onHandshakeData = (msg) => {
660
+ const parsedMsg = this.parseMsg(msg);
661
+ if (parsedMsg === null) {
662
+ this.listeners.onInvalidHandshake("could not parse message");
663
+ return;
664
+ }
665
+ this.listeners.onHandshake(parsedMsg);
666
+ };
667
+ sendHandshake(msg) {
668
+ return this.conn.send(this.options.codec.toBuffer(msg));
669
+ }
670
+ _handleStateExit() {
671
+ super._handleStateExit();
672
+ this.conn.removeDataListener(this.onHandshakeData);
673
+ this.conn.removeErrorListener(this.listeners.onConnectionErrored);
674
+ this.conn.removeCloseListener(this.listeners.onConnectionClosed);
675
+ clearTimeout(this.handshakeTimeout);
676
+ }
677
+ _handleClose() {
678
+ super._handleClose();
679
+ this.conn.close();
680
+ }
681
+ };
682
+
683
+ // transport/sessionStateMachine/SessionConnected.ts
684
+ var import_api2 = require("@opentelemetry/api");
685
+ var SessionConnected = class extends IdentifiedSession {
686
+ state = "Connected" /* Connected */;
687
+ conn;
688
+ listeners;
689
+ heartbeatHandle;
690
+ heartbeatMisses = 0;
691
+ get isActivelyHeartbeating() {
692
+ return this.heartbeatHandle !== void 0;
693
+ }
694
+ updateBookkeeping(ack, seq) {
695
+ this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
696
+ this.ack = seq + 1;
697
+ this.heartbeatMisses = 0;
698
+ }
411
699
  send(msg) {
412
- const fullMsg = this.constructMsg(msg);
413
- this.log?.debug(`sending msg`, {
414
- ...this.loggingMetadata,
415
- transportMessage: fullMsg
416
- });
417
- if (this.connection) {
418
- const ok = this.connection.send(this.codec.toBuffer(fullMsg));
419
- if (ok)
420
- return fullMsg.id;
421
- this.log?.info(
422
- `failed to send msg to ${fullMsg.to}, connection is probably dead`,
423
- {
424
- ...this.loggingMetadata,
425
- transportMessage: fullMsg
426
- }
427
- );
428
- } else {
700
+ const constructedMsg = this.constructMsg(msg);
701
+ this.sendBuffer.push(constructedMsg);
702
+ this.conn.send(this.options.codec.toBuffer(constructedMsg));
703
+ return constructedMsg.id;
704
+ }
705
+ constructor(conn, listeners, ...args) {
706
+ super(...args);
707
+ this.conn = conn;
708
+ this.listeners = listeners;
709
+ this.conn.addDataListener(this.onMessageData);
710
+ this.conn.addCloseListener(listeners.onConnectionClosed);
711
+ this.conn.addErrorListener(listeners.onConnectionErrored);
712
+ if (this.sendBuffer.length > 0) {
429
713
  this.log?.debug(
430
- `buffering msg to ${fullMsg.to}, connection not ready yet`,
431
- { ...this.loggingMetadata, transportMessage: fullMsg }
714
+ `sending ${this.sendBuffer.length} buffered messages`,
715
+ this.loggingMetadata
432
716
  );
433
717
  }
434
- return fullMsg.id;
718
+ for (const msg of this.sendBuffer) {
719
+ conn.send(this.options.codec.toBuffer(msg));
720
+ }
435
721
  }
436
- sendHeartbeat() {
437
- const misses = this.heartbeatMisses;
438
- const missDuration = misses * this.options.heartbeatIntervalMs;
439
- if (misses > this.options.heartbeatsUntilDead) {
440
- if (this.connection) {
722
+ startActiveHeartbeat() {
723
+ this.heartbeatHandle = setInterval(() => {
724
+ const misses = this.heartbeatMisses;
725
+ const missDuration = misses * this.options.heartbeatIntervalMs;
726
+ if (misses >= this.options.heartbeatsUntilDead) {
441
727
  this.log?.info(
442
728
  `closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
443
729
  this.loggingMetadata
444
730
  );
445
731
  this.telemetry.span.addEvent("closing connection due to inactivity");
446
- this.closeStaleConnection();
732
+ this.conn.close();
733
+ clearInterval(this.heartbeatHandle);
734
+ this.heartbeatHandle = void 0;
735
+ return;
447
736
  }
448
- return;
449
- }
737
+ this.sendHeartbeat();
738
+ this.heartbeatMisses++;
739
+ }, this.options.heartbeatIntervalMs);
740
+ }
741
+ sendHeartbeat() {
450
742
  this.send({
451
743
  streamId: "heartbeat",
452
744
  controlFlags: 1 /* AckBit */,
@@ -454,585 +746,293 @@ var Session = class {
454
746
  type: "ACK"
455
747
  }
456
748
  });
457
- this.heartbeatMisses++;
458
749
  }
459
- resetBufferedMessages() {
460
- this.sendBuffer = [];
461
- this.seq = 0;
462
- this.ack = 0;
463
- }
464
- sendBufferedMessages(conn) {
465
- this.log?.info(`resending ${this.sendBuffer.length} buffered messages`, {
466
- ...this.loggingMetadata,
467
- connId: conn.id
468
- });
469
- for (const msg of this.sendBuffer) {
470
- this.log?.debug(`resending msg`, {
471
- ...this.loggingMetadata,
472
- transportMessage: msg,
473
- connId: conn.id
474
- });
475
- const ok = conn.send(this.codec.toBuffer(msg));
476
- if (!ok) {
477
- const errMsg = `failed to send buffered message to ${this.to} (sus, this is a fresh connection)`;
478
- conn.telemetry?.span.setStatus({
479
- code: import_api2.SpanStatusCode.ERROR,
480
- message: errMsg
481
- });
482
- this.log?.error(errMsg, {
750
+ onMessageData = (msg) => {
751
+ const parsedMsg = this.parseMsg(msg);
752
+ if (parsedMsg === null)
753
+ return;
754
+ if (parsedMsg.seq !== this.ack) {
755
+ if (parsedMsg.seq < this.ack) {
756
+ this.log?.debug(
757
+ `received duplicate msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack}), discarding`,
758
+ {
759
+ ...this.loggingMetadata,
760
+ transportMessage: parsedMsg
761
+ }
762
+ );
763
+ } else {
764
+ const reason = `received out-of-order msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack})`;
765
+ this.log?.error(reason, {
483
766
  ...this.loggingMetadata,
484
- transportMessage: msg,
485
- connId: conn.id,
767
+ transportMessage: parsedMsg,
486
768
  tags: ["invariant-violation"]
487
769
  });
488
- conn.close();
489
- return;
770
+ this.telemetry.span.setStatus({
771
+ code: import_api2.SpanStatusCode.ERROR,
772
+ message: reason
773
+ });
774
+ this.listeners.onInvalidMessage(reason);
490
775
  }
491
- }
492
- }
493
- updateBookkeeping(ack, seq) {
494
- if (seq + 1 < this.ack) {
495
- this.log?.error(`received stale seq ${seq} + 1 < ${this.ack}`, {
496
- ...this.loggingMetadata,
497
- tags: ["invariant-violation"]
498
- });
499
776
  return;
500
777
  }
501
- this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
502
- this.ack = seq + 1;
503
- }
504
- closeStaleConnection(conn) {
505
- if (this.connection === void 0 || this.connection === conn)
506
- return;
507
- this.log?.info(
508
- `closing old inner connection from session to ${this.to}`,
509
- this.loggingMetadata
510
- );
511
- this.connection.close();
512
- this.connection = void 0;
513
- }
514
- replaceWithNewConnection(newConn, isTransparentReconnect) {
515
- this.closeStaleConnection(newConn);
516
- this.cancelGrace();
517
- if (isTransparentReconnect) {
518
- this.sendBufferedMessages(newConn);
519
- }
520
- this.connection = newConn;
521
- this.handshakingConnection = void 0;
522
- }
523
- replaceWithNewHandshakingConnection(newConn) {
524
- this.handshakingConnection = newConn;
525
- }
526
- beginGrace(cb) {
527
- this.log?.info(
528
- `starting ${this.options.sessionDisconnectGraceMs}ms grace period until session to ${this.to} is closed`,
529
- this.loggingMetadata
530
- );
531
- this.cancelGrace();
532
- this.disconnectionGrace = setTimeout(() => {
533
- this.log?.info(
534
- `grace period for ${this.to} elapsed`,
535
- this.loggingMetadata
536
- );
537
- cb();
538
- }, this.options.sessionDisconnectGraceMs);
539
- }
540
- // called on reconnect of the underlying session
541
- cancelGrace() {
542
- this.heartbeatMisses = 0;
543
- clearTimeout(this.disconnectionGrace);
544
- this.disconnectionGrace = void 0;
545
- }
546
- /**
547
- * Used to close the handshaking connection, if set.
548
- */
549
- closeHandshakingConnection(expectedHandshakingConn) {
550
- if (this.handshakingConnection === void 0)
551
- return;
552
- if (expectedHandshakingConn !== void 0 && this.handshakingConnection === expectedHandshakingConn) {
778
+ this.log?.debug(`received msg`, {
779
+ ...this.loggingMetadata,
780
+ transportMessage: parsedMsg
781
+ });
782
+ this.updateBookkeeping(parsedMsg.ack, parsedMsg.seq);
783
+ if (!isAck(parsedMsg.controlFlags)) {
784
+ this.listeners.onMessage(parsedMsg);
553
785
  return;
554
786
  }
555
- this.handshakingConnection.close();
556
- this.handshakingConnection = void 0;
557
- }
558
- // closed when we want to discard the whole session
559
- // (i.e. shutdown or session disconnect)
560
- close() {
561
- this.closeStaleConnection();
562
- this.cancelGrace();
563
- this.resetBufferedMessages();
564
- clearInterval(this.heartbeat);
565
- }
566
- get connected() {
567
- return this.connection !== void 0;
568
- }
569
- get nextExpectedAck() {
570
- return this.seq;
571
- }
572
- get nextExpectedSeq() {
573
- return this.ack;
574
- }
575
- /**
576
- * Check that the peer's next expected seq number matches something that is in our send buffer
577
- * _or_ matches our actual next seq.
578
- */
579
- nextExpectedSeqInRange(nextExpectedSeq) {
580
- for (const msg of this.sendBuffer) {
581
- if (nextExpectedSeq === msg.seq) {
582
- return true;
583
- }
787
+ this.log?.debug(`discarding msg (ack bit set)`, {
788
+ ...this.loggingMetadata,
789
+ transportMessage: parsedMsg
790
+ });
791
+ if (!this.isActivelyHeartbeating) {
792
+ this.sendHeartbeat();
584
793
  }
585
- return nextExpectedSeq === this.seq;
586
- }
587
- // This is only used in tests to make the session misbehave.
588
- /* @internal */
589
- advanceAckForTesting(by) {
590
- this.ack += by;
591
- }
592
- constructMsg(partialMsg) {
593
- const msg = {
594
- ...partialMsg,
595
- id: unsafeId(),
596
- to: this.to,
597
- from: this.from,
598
- seq: this.seq,
599
- ack: this.ack
600
- };
601
- this.seq++;
602
- this.sendBuffer.push(msg);
603
- return msg;
604
- }
605
- inspectSendBuffer() {
606
- return this.sendBuffer;
794
+ };
795
+ _handleStateExit() {
796
+ super._handleStateExit();
797
+ this.conn.removeDataListener(this.onMessageData);
798
+ this.conn.removeCloseListener(this.listeners.onConnectionClosed);
799
+ this.conn.removeErrorListener(this.listeners.onConnectionErrored);
800
+ clearInterval(this.heartbeatHandle);
801
+ this.heartbeatHandle = void 0;
802
+ }
803
+ _handleClose() {
804
+ super._handleClose();
805
+ this.conn.close();
607
806
  }
608
807
  };
609
808
 
610
- // transport/transport.ts
611
- var import_api3 = require("@opentelemetry/api");
612
-
613
- // codec/json.ts
614
- var encoder = new TextEncoder();
615
- var decoder = new TextDecoder();
616
- function uint8ArrayToBase64(uint8Array) {
617
- let binary = "";
618
- uint8Array.forEach((byte) => {
619
- binary += String.fromCharCode(byte);
620
- });
621
- return btoa(binary);
622
- }
623
- function base64ToUint8Array(base64) {
624
- const binaryString = atob(base64);
625
- const uint8Array = new Uint8Array(binaryString.length);
626
- for (let i = 0; i < binaryString.length; i++) {
627
- uint8Array[i] = binaryString.charCodeAt(i);
628
- }
629
- return uint8Array;
809
+ // transport/sessionStateMachine/transitions.ts
810
+ function inheritSharedSession(session) {
811
+ return [
812
+ session.id,
813
+ session.from,
814
+ session.to,
815
+ session.seq,
816
+ session.ack,
817
+ session.sendBuffer,
818
+ session.telemetry,
819
+ session.options,
820
+ session.log
821
+ ];
630
822
  }
631
- var NaiveJsonCodec = {
632
- toBuffer: (obj) => {
633
- return encoder.encode(
634
- JSON.stringify(obj, function replacer(key) {
635
- const val = this[key];
636
- if (val instanceof Uint8Array) {
637
- return { $t: uint8ArrayToBase64(val) };
638
- } else {
639
- return val;
640
- }
641
- })
642
- );
823
+ var SessionStateGraph = {
824
+ entrypoints: {
825
+ NoConnection(to, from, listeners, options, log) {
826
+ const id = `session-${generateId()}`;
827
+ const telemetry = createSessionTelemetryInfo(id, to, from);
828
+ const sendBuffer = [];
829
+ const session = new SessionNoConnection(
830
+ listeners,
831
+ id,
832
+ from,
833
+ to,
834
+ 0,
835
+ 0,
836
+ sendBuffer,
837
+ telemetry,
838
+ options,
839
+ log
840
+ );
841
+ session.log?.info(`session ${session.id} created in NoConnection state`, {
842
+ ...session.loggingMetadata,
843
+ tags: ["state-transition"]
844
+ });
845
+ return session;
846
+ },
847
+ WaitingForHandshake(from, conn, listeners, options, log) {
848
+ const session = new SessionWaitingForHandshake(
849
+ conn,
850
+ listeners,
851
+ from,
852
+ options,
853
+ log
854
+ );
855
+ session.log?.info(`session created in WaitingForHandshake state`, {
856
+ ...session.loggingMetadata,
857
+ tags: ["state-transition"]
858
+ });
859
+ return session;
860
+ }
643
861
  },
644
- fromBuffer: (buff) => {
645
- try {
646
- const parsed = JSON.parse(
647
- decoder.decode(buff),
648
- function reviver(_key, val) {
649
- if (val?.$t) {
650
- return base64ToUint8Array(val.$t);
651
- } else {
652
- return val;
653
- }
862
+ // All of the transitions 'move'/'consume' the old session and return a new one.
863
+ // After a session is transitioned, any usage of the old session will throw.
864
+ transition: {
865
+ // happy path transitions
866
+ NoConnectionToConnecting(oldSession, connPromise, listeners) {
867
+ const carriedState = inheritSharedSession(oldSession);
868
+ oldSession._handleStateExit();
869
+ const session = new SessionConnecting(
870
+ connPromise,
871
+ listeners,
872
+ ...carriedState
873
+ );
874
+ session.log?.info(
875
+ `session ${session.id} transition from NoConnection to Connecting`,
876
+ {
877
+ ...session.loggingMetadata,
878
+ tags: ["state-transition"]
654
879
  }
655
880
  );
656
- if (typeof parsed === "object")
657
- return parsed;
658
- return null;
659
- } catch {
660
- return null;
661
- }
662
- }
663
- };
664
-
665
- // transport/options.ts
666
- var defaultTransportOptions = {
667
- heartbeatIntervalMs: 1e3,
668
- heartbeatsUntilDead: 2,
669
- sessionDisconnectGraceMs: 5e3,
670
- codec: NaiveJsonCodec
671
- };
672
- var defaultConnectionRetryOptions = {
673
- baseIntervalMs: 250,
674
- maxJitterMs: 200,
675
- maxBackoffMs: 32e3,
676
- attemptBudgetCapacity: 5,
677
- budgetRestoreIntervalMs: 200
678
- };
679
- var defaultClientTransportOptions = {
680
- ...defaultTransportOptions,
681
- ...defaultConnectionRetryOptions
682
- };
683
- var defaultServerTransportOptions = {
684
- ...defaultTransportOptions
685
- };
686
-
687
- // transport/transport.ts
688
- var Transport = class {
689
- /**
690
- * The status of the transport.
691
- */
692
- status;
693
- /**
694
- * The {@link Codec} used to encode and decode messages.
695
- */
696
- codec;
697
- /**
698
- * The client ID of this transport.
699
- */
700
- clientId;
701
- /**
702
- * The map of {@link Session}s managed by this transport.
703
- */
704
- sessions;
705
- /**
706
- * The map of {@link Connection}s managed by this transport.
707
- */
708
- get connections() {
709
- return new Map(
710
- [...this.sessions].map(([client, session]) => [client, session.connection]).filter((entry) => entry[1] !== void 0)
711
- );
712
- }
713
- /**
714
- * The event dispatcher for handling events of type EventTypes.
715
- */
716
- eventDispatcher;
717
- /**
718
- * The options for this transport.
719
- */
720
- options;
721
- log;
722
- /**
723
- * Creates a new Transport instance.
724
- * This should also set up {@link onConnect}, and {@link onDisconnect} listeners.
725
- * @param codec The codec used to encode and decode messages.
726
- * @param clientId The client ID of this transport.
727
- */
728
- constructor(clientId, providedOptions) {
729
- this.options = { ...defaultTransportOptions, ...providedOptions };
730
- this.eventDispatcher = new EventDispatcher();
731
- this.sessions = /* @__PURE__ */ new Map();
732
- this.codec = this.options.codec;
733
- this.clientId = clientId;
734
- this.status = "open";
735
- }
736
- bindLogger(fn, level) {
737
- if (typeof fn === "function") {
738
- this.log = createLogProxy(new BaseLogger(fn, level));
739
- return;
740
- }
741
- this.log = createLogProxy(fn);
742
- }
743
- /**
744
- * Called when a new connection is established
745
- * and we know the identity of the connected client.
746
- * @param conn The connection object.
747
- */
748
- onConnect(conn, session, isTransparentReconnect) {
749
- this.eventDispatcher.dispatchEvent("connectionStatus", {
750
- status: "connect",
751
- conn
752
- });
753
- conn.telemetry = createConnectionTelemetryInfo(conn, session.telemetry);
754
- session.replaceWithNewConnection(conn, isTransparentReconnect);
755
- this.log?.info(`connected to ${session.to}`, {
756
- ...conn.loggingMetadata,
757
- ...session.loggingMetadata
758
- });
759
- }
760
- createSession(to, conn, propagationCtx) {
761
- const session = new Session(
762
- conn,
763
- this.clientId,
764
- to,
765
- this.options,
766
- propagationCtx
767
- );
768
- if (this.log) {
769
- session.bindLogger(this.log);
770
- }
771
- const currentSession = this.sessions.get(session.to);
772
- if (currentSession) {
773
- this.log?.warn(
774
- `session ${session.id} from ${session.to} surreptitiously replacing ${currentSession.id}`,
881
+ return session;
882
+ },
883
+ ConnectingToHandshaking(oldSession, conn, listeners) {
884
+ const carriedState = inheritSharedSession(oldSession);
885
+ oldSession._handleStateExit();
886
+ const session = new SessionHandshaking(conn, listeners, ...carriedState);
887
+ session.log?.info(
888
+ `session ${session.id} transition from Connecting to Handshaking`,
775
889
  {
776
- ...currentSession.loggingMetadata,
777
- tags: ["invariant-violation"]
890
+ ...session.loggingMetadata,
891
+ tags: ["state-transition"]
778
892
  }
779
893
  );
780
- this.deleteSession({
781
- session: currentSession,
782
- closeHandshakingConnection: false
783
- });
784
- }
785
- this.sessions.set(session.to, session);
786
- this.eventDispatcher.dispatchEvent("sessionStatus", {
787
- status: "connect",
788
- session
789
- });
790
- return session;
791
- }
792
- createNewSession({
793
- to,
794
- conn,
795
- sessionId,
796
- propagationCtx
797
- }) {
798
- let session = this.sessions.get(to);
799
- if (session !== void 0) {
800
- this.log?.info(
801
- `session for ${to} already exists, replacing it with a new session as requested`,
802
- session.loggingMetadata
894
+ return session;
895
+ },
896
+ HandshakingToConnected(oldSession, listeners) {
897
+ const carriedState = inheritSharedSession(oldSession);
898
+ const conn = oldSession.conn;
899
+ oldSession._handleStateExit();
900
+ const session = new SessionConnected(conn, listeners, ...carriedState);
901
+ session.log?.info(
902
+ `session ${session.id} transition from Handshaking to Connected`,
903
+ {
904
+ ...session.loggingMetadata,
905
+ tags: ["state-transition"]
906
+ }
803
907
  );
804
- this.deleteSession({
805
- session,
806
- closeHandshakingConnection: false
807
- });
808
- session = void 0;
809
- }
810
- session = this.createSession(to, conn, propagationCtx);
811
- session.advertisedSessionId = sessionId;
812
- this.log?.info(`created new session for ${to}`, session.loggingMetadata);
813
- return session;
814
- }
815
- getExistingSession({
816
- to,
817
- sessionId,
818
- nextExpectedSeq
819
- }) {
820
- const session = this.sessions.get(to);
821
- if (
822
- // reject this request if there was no previous session to replace
823
- session === void 0 || // or if both parties do not agree about the next expected sequence number
824
- !session.nextExpectedSeqInRange(nextExpectedSeq) || // or if both parties do not agree on the advertised session id
825
- session.advertisedSessionId !== sessionId
826
- ) {
827
- return false;
828
- }
829
- this.log?.info(
830
- `reused existing session for ${to}`,
831
- session.loggingMetadata
832
- );
833
- return session;
834
- }
835
- getOrCreateSession({
836
- to,
837
- conn,
838
- handshakingConn,
839
- sessionId,
840
- propagationCtx
841
- }) {
842
- let session = this.sessions.get(to);
843
- const isReconnect = session !== void 0;
844
- let isTransparentReconnect = isReconnect;
845
- if (session?.advertisedSessionId !== void 0 && sessionId !== void 0 && session.advertisedSessionId !== sessionId) {
846
- this.log?.info(
847
- `session for ${to} already exists but has a different session id (expected: ${session.advertisedSessionId}, got: ${sessionId}), creating a new one`,
848
- session.loggingMetadata
908
+ return session;
909
+ },
910
+ WaitingForHandshakeToConnected(pendingSession, oldSession, sessionId, to, propagationCtx, listeners) {
911
+ const conn = pendingSession.conn;
912
+ const { from, options } = pendingSession;
913
+ const carriedState = oldSession ? (
914
+ // old session exists, inherit state
915
+ inheritSharedSession(oldSession)
916
+ ) : (
917
+ // old session does not exist, create new state
918
+ [
919
+ sessionId,
920
+ from,
921
+ to,
922
+ 0,
923
+ 0,
924
+ [],
925
+ createSessionTelemetryInfo(sessionId, to, from, propagationCtx),
926
+ options,
927
+ pendingSession.log
928
+ ]
849
929
  );
850
- this.deleteSession({
851
- session,
852
- closeHandshakingConnection: handshakingConn !== void 0,
853
- handshakingConn
854
- });
855
- isTransparentReconnect = false;
856
- session = void 0;
857
- }
858
- if (!session) {
859
- session = this.createSession(to, conn, propagationCtx);
860
- this.log?.info(
861
- `no session for ${to}, created a new one`,
862
- session.loggingMetadata
930
+ pendingSession._handleStateExit();
931
+ oldSession?._handleStateExit();
932
+ const session = new SessionConnected(conn, listeners, ...carriedState);
933
+ session.log?.info(
934
+ `session ${session.id} transition from WaitingForHandshake to Connected`,
935
+ {
936
+ ...session.loggingMetadata,
937
+ tags: ["state-transition"]
938
+ }
939
+ );
940
+ return session;
941
+ },
942
+ // disconnect paths
943
+ ConnectingToNoConnection(oldSession, listeners) {
944
+ const carriedState = inheritSharedSession(oldSession);
945
+ oldSession.bestEffortClose();
946
+ oldSession._handleStateExit();
947
+ const session = new SessionNoConnection(listeners, ...carriedState);
948
+ session.log?.info(
949
+ `session ${session.id} transition from Connecting to NoConnection`,
950
+ {
951
+ ...session.loggingMetadata,
952
+ tags: ["state-transition"]
953
+ }
954
+ );
955
+ return session;
956
+ },
957
+ HandshakingToNoConnection(oldSession, listeners) {
958
+ const carriedState = inheritSharedSession(oldSession);
959
+ oldSession.conn.close();
960
+ oldSession._handleStateExit();
961
+ const session = new SessionNoConnection(listeners, ...carriedState);
962
+ session.log?.info(
963
+ `session ${session.id} transition from Handshaking to NoConnection`,
964
+ {
965
+ ...session.loggingMetadata,
966
+ tags: ["state-transition"]
967
+ }
863
968
  );
864
- }
865
- if (sessionId !== void 0) {
866
- session.advertisedSessionId = sessionId;
867
- }
868
- if (handshakingConn !== void 0) {
869
- session.replaceWithNewHandshakingConnection(handshakingConn);
870
- }
871
- return { session, isReconnect, isTransparentReconnect };
872
- }
873
- deleteSession({
874
- session,
875
- closeHandshakingConnection,
876
- handshakingConn
877
- }) {
878
- if (closeHandshakingConnection) {
879
- session.closeHandshakingConnection(handshakingConn);
880
- }
881
- session.close();
882
- session.telemetry.span.end();
883
- const currentSession = this.sessions.get(session.to);
884
- if (currentSession && currentSession.id !== session.id) {
885
- this.log?.warn(
886
- `session ${session.id} disconnect from ${session.to}, mismatch with ${currentSession.id}`,
969
+ return session;
970
+ },
971
+ ConnectedToNoConnection(oldSession, listeners) {
972
+ const carriedState = inheritSharedSession(oldSession);
973
+ oldSession.conn.close();
974
+ oldSession._handleStateExit();
975
+ const session = new SessionNoConnection(listeners, ...carriedState);
976
+ session.log?.info(
977
+ `session ${session.id} transition from Connected to NoConnection`,
887
978
  {
888
979
  ...session.loggingMetadata,
889
- tags: ["invariant-violation"]
980
+ tags: ["state-transition"]
890
981
  }
891
982
  );
892
- return;
983
+ return session;
893
984
  }
894
- this.sessions.delete(session.to);
895
- this.log?.info(
896
- `session ${session.id} disconnect from ${session.to}`,
897
- session.loggingMetadata
898
- );
899
- this.eventDispatcher.dispatchEvent("sessionStatus", {
900
- status: "disconnect",
901
- session
902
- });
903
985
  }
986
+ };
987
+
988
+ // transport/transport.ts
989
+ var Transport = class {
904
990
  /**
905
- * The downstream implementation needs to call this when a connection is closed.
906
- * @param conn The connection object.
907
- * @param connectedTo The peer we are connected to.
991
+ * The status of the transport.
908
992
  */
909
- onDisconnect(conn, session) {
910
- if (session.connection !== void 0 && session.connection.id !== conn.id) {
911
- session.telemetry.span.addEvent("onDisconnect race");
912
- this.log?.warn("onDisconnect race", {
913
- clientId: this.clientId,
914
- ...session.loggingMetadata,
915
- ...conn.loggingMetadata,
916
- tags: ["invariant-violation"]
917
- });
918
- return;
919
- }
920
- conn.telemetry?.span.end();
921
- this.eventDispatcher.dispatchEvent("connectionStatus", {
922
- status: "disconnect",
923
- conn
924
- });
925
- session.connection = void 0;
926
- session.beginGrace(() => {
927
- if (session.connection !== void 0) {
928
- session.telemetry.span.addEvent("session grace period race");
929
- this.log?.warn("session grace period race", {
930
- clientId: this.clientId,
931
- ...session.loggingMetadata,
932
- ...conn.loggingMetadata,
933
- tags: ["invariant-violation"]
934
- });
935
- return;
936
- }
937
- session.telemetry.span.addEvent("session grace period expired");
938
- this.deleteSession({
939
- session,
940
- closeHandshakingConnection: true,
941
- handshakingConn: conn
942
- });
943
- });
944
- }
993
+ status;
945
994
  /**
946
- * Parses a message from a Uint8Array into a {@link OpaqueTransportMessage}.
947
- * @param msg The message to parse.
948
- * @returns The parsed message, or null if the message is malformed or invalid.
995
+ * The client ID of this transport.
949
996
  */
950
- parseMsg(msg, conn) {
951
- const parsedMsg = this.codec.fromBuffer(msg);
952
- if (parsedMsg === null) {
953
- const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));
954
- this.log?.error(
955
- `received malformed msg, killing conn: ${decodedBuffer}`,
956
- {
957
- clientId: this.clientId,
958
- ...conn.loggingMetadata
959
- }
960
- );
961
- return null;
962
- }
963
- if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
964
- this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
965
- clientId: this.clientId,
966
- ...conn.loggingMetadata,
967
- validationErrors: [
968
- ...import_value.Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
969
- ]
970
- });
971
- return null;
997
+ clientId;
998
+ /**
999
+ * The event dispatcher for handling events of type EventTypes.
1000
+ */
1001
+ eventDispatcher;
1002
+ /**
1003
+ * The options for this transport.
1004
+ */
1005
+ options;
1006
+ log;
1007
+ sessions;
1008
+ /**
1009
+ * Creates a new Transport instance.
1010
+ * @param codec The codec used to encode and decode messages.
1011
+ * @param clientId The client ID of this transport.
1012
+ */
1013
+ constructor(clientId, providedOptions) {
1014
+ this.options = { ...defaultTransportOptions, ...providedOptions };
1015
+ this.eventDispatcher = new EventDispatcher();
1016
+ this.clientId = clientId;
1017
+ this.status = "open";
1018
+ this.sessions = /* @__PURE__ */ new Map();
1019
+ }
1020
+ bindLogger(fn, level) {
1021
+ if (typeof fn === "function") {
1022
+ this.log = createLogProxy(new BaseLogger(fn, level));
1023
+ return;
972
1024
  }
973
- return parsedMsg;
1025
+ this.log = createLogProxy(fn);
974
1026
  }
975
1027
  /**
976
1028
  * Called when a message is received by this transport.
977
1029
  * You generally shouldn't need to override this in downstream transport implementations.
978
1030
  * @param msg The received message.
979
1031
  */
980
- handleMsg(msg, conn) {
1032
+ handleMsg(msg) {
981
1033
  if (this.getStatus() !== "open")
982
1034
  return;
983
- const session = this.sessions.get(msg.from);
984
- if (!session) {
985
- this.log?.error(`received message for unknown session from ${msg.from}`, {
986
- clientId: this.clientId,
987
- transportMessage: msg,
988
- ...conn.loggingMetadata,
989
- tags: ["invariant-violation"]
990
- });
991
- return;
992
- }
993
- session.cancelGrace();
994
- this.log?.debug(`received msg`, {
995
- clientId: this.clientId,
996
- transportMessage: msg,
997
- ...conn.loggingMetadata
998
- });
999
- if (msg.seq !== session.nextExpectedSeq) {
1000
- if (msg.seq < session.nextExpectedSeq) {
1001
- this.log?.debug(
1002
- `received duplicate msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq}), discarding`,
1003
- {
1004
- clientId: this.clientId,
1005
- transportMessage: msg,
1006
- ...conn.loggingMetadata
1007
- }
1008
- );
1009
- } else {
1010
- const errMsg = `received out-of-order msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq})`;
1011
- this.log?.error(`${errMsg}, marking connection as dead`, {
1012
- clientId: this.clientId,
1013
- transportMessage: msg,
1014
- ...conn.loggingMetadata,
1015
- tags: ["invariant-violation"]
1016
- });
1017
- this.protocolError(ProtocolError.MessageOrderingViolated, errMsg);
1018
- session.telemetry.span.setStatus({
1019
- code: import_api3.SpanStatusCode.ERROR,
1020
- message: "message order violated"
1021
- });
1022
- this.deleteSession({ session, closeHandshakingConnection: true });
1023
- }
1024
- return;
1025
- }
1026
- session.updateBookkeeping(msg.ack, msg.seq);
1027
- if (!isAck(msg.controlFlags)) {
1028
- this.eventDispatcher.dispatchEvent("message", msg);
1029
- } else {
1030
- this.log?.debug(`discarding msg (ack bit set)`, {
1031
- clientId: this.clientId,
1032
- transportMessage: msg,
1033
- ...conn.loggingMetadata
1034
- });
1035
- }
1035
+ this.eventDispatcher.dispatchEvent("message", msg);
1036
1036
  }
1037
1037
  /**
1038
1038
  * Adds a listener to this transport.
@@ -1050,34 +1050,6 @@ var Transport = class {
1050
1050
  removeEventListener(type, handler) {
1051
1051
  this.eventDispatcher.removeEventListener(type, handler);
1052
1052
  }
1053
- /**
1054
- * Sends a message over this transport, delegating to the appropriate connection to actually
1055
- * send the message.
1056
- * @param msg The message to send.
1057
- * @returns The ID of the sent message or undefined if it wasn't sent
1058
- */
1059
- send(to, msg) {
1060
- if (this.getStatus() === "closed") {
1061
- const err = "transport is closed, cant send";
1062
- this.log?.error(err, {
1063
- clientId: this.clientId,
1064
- transportMessage: msg,
1065
- tags: ["invariant-violation"]
1066
- });
1067
- throw new Error(err);
1068
- }
1069
- return this.getOrCreateSession({ to }).session.send(msg);
1070
- }
1071
- // control helpers
1072
- sendCloseStream(to, streamId) {
1073
- return this.send(to, {
1074
- streamId,
1075
- controlFlags: 4 /* StreamClosedBit */,
1076
- payload: {
1077
- type: "CLOSE"
1078
- }
1079
- });
1080
- }
1081
1053
  protocolError(type, message) {
1082
1054
  this.eventDispatcher.dispatchEvent("protocolError", { type, message });
1083
1055
  }
@@ -1089,7 +1061,7 @@ var Transport = class {
1089
1061
  close() {
1090
1062
  this.status = "closed";
1091
1063
  for (const session of this.sessions.values()) {
1092
- this.deleteSession({ session, closeHandshakingConnection: true });
1064
+ this.deleteSession(session);
1093
1065
  }
1094
1066
  this.eventDispatcher.dispatchEvent("transportStatus", {
1095
1067
  status: this.status
@@ -1100,10 +1072,72 @@ var Transport = class {
1100
1072
  getStatus() {
1101
1073
  return this.status;
1102
1074
  }
1075
+ updateSession(session) {
1076
+ const activeSession = this.sessions.get(session.to);
1077
+ if (activeSession && activeSession.id !== session.id) {
1078
+ const msg = `attempt to transition active session for ${session.to} but active session (${activeSession.id}) is different from handle (${session.id})`;
1079
+ throw new Error(msg);
1080
+ }
1081
+ this.sessions.set(session.to, session);
1082
+ if (!activeSession) {
1083
+ this.eventDispatcher.dispatchEvent("sessionStatus", {
1084
+ status: "connect",
1085
+ session
1086
+ });
1087
+ }
1088
+ this.eventDispatcher.dispatchEvent("sessionTransition", {
1089
+ state: session.state,
1090
+ session
1091
+ });
1092
+ return session;
1093
+ }
1094
+ // state transitions
1095
+ deleteSession(session) {
1096
+ session.log?.info(`closing session ${session.id}`, session.loggingMetadata);
1097
+ this.eventDispatcher.dispatchEvent("sessionStatus", {
1098
+ status: "disconnect",
1099
+ session
1100
+ });
1101
+ session.close();
1102
+ this.sessions.delete(session.to);
1103
+ }
1104
+ // common listeners
1105
+ onSessionGracePeriodElapsed(session) {
1106
+ this.log?.warn(
1107
+ `session to ${session.to} grace period elapsed, closing`,
1108
+ session.loggingMetadata
1109
+ );
1110
+ this.deleteSession(session);
1111
+ }
1112
+ onConnectingFailed(session) {
1113
+ const noConnectionSession = SessionStateGraph.transition.ConnectingToNoConnection(session, {
1114
+ onSessionGracePeriodElapsed: () => {
1115
+ this.onSessionGracePeriodElapsed(noConnectionSession);
1116
+ }
1117
+ });
1118
+ return this.updateSession(noConnectionSession);
1119
+ }
1120
+ onConnClosed(session) {
1121
+ let noConnectionSession;
1122
+ if (session.state === "Handshaking" /* Handshaking */) {
1123
+ noConnectionSession = SessionStateGraph.transition.HandshakingToNoConnection(session, {
1124
+ onSessionGracePeriodElapsed: () => {
1125
+ this.onSessionGracePeriodElapsed(noConnectionSession);
1126
+ }
1127
+ });
1128
+ } else {
1129
+ noConnectionSession = SessionStateGraph.transition.ConnectedToNoConnection(session, {
1130
+ onSessionGracePeriodElapsed: () => {
1131
+ this.onSessionGracePeriodElapsed(noConnectionSession);
1132
+ }
1133
+ });
1134
+ }
1135
+ return this.updateSession(noConnectionSession);
1136
+ }
1103
1137
  };
1104
1138
 
1105
1139
  // transport/client.ts
1106
- var import_api4 = require("@opentelemetry/api");
1140
+ var import_api3 = require("@opentelemetry/api");
1107
1141
 
1108
1142
  // transport/rateLimit.ts
1109
1143
  var LeakyBucketRateLimit = class {
@@ -1191,10 +1225,6 @@ var ClientTransport = class extends Transport {
1191
1225
  * The options for this transport.
1192
1226
  */
1193
1227
  options;
1194
- /**
1195
- * The map of reconnect promises for each client ID.
1196
- */
1197
- inflightConnectionPromises;
1198
1228
  retryBudget;
1199
1229
  /**
1200
1230
  * A flag indicating whether the transport should automatically reconnect
@@ -1213,352 +1243,278 @@ var ClientTransport = class extends Transport {
1213
1243
  ...defaultClientTransportOptions,
1214
1244
  ...providedOptions
1215
1245
  };
1216
- this.inflightConnectionPromises = /* @__PURE__ */ new Map();
1217
1246
  this.retryBudget = new LeakyBucketRateLimit(this.options);
1218
1247
  }
1219
1248
  extendHandshake(options) {
1220
1249
  this.handshakeExtensions = options;
1221
1250
  }
1222
- handleConnection(conn, to) {
1223
- if (this.getStatus() !== "open")
1224
- return;
1225
- let session = void 0;
1226
- const handshakeTimeout = setTimeout(() => {
1227
- if (session)
1228
- return;
1229
- this.log?.warn(
1230
- `connection to ${to} timed out waiting for handshake, closing`,
1231
- { ...conn.loggingMetadata, clientId: this.clientId, connectedTo: to }
1232
- );
1233
- conn.close();
1234
- }, this.options.sessionDisconnectGraceMs);
1235
- const handshakeHandler = (data) => {
1236
- const maybeSession = this.receiveHandshakeResponseMessage(data, conn);
1237
- clearTimeout(handshakeTimeout);
1238
- if (!maybeSession) {
1239
- conn.close();
1240
- return;
1241
- } else {
1242
- session = maybeSession;
1243
- }
1244
- conn.removeDataListener(handshakeHandler);
1245
- conn.addDataListener((data2) => {
1246
- const parsed = this.parseMsg(data2, conn);
1247
- if (!parsed) {
1248
- conn.telemetry?.span.setStatus({
1249
- code: import_api4.SpanStatusCode.ERROR,
1250
- message: "message parse failure"
1251
- });
1252
- conn.close();
1253
- return;
1254
- }
1255
- this.handleMsg(parsed, conn);
1251
+ tryReconnecting(to) {
1252
+ if (this.reconnectOnConnectionDrop && this.getStatus() === "open") {
1253
+ this.connect(to);
1254
+ }
1255
+ }
1256
+ send(to, msg) {
1257
+ if (this.getStatus() === "closed") {
1258
+ const err = "transport is closed, cant send";
1259
+ this.log?.error(err, {
1260
+ clientId: this.clientId,
1261
+ transportMessage: msg,
1262
+ tags: ["invariant-violation"]
1256
1263
  });
1257
- };
1258
- conn.addDataListener(handshakeHandler);
1259
- conn.addCloseListener(() => {
1260
- if (session) {
1261
- this.onDisconnect(conn, session);
1262
- }
1263
- const willReconnect = this.reconnectOnConnectionDrop && this.getStatus() === "open";
1264
- this.log?.info(
1265
- `connection to ${to} disconnected` + (willReconnect ? ", reconnecting" : ""),
1266
- {
1267
- ...conn.loggingMetadata,
1268
- ...session?.loggingMetadata,
1269
- clientId: this.clientId,
1270
- connectedTo: to
1264
+ throw new Error(err);
1265
+ }
1266
+ let session = this.sessions.get(to);
1267
+ if (!session) {
1268
+ session = this.createUnconnectedSession(to);
1269
+ }
1270
+ return session.send(msg);
1271
+ }
1272
+ createUnconnectedSession(to) {
1273
+ const session = SessionStateGraph.entrypoints.NoConnection(
1274
+ to,
1275
+ this.clientId,
1276
+ {
1277
+ onSessionGracePeriodElapsed: () => {
1278
+ this.onSessionGracePeriodElapsed(session);
1271
1279
  }
1272
- );
1273
- this.inflightConnectionPromises.delete(to);
1274
- if (this.reconnectOnConnectionDrop) {
1275
- void this.connect(to);
1280
+ },
1281
+ this.options,
1282
+ this.log
1283
+ );
1284
+ this.updateSession(session);
1285
+ return session;
1286
+ }
1287
+ // listeners
1288
+ onConnectingFailed(session) {
1289
+ const noConnectionSession = super.onConnectingFailed(session);
1290
+ this.tryReconnecting(noConnectionSession.to);
1291
+ return noConnectionSession;
1292
+ }
1293
+ onConnClosed(session) {
1294
+ const noConnectionSession = super.onConnClosed(session);
1295
+ this.tryReconnecting(noConnectionSession.to);
1296
+ return noConnectionSession;
1297
+ }
1298
+ onConnectionEstablished(session, conn) {
1299
+ const handshakingSession = SessionStateGraph.transition.ConnectingToHandshaking(session, conn, {
1300
+ onConnectionErrored: (err) => {
1301
+ const errStr = coerceErrorString(err);
1302
+ this.log?.error(
1303
+ `connection to ${handshakingSession.to} errored during handshake: ${errStr}`,
1304
+ handshakingSession.loggingMetadata
1305
+ );
1306
+ },
1307
+ onConnectionClosed: () => {
1308
+ this.log?.warn(
1309
+ `connection to ${handshakingSession.to} closed during handshake`,
1310
+ handshakingSession.loggingMetadata
1311
+ );
1312
+ this.onConnClosed(handshakingSession);
1313
+ },
1314
+ onHandshake: (msg) => {
1315
+ this.onHandshakeResponse(handshakingSession, msg);
1316
+ },
1317
+ onInvalidHandshake: (reason) => {
1318
+ this.log?.error(
1319
+ `invalid handshake: ${reason}`,
1320
+ handshakingSession.loggingMetadata
1321
+ );
1322
+ this.deleteSession(session);
1323
+ this.protocolError(ProtocolError.HandshakeFailed, reason);
1324
+ },
1325
+ onHandshakeTimeout: () => {
1326
+ this.log?.error(
1327
+ `connection to ${handshakingSession.to} timed out during handshake`,
1328
+ handshakingSession.loggingMetadata
1329
+ );
1330
+ this.onConnClosed(handshakingSession);
1276
1331
  }
1277
1332
  });
1278
- conn.addErrorListener((err) => {
1279
- conn.telemetry?.span.setStatus({
1280
- code: import_api4.SpanStatusCode.ERROR,
1281
- message: "connection error"
1282
- });
1283
- this.log?.warn(
1284
- `error in connection to ${to}: ${coerceErrorString(err)}`,
1285
- {
1286
- ...conn.loggingMetadata,
1287
- ...session?.loggingMetadata,
1288
- clientId: this.clientId,
1289
- connectedTo: to
1290
- }
1291
- );
1333
+ this.updateSession(handshakingSession);
1334
+ void this.sendHandshake(handshakingSession);
1335
+ return handshakingSession;
1336
+ }
1337
+ rejectHandshakeResponse(session, reason, metadata) {
1338
+ session.conn.telemetry?.span.setStatus({
1339
+ code: import_api3.SpanStatusCode.ERROR,
1340
+ message: reason
1292
1341
  });
1342
+ this.log?.warn(reason, metadata);
1343
+ this.deleteSession(session);
1293
1344
  }
1294
- receiveHandshakeResponseMessage(data, conn) {
1295
- const parsed = this.parseMsg(data, conn);
1296
- if (!parsed) {
1297
- conn.telemetry?.span.setStatus({
1298
- code: import_api4.SpanStatusCode.ERROR,
1299
- message: "non-transport message"
1300
- });
1301
- this.protocolError(
1302
- ProtocolError.HandshakeFailed,
1303
- "received non-transport message"
1304
- );
1305
- return false;
1306
- }
1307
- if (!import_value2.Value.Check(ControlMessageHandshakeResponseSchema, parsed.payload)) {
1308
- conn.telemetry?.span.setStatus({
1309
- code: import_api4.SpanStatusCode.ERROR,
1310
- message: "invalid handshake response"
1311
- });
1312
- this.log?.warn(`received invalid handshake resp`, {
1313
- ...conn.loggingMetadata,
1314
- clientId: this.clientId,
1315
- connectedTo: parsed.from,
1316
- transportMessage: parsed,
1345
+ onHandshakeResponse(session, msg) {
1346
+ if (!import_value2.Value.Check(ControlMessageHandshakeResponseSchema, msg.payload)) {
1347
+ const reason = `received invalid handshake response`;
1348
+ this.rejectHandshakeResponse(session, reason, {
1349
+ ...session.loggingMetadata,
1350
+ transportMessage: msg,
1317
1351
  validationErrors: [
1318
- ...import_value2.Value.Errors(
1319
- ControlMessageHandshakeResponseSchema,
1320
- parsed.payload
1321
- )
1352
+ ...import_value2.Value.Errors(ControlMessageHandshakeResponseSchema, msg.payload)
1322
1353
  ]
1323
1354
  });
1324
- this.protocolError(
1325
- ProtocolError.HandshakeFailed,
1326
- "invalid handshake resp"
1327
- );
1328
- return false;
1355
+ return;
1329
1356
  }
1330
- const previousSession = this.sessions.get(parsed.from);
1331
- if (!parsed.payload.status.ok) {
1332
- if (parsed.payload.status.reason === SESSION_STATE_MISMATCH) {
1333
- if (previousSession) {
1334
- this.deleteSession({
1335
- session: previousSession,
1336
- closeHandshakingConnection: true
1337
- });
1338
- }
1339
- conn.telemetry?.span.setStatus({
1340
- code: import_api4.SpanStatusCode.ERROR,
1341
- message: parsed.payload.status.reason
1342
- });
1357
+ if (!msg.payload.status.ok) {
1358
+ const retriable = msg.payload.status.code ? import_value2.Value.Check(
1359
+ HandshakeErrorRetriableResponseCodes,
1360
+ msg.payload.status.code
1361
+ ) : false;
1362
+ const reason = `handshake failed: ${msg.payload.status.reason}`;
1363
+ this.rejectHandshakeResponse(session, reason, {
1364
+ ...session.loggingMetadata,
1365
+ transportMessage: msg
1366
+ });
1367
+ if (retriable) {
1368
+ this.tryReconnecting(session.to);
1343
1369
  } else {
1344
- conn.telemetry?.span.setStatus({
1345
- code: import_api4.SpanStatusCode.ERROR,
1346
- message: "handshake rejected"
1347
- });
1370
+ this.deleteSession(session);
1371
+ this.protocolError(ProtocolError.HandshakeFailed, reason);
1348
1372
  }
1349
- this.log?.warn(
1350
- `received handshake rejection: ${parsed.payload.status.reason}`,
1351
- {
1352
- ...conn.loggingMetadata,
1353
- clientId: this.clientId,
1354
- connectedTo: parsed.from,
1355
- transportMessage: parsed
1356
- }
1357
- );
1358
- this.protocolError(
1359
- ProtocolError.HandshakeFailed,
1360
- parsed.payload.status.reason
1361
- );
1362
- return false;
1373
+ return;
1363
1374
  }
1364
- if (previousSession?.advertisedSessionId && previousSession.advertisedSessionId !== parsed.payload.status.sessionId) {
1365
- this.deleteSession({
1366
- session: previousSession,
1367
- closeHandshakingConnection: true
1368
- });
1369
- conn.telemetry?.span.setStatus({
1370
- code: import_api4.SpanStatusCode.ERROR,
1371
- message: "session id mismatch"
1372
- });
1373
- this.log?.warn(`handshake from ${parsed.from} session id mismatch`, {
1374
- ...conn.loggingMetadata,
1375
- clientId: this.clientId,
1376
- connectedTo: parsed.from,
1377
- transportMessage: parsed
1375
+ if (msg.payload.status.sessionId !== session.id) {
1376
+ const reason = `session id mismatch: expected ${session.id}, got ${msg.payload.status.sessionId}`;
1377
+ this.rejectHandshakeResponse(session, reason, {
1378
+ ...session.loggingMetadata,
1379
+ transportMessage: msg
1378
1380
  });
1379
- this.protocolError(ProtocolError.HandshakeFailed, "session id mismatch");
1380
- return false;
1381
+ return;
1381
1382
  }
1382
- this.log?.debug(`handshake from ${parsed.from} ok`, {
1383
- ...conn.loggingMetadata,
1384
- clientId: this.clientId,
1385
- connectedTo: parsed.from,
1386
- transportMessage: parsed
1383
+ this.log?.info(`handshake from ${msg.from} ok`, {
1384
+ ...session.loggingMetadata,
1385
+ transportMessage: msg
1387
1386
  });
1388
- const { session, isTransparentReconnect } = this.getOrCreateSession({
1389
- to: parsed.from,
1390
- conn,
1391
- sessionId: parsed.payload.status.sessionId
1387
+ const connectedSession = SessionStateGraph.transition.HandshakingToConnected(session, {
1388
+ onConnectionErrored: (err) => {
1389
+ const errStr = coerceErrorString(err);
1390
+ this.log?.warn(
1391
+ `connection to ${connectedSession.to} errored: ${errStr}`,
1392
+ connectedSession.loggingMetadata
1393
+ );
1394
+ },
1395
+ onConnectionClosed: () => {
1396
+ this.log?.info(
1397
+ `connection to ${connectedSession.to} closed`,
1398
+ connectedSession.loggingMetadata
1399
+ );
1400
+ this.onConnClosed(connectedSession);
1401
+ },
1402
+ onMessage: (msg2) => this.handleMsg(msg2),
1403
+ onInvalidMessage: (reason) => {
1404
+ this.deleteSession(connectedSession);
1405
+ this.protocolError(ProtocolError.MessageOrderingViolated, reason);
1406
+ }
1392
1407
  });
1393
- this.onConnect(conn, session, isTransparentReconnect);
1394
- this.retryBudget.startRestoringBudget(session.to);
1395
- return session;
1408
+ this.updateSession(connectedSession);
1409
+ this.retryBudget.startRestoringBudget(connectedSession.to);
1396
1410
  }
1397
1411
  /**
1398
1412
  * Manually attempts to connect to a client.
1399
1413
  * @param to The client ID of the node to connect to.
1400
1414
  */
1401
- async connect(to) {
1402
- if (this.connections.has(to)) {
1403
- this.log?.info(`already connected to ${to}, skipping connect attempt`, {
1404
- clientId: this.clientId,
1405
- connectedTo: to
1406
- });
1415
+ connect(to) {
1416
+ let session = this.sessions.get(to);
1417
+ session ??= this.createUnconnectedSession(to);
1418
+ if (session.state !== "NoConnection" /* NoConnection */) {
1419
+ this.log?.debug(
1420
+ `session to ${to} has state ${session.state}, skipping connect attempt`,
1421
+ session.loggingMetadata
1422
+ );
1407
1423
  return;
1408
1424
  }
1409
- const canProceedWithConnection = () => this.getStatus() === "open";
1410
- if (!canProceedWithConnection()) {
1425
+ if (this.getStatus() !== "open") {
1411
1426
  this.log?.info(
1412
1427
  `transport state is no longer open, cancelling attempt to connect to ${to}`,
1413
- { clientId: this.clientId, connectedTo: to }
1428
+ session.loggingMetadata
1414
1429
  );
1415
1430
  return;
1416
1431
  }
1417
- let reconnectPromise = this.inflightConnectionPromises.get(to);
1418
- if (!reconnectPromise) {
1419
- if (!this.retryBudget.hasBudget(to)) {
1420
- const budgetConsumed = this.retryBudget.getBudgetConsumed(to);
1421
- const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
1422
- this.log?.error(errMsg, { clientId: this.clientId, connectedTo: to });
1423
- this.protocolError(ProtocolError.RetriesExceeded, errMsg);
1424
- return;
1425
- }
1426
- let sleep = Promise.resolve();
1427
- const backoffMs = this.retryBudget.getBackoffMs(to);
1428
- if (backoffMs > 0) {
1429
- sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
1430
- }
1431
- this.log?.info(
1432
- `attempting connection to ${to} (${backoffMs}ms backoff)`,
1433
- {
1434
- clientId: this.clientId,
1435
- connectedTo: to
1436
- }
1437
- );
1438
- this.retryBudget.consumeBudget(to);
1439
- reconnectPromise = tracing_default.startActiveSpan("connect", async (span) => {
1440
- try {
1441
- span.addEvent("backoff", { backoffMs });
1442
- await sleep;
1443
- if (!canProceedWithConnection()) {
1444
- throw new Error("transport state is no longer open");
1445
- }
1446
- span.addEvent("connecting");
1447
- const conn = await this.createNewOutgoingConnection(to);
1448
- if (!canProceedWithConnection()) {
1449
- this.log?.info(
1450
- `transport state is no longer open, closing pre-handshake connection to ${to}`,
1451
- {
1452
- ...conn.loggingMetadata,
1453
- clientId: this.clientId,
1454
- connectedTo: to
1455
- }
1456
- );
1457
- conn.close();
1458
- throw new Error("transport state is no longer open");
1459
- }
1460
- span.addEvent("sending handshake");
1461
- const ok = await this.sendHandshake(to, conn);
1462
- if (!ok) {
1463
- conn.close();
1464
- throw new Error("failed to send handshake");
1465
- }
1466
- return conn;
1467
- } catch (err) {
1468
- const errStr = coerceErrorString(err);
1469
- span.recordException(errStr);
1470
- span.setStatus({ code: import_api4.SpanStatusCode.ERROR });
1471
- throw err;
1472
- } finally {
1473
- span.end();
1474
- }
1475
- });
1476
- this.inflightConnectionPromises.set(to, reconnectPromise);
1477
- } else {
1478
- this.log?.info(
1479
- `attempting connection to ${to} (reusing previous attempt)`,
1480
- {
1481
- clientId: this.clientId,
1482
- connectedTo: to
1483
- }
1484
- );
1432
+ if (!this.retryBudget.hasBudget(to)) {
1433
+ const budgetConsumed = this.retryBudget.getBudgetConsumed(to);
1434
+ const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
1435
+ this.log?.error(errMsg, session.loggingMetadata);
1436
+ this.protocolError(ProtocolError.RetriesExceeded, errMsg);
1437
+ return;
1485
1438
  }
1486
- try {
1487
- await reconnectPromise;
1488
- } catch (error) {
1489
- this.inflightConnectionPromises.delete(to);
1490
- const errStr = coerceErrorString(error);
1491
- if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {
1492
- this.log?.warn(`connection to ${to} failed (${errStr})`, {
1493
- clientId: this.clientId,
1494
- connectedTo: to
1495
- });
1496
- } else {
1497
- this.log?.warn(`connection to ${to} failed (${errStr}), retrying`, {
1498
- clientId: this.clientId,
1499
- connectedTo: to
1500
- });
1501
- await this.connect(to);
1502
- }
1439
+ let sleep = Promise.resolve();
1440
+ const backoffMs = this.retryBudget.getBackoffMs(to);
1441
+ if (backoffMs > 0) {
1442
+ sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
1503
1443
  }
1504
- }
1505
- deleteSession({
1506
- session,
1507
- closeHandshakingConnection,
1508
- handshakingConn
1509
- }) {
1510
- this.inflightConnectionPromises.delete(session.to);
1511
- super.deleteSession({
1512
- session,
1513
- closeHandshakingConnection,
1514
- handshakingConn
1444
+ this.log?.info(
1445
+ `attempting connection to ${to} (${backoffMs}ms backoff)`,
1446
+ session.loggingMetadata
1447
+ );
1448
+ this.retryBudget.consumeBudget(to);
1449
+ const reconnectPromise = tracing_default.startActiveSpan("connect", async (span) => {
1450
+ try {
1451
+ span.addEvent("backoff", { backoffMs });
1452
+ await sleep;
1453
+ if (this.getStatus() !== "open") {
1454
+ throw new Error("transport state is no longer open");
1455
+ }
1456
+ span.addEvent("connecting");
1457
+ return await this.createNewOutgoingConnection(to);
1458
+ } catch (err) {
1459
+ const errStr = coerceErrorString(err);
1460
+ span.recordException(errStr);
1461
+ span.setStatus({ code: import_api3.SpanStatusCode.ERROR });
1462
+ throw err;
1463
+ } finally {
1464
+ span.end();
1465
+ }
1515
1466
  });
1467
+ const connectingSession = SessionStateGraph.transition.NoConnectionToConnecting(
1468
+ session,
1469
+ reconnectPromise,
1470
+ {
1471
+ onConnectionEstablished: (conn) => {
1472
+ this.log?.debug(
1473
+ `connection to ${connectingSession.to} established`,
1474
+ connectingSession.loggingMetadata
1475
+ );
1476
+ this.onConnectionEstablished(connectingSession, conn);
1477
+ },
1478
+ onConnectionFailed: (error) => {
1479
+ const errStr = coerceErrorString(error);
1480
+ this.log?.error(
1481
+ `error connecting to ${connectingSession.to}: ${errStr}`,
1482
+ connectingSession.loggingMetadata
1483
+ );
1484
+ this.onConnectingFailed(connectingSession);
1485
+ },
1486
+ onConnectionTimeout: () => {
1487
+ this.log?.error(
1488
+ `connection to ${connectingSession.to} timed out`,
1489
+ connectingSession.loggingMetadata
1490
+ );
1491
+ this.onConnectingFailed(connectingSession);
1492
+ }
1493
+ }
1494
+ );
1495
+ this.updateSession(connectingSession);
1516
1496
  }
1517
- async sendHandshake(to, conn) {
1497
+ async sendHandshake(session) {
1518
1498
  let metadata = void 0;
1519
1499
  if (this.handshakeExtensions) {
1520
1500
  metadata = await this.handshakeExtensions.construct();
1521
- if (!import_value2.Value.Check(this.handshakeExtensions.schema, metadata)) {
1522
- this.log?.error(`constructed handshake metadata did not match schema`, {
1523
- ...conn.loggingMetadata,
1524
- clientId: this.clientId,
1525
- connectedTo: to,
1526
- validationErrors: [
1527
- ...import_value2.Value.Errors(this.handshakeExtensions.schema, metadata)
1528
- ],
1529
- tags: ["invariant-violation"]
1530
- });
1531
- this.protocolError(
1532
- ProtocolError.HandshakeFailed,
1533
- "handshake metadata did not match schema"
1534
- );
1535
- conn.telemetry?.span.setStatus({
1536
- code: import_api4.SpanStatusCode.ERROR,
1537
- message: "handshake meta mismatch"
1538
- });
1539
- return false;
1540
- }
1541
1501
  }
1542
- const { session } = this.getOrCreateSession({ to, handshakingConn: conn });
1543
1502
  const requestMsg = handshakeRequestMessage({
1544
1503
  from: this.clientId,
1545
- to,
1504
+ to: session.to,
1546
1505
  sessionId: session.id,
1547
1506
  expectedSessionState: {
1548
- reconnect: session.advertisedSessionId !== void 0,
1549
- nextExpectedSeq: session.nextExpectedSeq
1507
+ nextExpectedSeq: session.ack,
1508
+ nextSentSeq: session.nextSeq()
1550
1509
  },
1551
1510
  metadata,
1552
1511
  tracing: getPropagationContext(session.telemetry.ctx)
1553
1512
  });
1554
- this.log?.debug(`sending handshake request to ${to}`, {
1555
- ...conn.loggingMetadata,
1556
- clientId: this.clientId,
1557
- connectedTo: to,
1513
+ this.log?.debug(`sending handshake request to ${session.to}`, {
1514
+ ...session.loggingMetadata,
1558
1515
  transportMessage: requestMsg
1559
1516
  });
1560
- conn.send(this.codec.toBuffer(requestMsg));
1561
- return true;
1517
+ session.sendHandshake(requestMsg);
1562
1518
  }
1563
1519
  close() {
1564
1520
  this.retryBudget.close();
@@ -1567,7 +1523,7 @@ var ClientTransport = class extends Transport {
1567
1523
  };
1568
1524
 
1569
1525
  // transport/server.ts
1570
- var import_api5 = require("@opentelemetry/api");
1526
+ var import_api4 = require("@opentelemetry/api");
1571
1527
  var import_value3 = require("@sinclair/typebox/value");
1572
1528
  var ServerTransport = class extends Transport {
1573
1529
  /**
@@ -1581,14 +1537,14 @@ var ServerTransport = class extends Transport {
1581
1537
  /**
1582
1538
  * A map of session handshake data for each session.
1583
1539
  */
1584
- sessionHandshakeMetadata;
1540
+ sessionHandshakeMetadata = /* @__PURE__ */ new Map();
1541
+ pendingSessions = /* @__PURE__ */ new Set();
1585
1542
  constructor(clientId, providedOptions) {
1586
1543
  super(clientId, providedOptions);
1587
1544
  this.options = {
1588
1545
  ...defaultServerTransportOptions,
1589
1546
  ...providedOptions
1590
1547
  };
1591
- this.sessionHandshakeMetadata = /* @__PURE__ */ new WeakMap();
1592
1548
  this.log?.info(`initiated server transport`, {
1593
1549
  clientId: this.clientId,
1594
1550
  protocolVersion: PROTOCOL_VERSION
@@ -1597,6 +1553,36 @@ var ServerTransport = class extends Transport {
1597
1553
  extendHandshake(options) {
1598
1554
  this.handshakeExtensions = options;
1599
1555
  }
1556
+ send(to, msg) {
1557
+ if (this.getStatus() === "closed") {
1558
+ const err = "transport is closed, cant send";
1559
+ this.log?.error(err, {
1560
+ clientId: this.clientId,
1561
+ transportMessage: msg,
1562
+ tags: ["invariant-violation"]
1563
+ });
1564
+ throw new Error(err);
1565
+ }
1566
+ const session = this.sessions.get(to);
1567
+ if (!session) {
1568
+ const err = `session to ${to} does not exist`;
1569
+ this.log?.error(err, {
1570
+ clientId: this.clientId,
1571
+ transportMessage: msg,
1572
+ tags: ["invariant-violation"]
1573
+ });
1574
+ throw new Error(err);
1575
+ }
1576
+ return session.send(msg);
1577
+ }
1578
+ deletePendingSession(pendingSession) {
1579
+ pendingSession.close();
1580
+ this.pendingSessions.delete(pendingSession);
1581
+ }
1582
+ deleteSession(session) {
1583
+ this.sessionHandshakeMetadata.delete(session.to);
1584
+ super.deleteSession(session);
1585
+ }
1600
1586
  handleConnection(conn) {
1601
1587
  if (this.getStatus() !== "open")
1602
1588
  return;
@@ -1604,281 +1590,369 @@ var ServerTransport = class extends Transport {
1604
1590
  ...conn.loggingMetadata,
1605
1591
  clientId: this.clientId
1606
1592
  });
1607
- let session = void 0;
1608
- const client = () => session?.to ?? "unknown";
1609
- const handshakeTimeout = setTimeout(() => {
1610
- if (!session) {
1611
- this.log?.warn(
1612
- `connection to ${client()} timed out waiting for handshake, closing`,
1613
- {
1614
- ...conn.loggingMetadata,
1615
- clientId: this.clientId,
1616
- connectedTo: client()
1617
- }
1618
- );
1619
- conn.telemetry?.span.setStatus({
1620
- code: import_api5.SpanStatusCode.ERROR,
1621
- message: "handshake timeout"
1622
- });
1623
- conn.close();
1624
- }
1625
- }, this.options.sessionDisconnectGraceMs);
1626
- const buffer = [];
1627
- let receivedHandshakeMessage = false;
1628
- const handshakeHandler = (data) => {
1629
- if (receivedHandshakeMessage) {
1630
- buffer.push(data);
1631
- return;
1632
- }
1633
- receivedHandshakeMessage = true;
1634
- clearTimeout(handshakeTimeout);
1635
- void this.receiveHandshakeRequestMessage(data, conn).then(
1636
- (maybeSession) => {
1637
- if (!maybeSession) {
1638
- conn.close();
1593
+ let receivedHandshake = false;
1594
+ const pendingSession = SessionStateGraph.entrypoints.WaitingForHandshake(
1595
+ this.clientId,
1596
+ conn,
1597
+ {
1598
+ onConnectionClosed: () => {
1599
+ this.log?.warn(
1600
+ `connection from unknown closed before handshake finished`,
1601
+ pendingSession.loggingMetadata
1602
+ );
1603
+ this.deletePendingSession(pendingSession);
1604
+ },
1605
+ onConnectionErrored: (err) => {
1606
+ const errorString = coerceErrorString(err);
1607
+ this.log?.warn(
1608
+ `connection from unknown errored before handshake finished: ${errorString}`,
1609
+ pendingSession.loggingMetadata
1610
+ );
1611
+ this.deletePendingSession(pendingSession);
1612
+ },
1613
+ onHandshakeTimeout: () => {
1614
+ this.log?.warn(
1615
+ `connection from unknown timed out before handshake finished`,
1616
+ pendingSession.loggingMetadata
1617
+ );
1618
+ this.deletePendingSession(pendingSession);
1619
+ },
1620
+ onHandshake: (msg) => {
1621
+ if (receivedHandshake) {
1622
+ this.log?.error(
1623
+ `received multiple handshake messages from pending session`,
1624
+ {
1625
+ ...pendingSession.loggingMetadata,
1626
+ connectedTo: msg.from,
1627
+ transportMessage: msg
1628
+ }
1629
+ );
1630
+ this.deletePendingSession(pendingSession);
1639
1631
  return;
1640
1632
  }
1641
- session = maybeSession;
1642
- const dataHandler = (data2) => {
1643
- const parsed = this.parseMsg(data2, conn);
1644
- if (!parsed) {
1645
- conn.close();
1646
- return;
1647
- }
1648
- this.handleMsg(parsed, conn);
1649
- };
1650
- for (const data2 of buffer) {
1651
- dataHandler(data2);
1652
- }
1653
- conn.removeDataListener(handshakeHandler);
1654
- conn.addDataListener(dataHandler);
1655
- buffer.length = 0;
1633
+ receivedHandshake = true;
1634
+ void this.onHandshakeRequest(pendingSession, msg);
1635
+ },
1636
+ onInvalidHandshake: (reason) => {
1637
+ this.log?.error(
1638
+ `invalid handshake: ${reason}`,
1639
+ pendingSession.loggingMetadata
1640
+ );
1641
+ this.deletePendingSession(pendingSession);
1642
+ this.protocolError(ProtocolError.HandshakeFailed, reason);
1656
1643
  }
1657
- );
1658
- };
1659
- conn.addDataListener(handshakeHandler);
1660
- conn.addCloseListener(() => {
1661
- if (!session)
1662
- return;
1663
- this.log?.info(`connection to ${client()} disconnected`, {
1664
- ...conn.loggingMetadata,
1665
- clientId: this.clientId
1666
- });
1667
- this.onDisconnect(conn, session);
1668
- });
1669
- conn.addErrorListener((err) => {
1670
- conn.telemetry?.span.setStatus({
1671
- code: import_api5.SpanStatusCode.ERROR,
1672
- message: "connection error"
1673
- });
1674
- if (!session)
1675
- return;
1676
- this.log?.warn(
1677
- `connection to ${client()} got an error: ${coerceErrorString(err)}`,
1678
- { ...conn.loggingMetadata, clientId: this.clientId }
1679
- );
1680
- });
1681
- }
1682
- async validateHandshakeMetadata(conn, session, rawMetadata, from) {
1683
- let parsedMetadata = {};
1684
- if (this.handshakeExtensions) {
1685
- if (!import_value3.Value.Check(this.handshakeExtensions.schema, rawMetadata)) {
1686
- conn.telemetry?.span.setStatus({
1687
- code: import_api5.SpanStatusCode.ERROR,
1688
- message: "malformed handshake meta"
1689
- });
1690
- const reason = "received malformed handshake metadata";
1691
- const responseMsg = handshakeResponseMessage({
1692
- from: this.clientId,
1693
- to: from,
1694
- status: {
1695
- ok: false,
1696
- reason
1697
- }
1698
- });
1699
- conn.send(this.codec.toBuffer(responseMsg));
1700
- this.log?.warn(`received malformed handshake metadata from ${from}`, {
1701
- ...conn.loggingMetadata,
1702
- clientId: this.clientId,
1703
- validationErrors: [
1704
- ...import_value3.Value.Errors(this.handshakeExtensions.schema, rawMetadata)
1705
- ]
1706
- });
1707
- this.protocolError(ProtocolError.HandshakeFailed, reason);
1708
- return false;
1709
- }
1710
- const previousParsedMetadata = session ? this.sessionHandshakeMetadata.get(session) : void 0;
1711
- parsedMetadata = await this.handshakeExtensions.validate(
1712
- rawMetadata,
1713
- previousParsedMetadata
1714
- );
1715
- if (parsedMetadata === false) {
1716
- const reason = "rejected by handshake handler";
1717
- conn.telemetry?.span.setStatus({
1718
- code: import_api5.SpanStatusCode.ERROR,
1719
- message: reason
1720
- });
1721
- const responseMsg = handshakeResponseMessage({
1722
- from: this.clientId,
1723
- to: from,
1724
- status: {
1725
- ok: false,
1726
- reason
1727
- }
1728
- });
1729
- conn.send(this.codec.toBuffer(responseMsg));
1730
- this.log?.warn(`rejected handshake from ${from}`, {
1731
- ...conn.loggingMetadata,
1732
- clientId: this.clientId
1733
- });
1734
- this.protocolError(ProtocolError.HandshakeFailed, reason);
1735
- return false;
1736
- }
1737
- }
1738
- return parsedMetadata;
1644
+ },
1645
+ this.options,
1646
+ this.log
1647
+ );
1648
+ this.pendingSessions.add(pendingSession);
1739
1649
  }
1740
- async receiveHandshakeRequestMessage(data, conn) {
1741
- const parsed = this.parseMsg(data, conn);
1742
- if (!parsed) {
1743
- conn.telemetry?.span.setStatus({
1744
- code: import_api5.SpanStatusCode.ERROR,
1745
- message: "non-transport message"
1746
- });
1747
- this.protocolError(
1748
- ProtocolError.HandshakeFailed,
1749
- "received non-transport message"
1750
- );
1751
- return false;
1752
- }
1753
- if (!import_value3.Value.Check(ControlMessageHandshakeRequestSchema, parsed.payload)) {
1754
- conn.telemetry?.span.setStatus({
1755
- code: import_api5.SpanStatusCode.ERROR,
1756
- message: "invalid handshake request"
1757
- });
1758
- const reason = "received invalid handshake msg";
1759
- const responseMsg2 = handshakeResponseMessage({
1650
+ rejectHandshakeRequest(session, to, reason, code, metadata) {
1651
+ session.conn.telemetry?.span.setStatus({
1652
+ code: import_api4.SpanStatusCode.ERROR,
1653
+ message: reason
1654
+ });
1655
+ this.log?.warn(reason, metadata);
1656
+ session.sendHandshake(
1657
+ handshakeResponseMessage({
1760
1658
  from: this.clientId,
1761
- to: parsed.from,
1659
+ to,
1762
1660
  status: {
1763
1661
  ok: false,
1662
+ code,
1764
1663
  reason
1765
1664
  }
1766
- });
1767
- conn.send(this.codec.toBuffer(responseMsg2));
1768
- this.log?.warn(reason, {
1769
- ...conn.loggingMetadata,
1770
- clientId: this.clientId,
1771
- // safe to this.log metadata here as we remove the payload
1772
- // before passing it to user-land
1773
- transportMessage: parsed,
1774
- validationErrors: [
1775
- ...import_value3.Value.Errors(ControlMessageHandshakeRequestSchema, parsed.payload)
1776
- ]
1777
- });
1778
- this.protocolError(
1779
- ProtocolError.HandshakeFailed,
1780
- "invalid handshake request"
1665
+ })
1666
+ );
1667
+ this.protocolError(ProtocolError.HandshakeFailed, reason);
1668
+ this.deletePendingSession(session);
1669
+ }
1670
+ async onHandshakeRequest(session, msg) {
1671
+ if (!import_value3.Value.Check(ControlMessageHandshakeRequestSchema, msg.payload)) {
1672
+ this.rejectHandshakeRequest(
1673
+ session,
1674
+ msg.from,
1675
+ "received invalid handshake request",
1676
+ "MALFORMED_HANDSHAKE",
1677
+ {
1678
+ ...session.loggingMetadata,
1679
+ transportMessage: msg,
1680
+ connectedTo: msg.from,
1681
+ validationErrors: [
1682
+ ...import_value3.Value.Errors(ControlMessageHandshakeRequestSchema, msg.payload)
1683
+ ]
1684
+ }
1781
1685
  );
1782
- return false;
1686
+ return;
1783
1687
  }
1784
- const gotVersion = parsed.payload.protocolVersion;
1688
+ const gotVersion = msg.payload.protocolVersion;
1785
1689
  if (gotVersion !== PROTOCOL_VERSION) {
1786
- conn.telemetry?.span.setStatus({
1787
- code: import_api5.SpanStatusCode.ERROR,
1788
- message: "incorrect protocol version"
1789
- });
1790
- const reason = `incorrect version (got: ${gotVersion} wanted ${PROTOCOL_VERSION})`;
1791
- const responseMsg2 = handshakeResponseMessage({
1792
- from: this.clientId,
1793
- to: parsed.from,
1794
- status: {
1795
- ok: false,
1796
- reason
1690
+ this.rejectHandshakeRequest(
1691
+ session,
1692
+ msg.from,
1693
+ `expected protocol version ${PROTOCOL_VERSION}, got ${gotVersion}`,
1694
+ "PROTOCOL_VERSION_MISMATCH",
1695
+ {
1696
+ ...session.loggingMetadata,
1697
+ connectedTo: msg.from,
1698
+ transportMessage: msg
1797
1699
  }
1798
- });
1799
- conn.send(this.codec.toBuffer(responseMsg2));
1800
- this.log?.warn(
1801
- `received handshake msg with incompatible protocol version (got: ${gotVersion}, expected: ${PROTOCOL_VERSION})`,
1802
- { ...conn.loggingMetadata, clientId: this.clientId }
1803
1700
  );
1804
- this.protocolError(ProtocolError.HandshakeFailed, reason);
1805
- return false;
1701
+ return;
1806
1702
  }
1807
- const oldSession = this.sessions.get(parsed.from);
1703
+ let oldSession = this.sessions.get(msg.from);
1808
1704
  const parsedMetadata = await this.validateHandshakeMetadata(
1809
- conn,
1705
+ session,
1810
1706
  oldSession,
1811
- parsed.payload.metadata,
1812
- parsed.from
1707
+ msg.payload.metadata,
1708
+ msg.from
1813
1709
  );
1814
1710
  if (parsedMetadata === false) {
1815
- return false;
1711
+ return;
1816
1712
  }
1817
- let session;
1818
- let isTransparentReconnect;
1819
- if (!parsed.payload.expectedSessionState) {
1820
- ({ session, isTransparentReconnect } = this.getOrCreateSession({
1821
- to: parsed.from,
1822
- conn,
1823
- sessionId: parsed.payload.sessionId,
1824
- propagationCtx: parsed.tracing
1825
- }));
1826
- } else if (parsed.payload.expectedSessionState.reconnect) {
1827
- const existingSession = this.getExistingSession({
1828
- to: parsed.from,
1829
- sessionId: parsed.payload.sessionId,
1830
- nextExpectedSeq: parsed.payload.expectedSessionState.nextExpectedSeq
1831
- });
1832
- if (existingSession === false) {
1833
- conn.telemetry?.span.setStatus({
1834
- code: import_api5.SpanStatusCode.ERROR,
1835
- message: SESSION_STATE_MISMATCH
1713
+ let connectCase = "new session";
1714
+ if (oldSession && oldSession.id === msg.payload.sessionId) {
1715
+ connectCase = "transparent reconnection";
1716
+ const clientNextExpectedSeq = msg.payload.expectedSessionState.nextExpectedSeq;
1717
+ const clientNextSentSeq = msg.payload.expectedSessionState.nextSentSeq ?? 0;
1718
+ const ourNextSeq = oldSession.nextSeq();
1719
+ const ourAck = oldSession.ack;
1720
+ if (clientNextSentSeq > ourAck) {
1721
+ this.rejectHandshakeRequest(
1722
+ session,
1723
+ msg.from,
1724
+ `client is in the future: server wanted next message to be ${ourAck} but client would have sent ${clientNextSentSeq}`,
1725
+ "SESSION_STATE_MISMATCH",
1726
+ {
1727
+ ...session.loggingMetadata,
1728
+ connectedTo: msg.from,
1729
+ transportMessage: msg
1730
+ }
1731
+ );
1732
+ return;
1733
+ }
1734
+ if (ourNextSeq > clientNextExpectedSeq) {
1735
+ this.rejectHandshakeRequest(
1736
+ session,
1737
+ msg.from,
1738
+ `server is in the future: client wanted next message to be ${clientNextExpectedSeq} but server would have sent ${ourNextSeq}`,
1739
+ "SESSION_STATE_MISMATCH",
1740
+ {
1741
+ ...session.loggingMetadata,
1742
+ connectedTo: msg.from,
1743
+ transportMessage: msg
1744
+ }
1745
+ );
1746
+ return;
1747
+ }
1748
+ if (oldSession.state === "Connected" /* Connected */) {
1749
+ const noConnectionSession = SessionStateGraph.transition.ConnectedToNoConnection(oldSession, {
1750
+ onSessionGracePeriodElapsed: () => {
1751
+ this.onSessionGracePeriodElapsed(noConnectionSession);
1752
+ }
1836
1753
  });
1837
- const reason = SESSION_STATE_MISMATCH;
1838
- const responseMsg2 = handshakeResponseMessage({
1839
- from: this.clientId,
1840
- to: parsed.from,
1841
- status: {
1842
- ok: false,
1843
- reason
1754
+ oldSession = noConnectionSession;
1755
+ } else if (oldSession.state === "Handshaking" /* Handshaking */) {
1756
+ const noConnectionSession = SessionStateGraph.transition.HandshakingToNoConnection(oldSession, {
1757
+ onSessionGracePeriodElapsed: () => {
1758
+ this.onSessionGracePeriodElapsed(noConnectionSession);
1844
1759
  }
1845
1760
  });
1846
- conn.send(this.codec.toBuffer(responseMsg2));
1847
- this.log?.warn(
1848
- `'received handshake msg with incompatible existing session state: ${parsed.payload.sessionId}`,
1849
- { ...conn.loggingMetadata, clientId: this.clientId }
1850
- );
1851
- this.protocolError(ProtocolError.HandshakeFailed, reason);
1852
- return false;
1761
+ oldSession = noConnectionSession;
1762
+ } else if (oldSession.state === "Connecting" /* Connecting */) {
1763
+ const noConnectionSession = SessionStateGraph.transition.ConnectingToNoConnection(oldSession, {
1764
+ onSessionGracePeriodElapsed: () => {
1765
+ this.onSessionGracePeriodElapsed(noConnectionSession);
1766
+ }
1767
+ });
1768
+ oldSession = noConnectionSession;
1853
1769
  }
1854
- session = existingSession;
1855
- isTransparentReconnect = false;
1770
+ this.updateSession(oldSession);
1771
+ } else if (oldSession) {
1772
+ connectCase = "hard reconnection";
1773
+ this.deleteSession(oldSession);
1774
+ oldSession = void 0;
1856
1775
  } else {
1857
- const createdSession = this.createNewSession({
1858
- to: parsed.from,
1859
- conn,
1860
- sessionId: parsed.payload.sessionId,
1861
- propagationCtx: parsed.tracing
1862
- });
1863
- session = createdSession;
1864
- isTransparentReconnect = false;
1776
+ connectCase = "unknown session";
1777
+ const clientNextExpectedSeq = msg.payload.expectedSessionState.nextExpectedSeq;
1778
+ const clientNextSentSeq = msg.payload.expectedSessionState.nextSentSeq ?? 0;
1779
+ if (clientNextSentSeq > 0 || clientNextExpectedSeq > 0) {
1780
+ this.rejectHandshakeRequest(
1781
+ session,
1782
+ msg.from,
1783
+ `client is trying to reconnect to a session the server don't know about: ${msg.payload.sessionId}`,
1784
+ "SESSION_STATE_MISMATCH",
1785
+ {
1786
+ ...session.loggingMetadata,
1787
+ connectedTo: msg.from,
1788
+ transportMessage: msg
1789
+ }
1790
+ );
1791
+ return;
1792
+ }
1865
1793
  }
1866
- this.sessionHandshakeMetadata.set(session, parsedMetadata);
1867
- this.log?.debug(
1868
- `handshake from ${parsed.from} ok, responding with handshake success`,
1869
- conn.loggingMetadata
1794
+ const sessionId = msg.payload.sessionId;
1795
+ this.log?.info(
1796
+ `handshake from ${msg.from} ok (${connectCase}), responding with handshake success`,
1797
+ {
1798
+ ...session.loggingMetadata,
1799
+ connectedTo: msg.from
1800
+ }
1870
1801
  );
1871
1802
  const responseMsg = handshakeResponseMessage({
1872
1803
  from: this.clientId,
1873
- to: parsed.from,
1804
+ to: msg.from,
1874
1805
  status: {
1875
1806
  ok: true,
1876
- sessionId: session.id
1807
+ sessionId
1877
1808
  }
1878
1809
  });
1879
- conn.send(this.codec.toBuffer(responseMsg));
1880
- this.onConnect(conn, session, isTransparentReconnect);
1881
- return session;
1810
+ session.sendHandshake(responseMsg);
1811
+ const connectedSession = SessionStateGraph.transition.WaitingForHandshakeToConnected(
1812
+ session,
1813
+ // by this point oldSession is either no connection or we dont have an old session
1814
+ oldSession,
1815
+ sessionId,
1816
+ msg.from,
1817
+ msg.tracing,
1818
+ {
1819
+ onConnectionErrored: (err) => {
1820
+ const errStr = coerceErrorString(err);
1821
+ this.log?.warn(
1822
+ `connection to ${connectedSession.to} errored: ${errStr}`,
1823
+ connectedSession.loggingMetadata
1824
+ );
1825
+ },
1826
+ onConnectionClosed: () => {
1827
+ this.log?.info(
1828
+ `connection to ${connectedSession.to} closed`,
1829
+ connectedSession.loggingMetadata
1830
+ );
1831
+ this.onConnClosed(connectedSession);
1832
+ },
1833
+ onMessage: (msg2) => this.handleMsg(msg2),
1834
+ onInvalidMessage: (reason) => {
1835
+ this.protocolError(ProtocolError.MessageOrderingViolated, reason);
1836
+ this.deleteSession(connectedSession);
1837
+ }
1838
+ }
1839
+ );
1840
+ this.sessionHandshakeMetadata.set(connectedSession.to, parsedMetadata);
1841
+ this.updateSession(connectedSession);
1842
+ this.pendingSessions.delete(session);
1843
+ connectedSession.startActiveHeartbeat();
1844
+ }
1845
+ async validateHandshakeMetadata(handshakingSession, existingSession, rawMetadata, from) {
1846
+ let parsedMetadata = {};
1847
+ if (this.handshakeExtensions) {
1848
+ if (!import_value3.Value.Check(this.handshakeExtensions.schema, rawMetadata)) {
1849
+ this.rejectHandshakeRequest(
1850
+ handshakingSession,
1851
+ from,
1852
+ "received malformed handshake metadata",
1853
+ "MALFORMED_HANDSHAKE_META",
1854
+ {
1855
+ ...handshakingSession.loggingMetadata,
1856
+ connectedTo: from,
1857
+ validationErrors: [
1858
+ ...import_value3.Value.Errors(this.handshakeExtensions.schema, rawMetadata)
1859
+ ]
1860
+ }
1861
+ );
1862
+ return false;
1863
+ }
1864
+ const previousParsedMetadata = existingSession ? this.sessionHandshakeMetadata.get(existingSession.to) : void 0;
1865
+ parsedMetadata = await this.handshakeExtensions.validate(
1866
+ rawMetadata,
1867
+ previousParsedMetadata
1868
+ );
1869
+ if (parsedMetadata === false) {
1870
+ this.rejectHandshakeRequest(
1871
+ handshakingSession,
1872
+ from,
1873
+ "rejected by handshake handler",
1874
+ "REJECTED_BY_CUSTOM_HANDLER",
1875
+ {
1876
+ ...handshakingSession.loggingMetadata,
1877
+ connectedTo: from,
1878
+ clientId: this.clientId
1879
+ }
1880
+ );
1881
+ return false;
1882
+ }
1883
+ }
1884
+ return parsedMetadata;
1885
+ }
1886
+ };
1887
+
1888
+ // transport/connection.ts
1889
+ var Connection = class {
1890
+ id;
1891
+ telemetry;
1892
+ constructor() {
1893
+ this.id = `conn-${generateId()}`;
1894
+ }
1895
+ get loggingMetadata() {
1896
+ const metadata = { connId: this.id };
1897
+ const spanContext = this.telemetry?.span.spanContext();
1898
+ if (this.telemetry?.span.isRecording() && spanContext) {
1899
+ metadata.telemetry = {
1900
+ traceId: spanContext.traceId,
1901
+ spanId: spanContext.spanId
1902
+ };
1903
+ }
1904
+ return metadata;
1905
+ }
1906
+ // can't use event emitter because we need this to work in both node + browser
1907
+ _dataListeners = /* @__PURE__ */ new Set();
1908
+ _closeListeners = /* @__PURE__ */ new Set();
1909
+ _errorListeners = /* @__PURE__ */ new Set();
1910
+ get dataListeners() {
1911
+ return [...this._dataListeners];
1912
+ }
1913
+ get closeListeners() {
1914
+ return [...this._closeListeners];
1915
+ }
1916
+ get errorListeners() {
1917
+ return [...this._errorListeners];
1918
+ }
1919
+ /**
1920
+ * Handle adding a callback for when a message is received.
1921
+ * @param msg The message that was received.
1922
+ */
1923
+ addDataListener(cb) {
1924
+ this._dataListeners.add(cb);
1925
+ }
1926
+ removeDataListener(cb) {
1927
+ this._dataListeners.delete(cb);
1928
+ }
1929
+ /**
1930
+ * Handle adding a callback for when the connection is closed.
1931
+ * This should also be called if an error happens and after notifying all the error listeners.
1932
+ * @param cb The callback to call when the connection is closed.
1933
+ */
1934
+ addCloseListener(cb) {
1935
+ this._closeListeners.add(cb);
1936
+ }
1937
+ removeCloseListener(cb) {
1938
+ this._closeListeners.delete(cb);
1939
+ }
1940
+ /**
1941
+ * Handle adding a callback for when an error is received.
1942
+ * This should only be used for this.logging errors, all cleanup
1943
+ * should be delegated to addCloseListener.
1944
+ *
1945
+ * The implementer should take care such that the implemented
1946
+ * connection will call both the close and error callbacks
1947
+ * on an error.
1948
+ *
1949
+ * @param cb The callback to call when an error is received.
1950
+ */
1951
+ addErrorListener(cb) {
1952
+ this._errorListeners.add(cb);
1953
+ }
1954
+ removeErrorListener(cb) {
1955
+ this._errorListeners.delete(cb);
1882
1956
  }
1883
1957
  };
1884
1958
  // Annotate the CommonJS export names for ESM import in node:
@@ -1888,7 +1962,7 @@ var ServerTransport = class extends Transport {
1888
1962
  OpaqueTransportMessageSchema,
1889
1963
  ProtocolError,
1890
1964
  ServerTransport,
1891
- Session,
1965
+ SessionState,
1892
1966
  Transport,
1893
1967
  TransportMessageSchema
1894
1968
  });