@replit/river 0.207.2 → 0.208.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 (65) hide show
  1. package/dist/adapter-ChksXKVN.d.ts +46 -0
  2. package/dist/adapter-Cuc4JtfV.d.cts +46 -0
  3. package/dist/chunk-2JNVDUMN.js +2238 -0
  4. package/dist/chunk-2JNVDUMN.js.map +1 -0
  5. package/dist/{chunk-4HE7UYRL.js → chunk-DKW3GC3M.js} +6 -5
  6. package/dist/{chunk-4HE7UYRL.js.map → chunk-DKW3GC3M.js.map} +1 -1
  7. package/dist/{chunk-46IVOKJU.js → chunk-ETZAHFGQ.js} +80 -61
  8. package/dist/chunk-ETZAHFGQ.js.map +1 -0
  9. package/dist/codec/index.cjs +157 -23
  10. package/dist/codec/index.cjs.map +1 -1
  11. package/dist/codec/index.d.cts +5 -1
  12. package/dist/codec/index.d.ts +5 -1
  13. package/dist/codec/index.js +6 -20
  14. package/dist/codec/index.js.map +1 -1
  15. package/dist/connection-BF4zg6Qv.d.cts +35 -0
  16. package/dist/{connection-a18e31d5.d.ts → connection-Donr3JRB.d.ts} +4 -3
  17. package/dist/index-C9tpZjBN.d.cts +37 -0
  18. package/dist/index-D8IOd3LG.d.ts +37 -0
  19. package/dist/logging/index.d.cts +2 -1
  20. package/dist/logging/index.d.ts +2 -1
  21. package/dist/{message-ffacb98a.d.ts → message-Di94OL80.d.cts} +1 -35
  22. package/dist/message-Di94OL80.d.ts +108 -0
  23. package/dist/router/index.cjs +62 -43
  24. package/dist/router/index.cjs.map +1 -1
  25. package/dist/router/index.d.cts +27 -7
  26. package/dist/router/index.d.ts +27 -7
  27. package/dist/router/index.js +1 -1
  28. package/dist/testUtil/index.cjs +828 -725
  29. package/dist/testUtil/index.cjs.map +1 -1
  30. package/dist/testUtil/index.d.cts +5 -4
  31. package/dist/testUtil/index.d.ts +5 -4
  32. package/dist/testUtil/index.js +23 -25
  33. package/dist/testUtil/index.js.map +1 -1
  34. package/dist/transport/impls/ws/client.cjs +293 -233
  35. package/dist/transport/impls/ws/client.cjs.map +1 -1
  36. package/dist/transport/impls/ws/client.d.cts +6 -5
  37. package/dist/transport/impls/ws/client.d.ts +6 -5
  38. package/dist/transport/impls/ws/client.js +5 -7
  39. package/dist/transport/impls/ws/client.js.map +1 -1
  40. package/dist/transport/impls/ws/server.cjs +269 -200
  41. package/dist/transport/impls/ws/server.cjs.map +1 -1
  42. package/dist/transport/impls/ws/server.d.cts +6 -5
  43. package/dist/transport/impls/ws/server.d.ts +6 -5
  44. package/dist/transport/impls/ws/server.js +5 -7
  45. package/dist/transport/impls/ws/server.js.map +1 -1
  46. package/dist/transport/index.cjs +438 -342
  47. package/dist/transport/index.cjs.map +1 -1
  48. package/dist/transport/index.d.cts +7 -6
  49. package/dist/transport/index.d.ts +7 -6
  50. package/dist/transport/index.js +5 -10
  51. package/dist/transport-CCaWx1Rb.d.cts +1566 -0
  52. package/dist/{services-43528f4b.d.ts → transport-CZb3vdB4.d.ts} +294 -293
  53. package/dist/{wslike-e0b32dd5.d.ts → wslike-Dng9H1C7.d.cts} +1 -1
  54. package/dist/wslike-Dng9H1C7.d.ts +40 -0
  55. package/package.json +3 -3
  56. package/dist/chunk-24EWYOGK.js +0 -1287
  57. package/dist/chunk-24EWYOGK.js.map +0 -1
  58. package/dist/chunk-46IVOKJU.js.map +0 -1
  59. package/dist/chunk-A7RGOVRV.js +0 -438
  60. package/dist/chunk-A7RGOVRV.js.map +0 -1
  61. package/dist/chunk-AJGIY2UB.js +0 -56
  62. package/dist/chunk-AJGIY2UB.js.map +0 -1
  63. package/dist/chunk-XV4RQ62N.js +0 -377
  64. package/dist/chunk-XV4RQ62N.js.map +0 -1
  65. package/dist/types-3e5768ec.d.ts +0 -20
@@ -0,0 +1,2238 @@
1
+ import {
2
+ BaseLogger,
3
+ createLogProxy
4
+ } from "./chunk-CC7RN7GI.js";
5
+ import {
6
+ ControlMessageHandshakeRequestSchema,
7
+ ControlMessageHandshakeResponseSchema,
8
+ HandshakeErrorCustomHandlerFatalResponseCodes,
9
+ HandshakeErrorRetriableResponseCodes,
10
+ OpaqueTransportMessageSchema,
11
+ acceptedProtocolVersions,
12
+ coerceErrorString,
13
+ createConnectionTelemetryInfo,
14
+ createSessionTelemetryInfo,
15
+ currentProtocolVersion,
16
+ generateId,
17
+ getPropagationContext,
18
+ getTracer,
19
+ handshakeRequestMessage,
20
+ handshakeResponseMessage,
21
+ isAcceptedProtocolVersion,
22
+ isAck
23
+ } from "./chunk-ETZAHFGQ.js";
24
+
25
+ // transport/events.ts
26
+ var ProtocolError = {
27
+ RetriesExceeded: "conn_retry_exceeded",
28
+ HandshakeFailed: "handshake_failed",
29
+ MessageOrderingViolated: "message_ordering_violated",
30
+ InvalidMessage: "invalid_message",
31
+ MessageSendFailure: "message_send_failure"
32
+ };
33
+ var EventDispatcher = class {
34
+ eventListeners = {};
35
+ removeAllListeners() {
36
+ this.eventListeners = {};
37
+ }
38
+ numberOfListeners(eventType) {
39
+ return this.eventListeners[eventType]?.size ?? 0;
40
+ }
41
+ addEventListener(eventType, handler) {
42
+ if (!this.eventListeners[eventType]) {
43
+ this.eventListeners[eventType] = /* @__PURE__ */ new Set();
44
+ }
45
+ this.eventListeners[eventType]?.add(handler);
46
+ }
47
+ removeEventListener(eventType, handler) {
48
+ const handlers = this.eventListeners[eventType];
49
+ if (handlers) {
50
+ this.eventListeners[eventType]?.delete(handler);
51
+ }
52
+ }
53
+ dispatchEvent(eventType, event) {
54
+ const handlers = this.eventListeners[eventType];
55
+ if (handlers) {
56
+ const copy = [...handlers];
57
+ for (const handler of copy) {
58
+ handler(event);
59
+ }
60
+ }
61
+ }
62
+ };
63
+
64
+ // codec/json.ts
65
+ var encoder = new TextEncoder();
66
+ var decoder = new TextDecoder();
67
+ function uint8ArrayToBase64(uint8Array) {
68
+ let binary = "";
69
+ uint8Array.forEach((byte) => {
70
+ binary += String.fromCharCode(byte);
71
+ });
72
+ return btoa(binary);
73
+ }
74
+ function base64ToUint8Array(base64) {
75
+ const binaryString = atob(base64);
76
+ const uint8Array = new Uint8Array(binaryString.length);
77
+ for (let i = 0; i < binaryString.length; i++) {
78
+ uint8Array[i] = binaryString.charCodeAt(i);
79
+ }
80
+ return uint8Array;
81
+ }
82
+ var NaiveJsonCodec = {
83
+ toBuffer: (obj) => {
84
+ return encoder.encode(
85
+ JSON.stringify(obj, function replacer(key) {
86
+ const val = this[key];
87
+ if (val instanceof Uint8Array) {
88
+ return { $t: uint8ArrayToBase64(val) };
89
+ } else {
90
+ return val;
91
+ }
92
+ })
93
+ );
94
+ },
95
+ fromBuffer: (buff) => {
96
+ const parsed = JSON.parse(
97
+ decoder.decode(buff),
98
+ function reviver(_key, val) {
99
+ if (val?.$t) {
100
+ return base64ToUint8Array(val.$t);
101
+ } else {
102
+ return val;
103
+ }
104
+ }
105
+ );
106
+ if (typeof parsed !== "object" || parsed === null) {
107
+ throw new Error("unpacked msg is not an object");
108
+ }
109
+ return parsed;
110
+ }
111
+ };
112
+
113
+ // transport/options.ts
114
+ var defaultTransportOptions = {
115
+ heartbeatIntervalMs: 1e3,
116
+ heartbeatsUntilDead: 2,
117
+ sessionDisconnectGraceMs: 5e3,
118
+ connectionTimeoutMs: 2e3,
119
+ handshakeTimeoutMs: 1e3,
120
+ enableTransparentSessionReconnects: true,
121
+ codec: NaiveJsonCodec
122
+ };
123
+ var defaultConnectionRetryOptions = {
124
+ baseIntervalMs: 150,
125
+ maxJitterMs: 200,
126
+ maxBackoffMs: 32e3,
127
+ attemptBudgetCapacity: 5,
128
+ budgetRestoreIntervalMs: 200
129
+ };
130
+ var defaultClientTransportOptions = {
131
+ ...defaultTransportOptions,
132
+ ...defaultConnectionRetryOptions
133
+ };
134
+ var defaultServerTransportOptions = {
135
+ ...defaultTransportOptions
136
+ };
137
+
138
+ // transport/sessionStateMachine/common.ts
139
+ var SessionState = /* @__PURE__ */ ((SessionState2) => {
140
+ SessionState2["NoConnection"] = "NoConnection";
141
+ SessionState2["BackingOff"] = "BackingOff";
142
+ SessionState2["Connecting"] = "Connecting";
143
+ SessionState2["Handshaking"] = "Handshaking";
144
+ SessionState2["Connected"] = "Connected";
145
+ SessionState2["WaitingForHandshake"] = "WaitingForHandshake";
146
+ return SessionState2;
147
+ })(SessionState || {});
148
+ var ERR_CONSUMED = `session state has been consumed and is no longer valid`;
149
+ var StateMachineState = class {
150
+ /*
151
+ * Whether this state has been consumed
152
+ * and we've moved on to another state
153
+ */
154
+ _isConsumed;
155
+ /**
156
+ * Cleanup this state machine state and mark it as consumed.
157
+ * After calling close, it is an error to access any properties on the state.
158
+ * You should never need to call this as a consumer.
159
+ *
160
+ * If you're looking to close the session from the client,
161
+ * use `.hardDisconnect` on the client transport.
162
+ */
163
+ close() {
164
+ this._handleClose();
165
+ }
166
+ constructor() {
167
+ this._isConsumed = false;
168
+ return new Proxy(this, {
169
+ get(target, prop) {
170
+ if (prop === "_isConsumed" || prop === "id" || prop === "state") {
171
+ return Reflect.get(target, prop);
172
+ }
173
+ if (prop === "_handleStateExit") {
174
+ return () => {
175
+ target._isConsumed = true;
176
+ target._handleStateExit();
177
+ };
178
+ }
179
+ if (prop === "_handleClose") {
180
+ return () => {
181
+ target._isConsumed = true;
182
+ target._handleStateExit();
183
+ target._handleClose();
184
+ };
185
+ }
186
+ if (target._isConsumed) {
187
+ throw new Error(
188
+ `${ERR_CONSUMED}: getting ${prop.toString()} on consumed state`
189
+ );
190
+ }
191
+ return Reflect.get(target, prop);
192
+ },
193
+ set(target, prop, value) {
194
+ if (target._isConsumed) {
195
+ throw new Error(
196
+ `${ERR_CONSUMED}: setting ${prop.toString()} on consumed state`
197
+ );
198
+ }
199
+ return Reflect.set(target, prop, value);
200
+ }
201
+ });
202
+ }
203
+ };
204
+ var CommonSession = class extends StateMachineState {
205
+ from;
206
+ options;
207
+ codec;
208
+ tracer;
209
+ log;
210
+ constructor({ from, options, log, tracer, codec }) {
211
+ super();
212
+ this.from = from;
213
+ this.options = options;
214
+ this.log = log;
215
+ this.tracer = tracer;
216
+ this.codec = codec;
217
+ }
218
+ };
219
+ var IdentifiedSession = class extends CommonSession {
220
+ id;
221
+ telemetry;
222
+ to;
223
+ protocolVersion;
224
+ /**
225
+ * Index of the message we will send next (excluding handshake)
226
+ */
227
+ seq;
228
+ /**
229
+ * Last seq we sent over the wire this session (excluding handshake) and retransmissions
230
+ */
231
+ seqSent;
232
+ /**
233
+ * Number of unique messages we've received this session (excluding handshake)
234
+ */
235
+ ack;
236
+ sendBuffer;
237
+ constructor(props) {
238
+ const {
239
+ id,
240
+ to,
241
+ seq,
242
+ ack,
243
+ sendBuffer,
244
+ telemetry,
245
+ log,
246
+ protocolVersion,
247
+ seqSent: messagesSent
248
+ } = props;
249
+ super(props);
250
+ this.id = id;
251
+ this.to = to;
252
+ this.seq = seq;
253
+ this.ack = ack;
254
+ this.sendBuffer = sendBuffer;
255
+ this.telemetry = telemetry;
256
+ this.log = log;
257
+ this.protocolVersion = protocolVersion;
258
+ this.seqSent = messagesSent;
259
+ }
260
+ get loggingMetadata() {
261
+ const metadata = {
262
+ clientId: this.from,
263
+ connectedTo: this.to,
264
+ sessionId: this.id
265
+ };
266
+ if (this.telemetry.span.isRecording()) {
267
+ const spanContext = this.telemetry.span.spanContext();
268
+ metadata.telemetry = {
269
+ traceId: spanContext.traceId,
270
+ spanId: spanContext.spanId
271
+ };
272
+ }
273
+ return metadata;
274
+ }
275
+ constructMsg(partialMsg) {
276
+ const msg = {
277
+ ...partialMsg,
278
+ id: generateId(),
279
+ to: this.to,
280
+ from: this.from,
281
+ seq: this.seq,
282
+ ack: this.ack
283
+ };
284
+ this.seq++;
285
+ return msg;
286
+ }
287
+ nextSeq() {
288
+ return this.sendBuffer.length > 0 ? this.sendBuffer[0].seq : this.seq;
289
+ }
290
+ send(msg) {
291
+ const constructedMsg = this.constructMsg(msg);
292
+ this.sendBuffer.push(constructedMsg);
293
+ return {
294
+ ok: true,
295
+ value: constructedMsg.id
296
+ };
297
+ }
298
+ _handleStateExit() {
299
+ }
300
+ _handleClose() {
301
+ this.sendBuffer.length = 0;
302
+ this.telemetry.span.end();
303
+ }
304
+ };
305
+ var IdentifiedSessionWithGracePeriod = class extends IdentifiedSession {
306
+ graceExpiryTime;
307
+ gracePeriodTimeout;
308
+ listeners;
309
+ constructor(props) {
310
+ super(props);
311
+ this.listeners = props.listeners;
312
+ this.graceExpiryTime = props.graceExpiryTime;
313
+ this.gracePeriodTimeout = setTimeout(() => {
314
+ this.listeners.onSessionGracePeriodElapsed();
315
+ }, this.graceExpiryTime - Date.now());
316
+ }
317
+ _handleStateExit() {
318
+ super._handleStateExit();
319
+ if (this.gracePeriodTimeout) {
320
+ clearTimeout(this.gracePeriodTimeout);
321
+ this.gracePeriodTimeout = void 0;
322
+ }
323
+ }
324
+ _handleClose() {
325
+ super._handleClose();
326
+ }
327
+ };
328
+ function sendMessage(conn, codec, msg) {
329
+ const buff = codec.toBuffer(msg);
330
+ if (!buff.ok) {
331
+ return buff;
332
+ }
333
+ const sent = conn.send(buff.value);
334
+ if (!sent) {
335
+ return {
336
+ ok: false,
337
+ reason: "failed to send message"
338
+ };
339
+ }
340
+ return {
341
+ ok: true,
342
+ value: msg.id
343
+ };
344
+ }
345
+
346
+ // transport/sessionStateMachine/SessionConnecting.ts
347
+ var SessionConnecting = class extends IdentifiedSessionWithGracePeriod {
348
+ state = "Connecting" /* Connecting */;
349
+ connPromise;
350
+ listeners;
351
+ connectionTimeout;
352
+ constructor(props) {
353
+ super(props);
354
+ this.connPromise = props.connPromise;
355
+ this.listeners = props.listeners;
356
+ this.connPromise.then(
357
+ (conn) => {
358
+ if (this._isConsumed) return;
359
+ this.listeners.onConnectionEstablished(conn);
360
+ },
361
+ (err) => {
362
+ if (this._isConsumed) return;
363
+ this.listeners.onConnectionFailed(err);
364
+ }
365
+ );
366
+ this.connectionTimeout = setTimeout(() => {
367
+ this.listeners.onConnectionTimeout();
368
+ }, this.options.connectionTimeoutMs);
369
+ }
370
+ // close a pending connection if it resolves, ignore errors if the promise
371
+ // ends up rejected anyways
372
+ bestEffortClose() {
373
+ const logger = this.log;
374
+ const metadata = this.loggingMetadata;
375
+ this.connPromise.then((conn) => {
376
+ conn.close();
377
+ logger?.info(
378
+ "connection eventually resolved but session has transitioned, closed connection",
379
+ {
380
+ ...metadata,
381
+ ...conn.loggingMetadata
382
+ }
383
+ );
384
+ }).catch(() => {
385
+ });
386
+ }
387
+ _handleStateExit() {
388
+ super._handleStateExit();
389
+ if (this.connectionTimeout) {
390
+ clearTimeout(this.connectionTimeout);
391
+ this.connectionTimeout = void 0;
392
+ }
393
+ }
394
+ _handleClose() {
395
+ super._handleClose();
396
+ this.bestEffortClose();
397
+ }
398
+ };
399
+
400
+ // transport/sessionStateMachine/SessionNoConnection.ts
401
+ var SessionNoConnection = class extends IdentifiedSessionWithGracePeriod {
402
+ state = "NoConnection" /* NoConnection */;
403
+ _handleClose() {
404
+ super._handleClose();
405
+ }
406
+ _handleStateExit() {
407
+ super._handleStateExit();
408
+ }
409
+ };
410
+
411
+ // transport/sessionStateMachine/SessionWaitingForHandshake.ts
412
+ var SessionWaitingForHandshake = class extends CommonSession {
413
+ state = "WaitingForHandshake" /* WaitingForHandshake */;
414
+ conn;
415
+ listeners;
416
+ handshakeTimeout;
417
+ constructor(props) {
418
+ super(props);
419
+ this.conn = props.conn;
420
+ this.listeners = props.listeners;
421
+ this.handshakeTimeout = setTimeout(() => {
422
+ this.listeners.onHandshakeTimeout();
423
+ }, this.options.handshakeTimeoutMs);
424
+ this.conn.setDataListener(this.onHandshakeData);
425
+ this.conn.setErrorListener(this.listeners.onConnectionErrored);
426
+ this.conn.setCloseListener(this.listeners.onConnectionClosed);
427
+ }
428
+ get loggingMetadata() {
429
+ return {
430
+ clientId: this.from,
431
+ connId: this.conn.id,
432
+ ...this.conn.loggingMetadata
433
+ };
434
+ }
435
+ onHandshakeData = (msg) => {
436
+ const parsedMsgRes = this.codec.fromBuffer(msg);
437
+ if (!parsedMsgRes.ok) {
438
+ this.listeners.onInvalidHandshake(
439
+ `could not parse handshake message: ${parsedMsgRes.reason}`,
440
+ "MALFORMED_HANDSHAKE"
441
+ );
442
+ return;
443
+ }
444
+ this.listeners.onHandshake(parsedMsgRes.value);
445
+ };
446
+ sendHandshake(msg) {
447
+ return sendMessage(this.conn, this.codec, msg);
448
+ }
449
+ _handleStateExit() {
450
+ this.conn.removeDataListener();
451
+ this.conn.removeErrorListener();
452
+ this.conn.removeCloseListener();
453
+ clearTimeout(this.handshakeTimeout);
454
+ this.handshakeTimeout = void 0;
455
+ }
456
+ _handleClose() {
457
+ this.conn.close();
458
+ }
459
+ };
460
+
461
+ // transport/sessionStateMachine/SessionHandshaking.ts
462
+ var SessionHandshaking = class extends IdentifiedSessionWithGracePeriod {
463
+ state = "Handshaking" /* Handshaking */;
464
+ conn;
465
+ listeners;
466
+ handshakeTimeout;
467
+ constructor(props) {
468
+ super(props);
469
+ this.conn = props.conn;
470
+ this.listeners = props.listeners;
471
+ this.handshakeTimeout = setTimeout(() => {
472
+ this.listeners.onHandshakeTimeout();
473
+ }, this.options.handshakeTimeoutMs);
474
+ this.conn.setDataListener(this.onHandshakeData);
475
+ this.conn.setErrorListener(this.listeners.onConnectionErrored);
476
+ this.conn.setCloseListener(this.listeners.onConnectionClosed);
477
+ }
478
+ get loggingMetadata() {
479
+ return {
480
+ ...super.loggingMetadata,
481
+ ...this.conn.loggingMetadata
482
+ };
483
+ }
484
+ onHandshakeData = (msg) => {
485
+ const parsedMsgRes = this.codec.fromBuffer(msg);
486
+ if (!parsedMsgRes.ok) {
487
+ this.listeners.onInvalidHandshake(
488
+ `could not parse handshake message: ${parsedMsgRes.reason}`,
489
+ "MALFORMED_HANDSHAKE"
490
+ );
491
+ return;
492
+ }
493
+ this.listeners.onHandshake(parsedMsgRes.value);
494
+ };
495
+ sendHandshake(msg) {
496
+ return sendMessage(this.conn, this.codec, msg);
497
+ }
498
+ _handleStateExit() {
499
+ super._handleStateExit();
500
+ this.conn.removeDataListener();
501
+ this.conn.removeErrorListener();
502
+ this.conn.removeCloseListener();
503
+ if (this.handshakeTimeout) {
504
+ clearTimeout(this.handshakeTimeout);
505
+ this.handshakeTimeout = void 0;
506
+ }
507
+ }
508
+ _handleClose() {
509
+ super._handleClose();
510
+ this.conn.close();
511
+ }
512
+ };
513
+
514
+ // transport/sessionStateMachine/SessionConnected.ts
515
+ import { SpanStatusCode } from "@opentelemetry/api";
516
+ var SessionConnected = class extends IdentifiedSession {
517
+ state = "Connected" /* Connected */;
518
+ conn;
519
+ listeners;
520
+ heartbeatHandle;
521
+ heartbeatMissTimeout;
522
+ isActivelyHeartbeating = false;
523
+ updateBookkeeping(ack, seq) {
524
+ this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
525
+ this.ack = seq + 1;
526
+ if (this.heartbeatMissTimeout) {
527
+ clearTimeout(this.heartbeatMissTimeout);
528
+ }
529
+ this.startMissingHeartbeatTimeout();
530
+ }
531
+ assertSendOrdering(constructedMsg) {
532
+ if (constructedMsg.seq > this.seqSent + 1) {
533
+ const msg = `invariant violation: would have sent out of order msg (seq: ${constructedMsg.seq}, expected: ${this.seqSent} + 1)`;
534
+ this.log?.error(msg, {
535
+ ...this.loggingMetadata,
536
+ transportMessage: constructedMsg,
537
+ tags: ["invariant-violation"]
538
+ });
539
+ throw new Error(msg);
540
+ }
541
+ }
542
+ send(msg) {
543
+ const constructedMsg = this.constructMsg(msg);
544
+ this.assertSendOrdering(constructedMsg);
545
+ this.sendBuffer.push(constructedMsg);
546
+ const res = sendMessage(this.conn, this.codec, constructedMsg);
547
+ if (!res.ok) {
548
+ this.listeners.onMessageSendFailure(constructedMsg, res.reason);
549
+ return res;
550
+ }
551
+ this.seqSent = constructedMsg.seq;
552
+ return res;
553
+ }
554
+ constructor(props) {
555
+ super(props);
556
+ this.conn = props.conn;
557
+ this.listeners = props.listeners;
558
+ this.conn.setDataListener(this.onMessageData);
559
+ this.conn.setCloseListener(this.listeners.onConnectionClosed);
560
+ this.conn.setErrorListener(this.listeners.onConnectionErrored);
561
+ }
562
+ sendBufferedMessages() {
563
+ if (this.sendBuffer.length > 0) {
564
+ this.log?.info(
565
+ `sending ${this.sendBuffer.length} buffered messages, starting at seq ${this.nextSeq()}`,
566
+ this.loggingMetadata
567
+ );
568
+ for (const msg of this.sendBuffer) {
569
+ this.assertSendOrdering(msg);
570
+ const res = sendMessage(this.conn, this.codec, msg);
571
+ if (!res.ok) {
572
+ this.listeners.onMessageSendFailure(msg, res.reason);
573
+ return res;
574
+ }
575
+ this.seqSent = msg.seq;
576
+ }
577
+ }
578
+ return { ok: true, value: void 0 };
579
+ }
580
+ get loggingMetadata() {
581
+ return {
582
+ ...super.loggingMetadata,
583
+ ...this.conn.loggingMetadata
584
+ };
585
+ }
586
+ startMissingHeartbeatTimeout() {
587
+ const maxMisses = this.options.heartbeatsUntilDead;
588
+ const missDuration = maxMisses * this.options.heartbeatIntervalMs;
589
+ this.heartbeatMissTimeout = setTimeout(() => {
590
+ this.log?.info(
591
+ `closing connection to ${this.to} due to inactivity (missed ${maxMisses} heartbeats which is ${missDuration}ms)`,
592
+ this.loggingMetadata
593
+ );
594
+ this.telemetry.span.addEvent(
595
+ "closing connection due to missing heartbeat"
596
+ );
597
+ this.conn.close();
598
+ }, missDuration);
599
+ }
600
+ startActiveHeartbeat() {
601
+ this.isActivelyHeartbeating = true;
602
+ this.heartbeatHandle = setInterval(() => {
603
+ this.sendHeartbeat();
604
+ }, this.options.heartbeatIntervalMs);
605
+ }
606
+ sendHeartbeat() {
607
+ this.log?.debug("sending heartbeat", this.loggingMetadata);
608
+ const heartbeat = {
609
+ streamId: "heartbeat",
610
+ controlFlags: 1 /* AckBit */,
611
+ payload: {
612
+ type: "ACK"
613
+ }
614
+ };
615
+ this.send(heartbeat);
616
+ }
617
+ onMessageData = (msg) => {
618
+ const parsedMsgRes = this.codec.fromBuffer(msg);
619
+ if (!parsedMsgRes.ok) {
620
+ this.listeners.onInvalidMessage(
621
+ `could not parse message: ${parsedMsgRes.reason}`
622
+ );
623
+ return;
624
+ }
625
+ const parsedMsg = parsedMsgRes.value;
626
+ if (parsedMsg.seq !== this.ack) {
627
+ if (parsedMsg.seq < this.ack) {
628
+ this.log?.debug(
629
+ `received duplicate msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack}), discarding`,
630
+ {
631
+ ...this.loggingMetadata,
632
+ transportMessage: parsedMsg
633
+ }
634
+ );
635
+ } else {
636
+ const reason = `received out-of-order msg, closing connection (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack})`;
637
+ this.log?.error(reason, {
638
+ ...this.loggingMetadata,
639
+ transportMessage: parsedMsg,
640
+ tags: ["invariant-violation"]
641
+ });
642
+ this.telemetry.span.setStatus({
643
+ code: SpanStatusCode.ERROR,
644
+ message: reason
645
+ });
646
+ this.conn.close();
647
+ }
648
+ return;
649
+ }
650
+ this.log?.debug(`received msg`, {
651
+ ...this.loggingMetadata,
652
+ transportMessage: parsedMsg
653
+ });
654
+ this.updateBookkeeping(parsedMsg.ack, parsedMsg.seq);
655
+ if (!isAck(parsedMsg.controlFlags)) {
656
+ this.listeners.onMessage(parsedMsg);
657
+ return;
658
+ }
659
+ this.log?.debug(`discarding msg (ack bit set)`, {
660
+ ...this.loggingMetadata,
661
+ transportMessage: parsedMsg
662
+ });
663
+ if (!this.isActivelyHeartbeating) {
664
+ this.sendHeartbeat();
665
+ }
666
+ };
667
+ _handleStateExit() {
668
+ super._handleStateExit();
669
+ this.conn.removeDataListener();
670
+ this.conn.removeCloseListener();
671
+ this.conn.removeErrorListener();
672
+ if (this.heartbeatHandle) {
673
+ clearInterval(this.heartbeatHandle);
674
+ this.heartbeatHandle = void 0;
675
+ }
676
+ if (this.heartbeatMissTimeout) {
677
+ clearTimeout(this.heartbeatMissTimeout);
678
+ this.heartbeatMissTimeout = void 0;
679
+ }
680
+ }
681
+ _handleClose() {
682
+ super._handleClose();
683
+ this.conn.close();
684
+ }
685
+ };
686
+
687
+ // transport/sessionStateMachine/SessionBackingOff.ts
688
+ var SessionBackingOff = class extends IdentifiedSessionWithGracePeriod {
689
+ state = "BackingOff" /* BackingOff */;
690
+ listeners;
691
+ backoffTimeout;
692
+ constructor(props) {
693
+ super(props);
694
+ this.listeners = props.listeners;
695
+ this.backoffTimeout = setTimeout(() => {
696
+ this.listeners.onBackoffFinished();
697
+ }, props.backoffMs);
698
+ }
699
+ _handleClose() {
700
+ super._handleClose();
701
+ }
702
+ _handleStateExit() {
703
+ super._handleStateExit();
704
+ if (this.backoffTimeout) {
705
+ clearTimeout(this.backoffTimeout);
706
+ this.backoffTimeout = void 0;
707
+ }
708
+ }
709
+ };
710
+
711
+ // codec/binary.ts
712
+ import { decode, encode } from "@msgpack/msgpack";
713
+ var BinaryCodec = {
714
+ toBuffer(obj) {
715
+ return encode(obj, { ignoreUndefined: true });
716
+ },
717
+ fromBuffer: (buff) => {
718
+ const res = decode(buff);
719
+ if (typeof res !== "object" || res === null) {
720
+ throw new Error("unpacked msg is not an object");
721
+ }
722
+ return res;
723
+ }
724
+ };
725
+
726
+ // codec/adapter.ts
727
+ import { Value } from "@sinclair/typebox/value";
728
+ var CodecMessageAdapter = class {
729
+ constructor(codec) {
730
+ this.codec = codec;
731
+ }
732
+ toBuffer(msg) {
733
+ try {
734
+ return {
735
+ ok: true,
736
+ value: this.codec.toBuffer(msg)
737
+ };
738
+ } catch (e) {
739
+ return {
740
+ ok: false,
741
+ reason: coerceErrorString(e)
742
+ };
743
+ }
744
+ }
745
+ fromBuffer(buf) {
746
+ try {
747
+ const parsedMsg = this.codec.fromBuffer(buf);
748
+ if (!Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
749
+ return {
750
+ ok: false,
751
+ reason: "transport message schema mismatch"
752
+ };
753
+ }
754
+ return {
755
+ ok: true,
756
+ value: parsedMsg
757
+ };
758
+ } catch (e) {
759
+ return {
760
+ ok: false,
761
+ reason: coerceErrorString(e)
762
+ };
763
+ }
764
+ }
765
+ };
766
+
767
+ // transport/sessionStateMachine/transitions.ts
768
+ function inheritSharedSession(session) {
769
+ return {
770
+ id: session.id,
771
+ from: session.from,
772
+ to: session.to,
773
+ seq: session.seq,
774
+ ack: session.ack,
775
+ seqSent: session.seqSent,
776
+ sendBuffer: session.sendBuffer,
777
+ telemetry: session.telemetry,
778
+ options: session.options,
779
+ log: session.log,
780
+ tracer: session.tracer,
781
+ protocolVersion: session.protocolVersion,
782
+ codec: session.codec
783
+ };
784
+ }
785
+ function inheritSharedSessionWithGrace(session) {
786
+ return {
787
+ ...inheritSharedSession(session),
788
+ graceExpiryTime: session.graceExpiryTime
789
+ };
790
+ }
791
+ var SessionStateGraph = {
792
+ entrypoints: {
793
+ NoConnection: (to, from, listeners, options, protocolVersion, tracer, log) => {
794
+ const id = `session-${generateId()}`;
795
+ const telemetry = createSessionTelemetryInfo(tracer, id, to, from);
796
+ const sendBuffer = [];
797
+ const session = new SessionNoConnection({
798
+ listeners,
799
+ id,
800
+ from,
801
+ to,
802
+ seq: 0,
803
+ ack: 0,
804
+ seqSent: 0,
805
+ graceExpiryTime: Date.now() + options.sessionDisconnectGraceMs,
806
+ sendBuffer,
807
+ telemetry,
808
+ options,
809
+ protocolVersion,
810
+ tracer,
811
+ log,
812
+ codec: new CodecMessageAdapter(options.codec)
813
+ });
814
+ session.log?.info(`session ${session.id} created in NoConnection state`, {
815
+ ...session.loggingMetadata,
816
+ tags: ["state-transition"]
817
+ });
818
+ return session;
819
+ },
820
+ WaitingForHandshake: (from, conn, listeners, options, tracer, log) => {
821
+ const session = new SessionWaitingForHandshake({
822
+ conn,
823
+ listeners,
824
+ from,
825
+ options,
826
+ tracer,
827
+ log,
828
+ codec: new CodecMessageAdapter(options.codec)
829
+ });
830
+ session.log?.info(`session created in WaitingForHandshake state`, {
831
+ ...session.loggingMetadata,
832
+ tags: ["state-transition"]
833
+ });
834
+ return session;
835
+ }
836
+ },
837
+ // All of the transitions 'move'/'consume' the old session and return a new one.
838
+ // After a session is transitioned, any usage of the old session will throw.
839
+ transition: {
840
+ // happy path transitions
841
+ NoConnectionToBackingOff: (oldSession, backoffMs, listeners) => {
842
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
843
+ oldSession._handleStateExit();
844
+ const session = new SessionBackingOff({
845
+ backoffMs,
846
+ listeners,
847
+ ...carriedState
848
+ });
849
+ session.log?.info(
850
+ `session ${session.id} transition from NoConnection to BackingOff`,
851
+ {
852
+ ...session.loggingMetadata,
853
+ tags: ["state-transition"]
854
+ }
855
+ );
856
+ return session;
857
+ },
858
+ BackingOffToConnecting: (oldSession, connPromise, listeners) => {
859
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
860
+ oldSession._handleStateExit();
861
+ const session = new SessionConnecting({
862
+ connPromise,
863
+ listeners,
864
+ ...carriedState
865
+ });
866
+ session.log?.info(
867
+ `session ${session.id} transition from BackingOff to Connecting`,
868
+ {
869
+ ...session.loggingMetadata,
870
+ tags: ["state-transition"]
871
+ }
872
+ );
873
+ return session;
874
+ },
875
+ ConnectingToHandshaking: (oldSession, conn, listeners) => {
876
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
877
+ oldSession._handleStateExit();
878
+ const session = new SessionHandshaking({
879
+ conn,
880
+ listeners,
881
+ ...carriedState
882
+ });
883
+ conn.telemetry = createConnectionTelemetryInfo(
884
+ session.tracer,
885
+ conn,
886
+ session.telemetry
887
+ );
888
+ session.log?.info(
889
+ `session ${session.id} transition from Connecting to Handshaking`,
890
+ {
891
+ ...session.loggingMetadata,
892
+ tags: ["state-transition"]
893
+ }
894
+ );
895
+ return session;
896
+ },
897
+ HandshakingToConnected: (oldSession, listeners) => {
898
+ const carriedState = inheritSharedSession(oldSession);
899
+ const conn = oldSession.conn;
900
+ oldSession._handleStateExit();
901
+ const session = new SessionConnected({
902
+ conn,
903
+ listeners,
904
+ ...carriedState
905
+ });
906
+ session.startMissingHeartbeatTimeout();
907
+ session.log?.info(
908
+ `session ${session.id} transition from Handshaking to Connected`,
909
+ {
910
+ ...session.loggingMetadata,
911
+ tags: ["state-transition"]
912
+ }
913
+ );
914
+ return session;
915
+ },
916
+ WaitingForHandshakeToConnected: (pendingSession, oldSession, sessionId, to, propagationCtx, listeners, protocolVersion) => {
917
+ const conn = pendingSession.conn;
918
+ const { from, options } = pendingSession;
919
+ const carriedState = oldSession ? (
920
+ // old session exists, inherit state
921
+ inheritSharedSession(oldSession)
922
+ ) : (
923
+ // old session does not exist, create new state
924
+ {
925
+ id: sessionId,
926
+ from,
927
+ to,
928
+ seq: 0,
929
+ ack: 0,
930
+ seqSent: 0,
931
+ sendBuffer: [],
932
+ telemetry: createSessionTelemetryInfo(
933
+ pendingSession.tracer,
934
+ sessionId,
935
+ to,
936
+ from,
937
+ propagationCtx
938
+ ),
939
+ options,
940
+ tracer: pendingSession.tracer,
941
+ log: pendingSession.log,
942
+ protocolVersion,
943
+ codec: new CodecMessageAdapter(options.codec)
944
+ }
945
+ );
946
+ pendingSession._handleStateExit();
947
+ oldSession?._handleStateExit();
948
+ const session = new SessionConnected({
949
+ conn,
950
+ listeners,
951
+ ...carriedState
952
+ });
953
+ session.startMissingHeartbeatTimeout();
954
+ conn.telemetry = createConnectionTelemetryInfo(
955
+ session.tracer,
956
+ conn,
957
+ session.telemetry
958
+ );
959
+ session.log?.info(
960
+ `session ${session.id} transition from WaitingForHandshake to Connected`,
961
+ {
962
+ ...session.loggingMetadata,
963
+ tags: ["state-transition"]
964
+ }
965
+ );
966
+ return session;
967
+ },
968
+ // disconnect paths
969
+ BackingOffToNoConnection: (oldSession, listeners) => {
970
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
971
+ oldSession._handleStateExit();
972
+ const session = new SessionNoConnection({
973
+ listeners,
974
+ ...carriedState
975
+ });
976
+ session.log?.info(
977
+ `session ${session.id} transition from BackingOff to NoConnection`,
978
+ {
979
+ ...session.loggingMetadata,
980
+ tags: ["state-transition"]
981
+ }
982
+ );
983
+ return session;
984
+ },
985
+ ConnectingToNoConnection: (oldSession, listeners) => {
986
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
987
+ oldSession.bestEffortClose();
988
+ oldSession._handleStateExit();
989
+ const session = new SessionNoConnection({
990
+ listeners,
991
+ ...carriedState
992
+ });
993
+ session.log?.info(
994
+ `session ${session.id} transition from Connecting to NoConnection`,
995
+ {
996
+ ...session.loggingMetadata,
997
+ tags: ["state-transition"]
998
+ }
999
+ );
1000
+ return session;
1001
+ },
1002
+ HandshakingToNoConnection: (oldSession, listeners) => {
1003
+ const carriedState = inheritSharedSessionWithGrace(oldSession);
1004
+ oldSession.conn.close();
1005
+ oldSession._handleStateExit();
1006
+ const session = new SessionNoConnection({
1007
+ listeners,
1008
+ ...carriedState
1009
+ });
1010
+ session.log?.info(
1011
+ `session ${session.id} transition from Handshaking to NoConnection`,
1012
+ {
1013
+ ...session.loggingMetadata,
1014
+ tags: ["state-transition"]
1015
+ }
1016
+ );
1017
+ return session;
1018
+ },
1019
+ ConnectedToNoConnection: (oldSession, listeners) => {
1020
+ const carriedState = inheritSharedSession(oldSession);
1021
+ const graceExpiryTime = Date.now() + oldSession.options.sessionDisconnectGraceMs;
1022
+ oldSession.conn.close();
1023
+ oldSession._handleStateExit();
1024
+ const session = new SessionNoConnection({
1025
+ listeners,
1026
+ graceExpiryTime,
1027
+ ...carriedState
1028
+ });
1029
+ session.log?.info(
1030
+ `session ${session.id} transition from Connected to NoConnection`,
1031
+ {
1032
+ ...session.loggingMetadata,
1033
+ tags: ["state-transition"]
1034
+ }
1035
+ );
1036
+ return session;
1037
+ }
1038
+ }
1039
+ };
1040
+ var transitions = SessionStateGraph.transition;
1041
+ var ClientSessionStateGraph = {
1042
+ entrypoint: SessionStateGraph.entrypoints.NoConnection,
1043
+ transition: {
1044
+ // happy paths
1045
+ // NoConnection -> BackingOff: attempt to connect
1046
+ NoConnectionToBackingOff: transitions.NoConnectionToBackingOff,
1047
+ // BackingOff -> Connecting: backoff period elapsed, start connection
1048
+ BackingOffToConnecting: transitions.BackingOffToConnecting,
1049
+ // Connecting -> Handshaking: connection established, start handshake
1050
+ ConnectingToHandshaking: transitions.ConnectingToHandshaking,
1051
+ // Handshaking -> Connected: handshake complete, session ready
1052
+ HandshakingToConnected: transitions.HandshakingToConnected,
1053
+ // disconnect paths
1054
+ // BackingOff -> NoConnection: unused
1055
+ BackingOffToNoConnection: transitions.BackingOffToNoConnection,
1056
+ // Connecting -> NoConnection: connection failed or connection timeout
1057
+ ConnectingToNoConnection: transitions.ConnectingToNoConnection,
1058
+ // Handshaking -> NoConnection: connection closed or handshake timeout
1059
+ HandshakingToNoConnection: transitions.HandshakingToNoConnection,
1060
+ // Connected -> NoConnection: connection closed
1061
+ ConnectedToNoConnection: transitions.ConnectedToNoConnection
1062
+ // destroy/close paths
1063
+ // NoConnection -> x: grace period elapsed
1064
+ // BackingOff -> x: grace period elapsed
1065
+ // Connecting -> x: grace period elapsed
1066
+ // Handshaking -> x: grace period elapsed or invalid handshake message or handshake rejection
1067
+ // Connected -> x: grace period elapsed or invalid message
1068
+ }
1069
+ };
1070
+ var ServerSessionStateGraph = {
1071
+ entrypoint: SessionStateGraph.entrypoints.WaitingForHandshake,
1072
+ transition: {
1073
+ // happy paths
1074
+ // WaitingForHandshake -> Connected: handshake complete, session ready
1075
+ WaitingForHandshakeToConnected: transitions.WaitingForHandshakeToConnected,
1076
+ // disconnect paths
1077
+ // Connected -> NoConnection: connection closed
1078
+ ConnectedToNoConnection: transitions.ConnectedToNoConnection
1079
+ // destroy/close paths
1080
+ // WaitingForHandshake -> x: handshake timeout elapsed or invalid handshake message or handshake rejection or connection closed
1081
+ }
1082
+ };
1083
+
1084
+ // transport/transport.ts
1085
+ var Transport = class {
1086
+ /**
1087
+ * The status of the transport.
1088
+ */
1089
+ status;
1090
+ /**
1091
+ * The client ID of this transport.
1092
+ */
1093
+ clientId;
1094
+ /**
1095
+ * The event dispatcher for handling events of type EventTypes.
1096
+ */
1097
+ eventDispatcher;
1098
+ /**
1099
+ * The options for this transport.
1100
+ */
1101
+ options;
1102
+ log;
1103
+ tracer;
1104
+ sessions;
1105
+ /**
1106
+ * Creates a new Transport instance.
1107
+ * @param codec The codec used to encode and decode messages.
1108
+ * @param clientId The client ID of this transport.
1109
+ */
1110
+ constructor(clientId, providedOptions) {
1111
+ this.options = { ...defaultTransportOptions, ...providedOptions };
1112
+ this.eventDispatcher = new EventDispatcher();
1113
+ this.clientId = clientId;
1114
+ this.status = "open";
1115
+ this.sessions = /* @__PURE__ */ new Map();
1116
+ this.tracer = getTracer();
1117
+ }
1118
+ bindLogger(fn, level) {
1119
+ if (typeof fn === "function") {
1120
+ this.log = createLogProxy(new BaseLogger(fn, level));
1121
+ return;
1122
+ }
1123
+ this.log = createLogProxy(fn);
1124
+ }
1125
+ /**
1126
+ * Called when a message is received by this transport.
1127
+ * You generally shouldn't need to override this in downstream transport implementations.
1128
+ * @param message The received message.
1129
+ */
1130
+ handleMsg(message) {
1131
+ if (this.getStatus() !== "open") return;
1132
+ this.eventDispatcher.dispatchEvent("message", message);
1133
+ }
1134
+ /**
1135
+ * Adds a listener to this transport.
1136
+ * @param the type of event to listen for
1137
+ * @param handler The message handler to add.
1138
+ */
1139
+ addEventListener(type, handler) {
1140
+ this.eventDispatcher.addEventListener(type, handler);
1141
+ }
1142
+ /**
1143
+ * Removes a listener from this transport.
1144
+ * @param the type of event to un-listen on
1145
+ * @param handler The message handler to remove.
1146
+ */
1147
+ removeEventListener(type, handler) {
1148
+ this.eventDispatcher.removeEventListener(type, handler);
1149
+ }
1150
+ protocolError(message) {
1151
+ this.eventDispatcher.dispatchEvent("protocolError", message);
1152
+ }
1153
+ /**
1154
+ * Default close implementation for transports. You should override this in the downstream
1155
+ * implementation if you need to do any additional cleanup and call super.close() at the end.
1156
+ * Closes the transport. Any messages sent while the transport is closed will be silently discarded.
1157
+ */
1158
+ close() {
1159
+ this.status = "closed";
1160
+ const sessions = Array.from(this.sessions.values());
1161
+ for (const session of sessions) {
1162
+ this.deleteSession(session);
1163
+ }
1164
+ this.eventDispatcher.dispatchEvent("transportStatus", {
1165
+ status: this.status
1166
+ });
1167
+ this.eventDispatcher.removeAllListeners();
1168
+ this.log?.info(`manually closed transport`, { clientId: this.clientId });
1169
+ }
1170
+ getStatus() {
1171
+ return this.status;
1172
+ }
1173
+ // state transitions
1174
+ createSession(session) {
1175
+ const activeSession = this.sessions.get(session.to);
1176
+ if (activeSession) {
1177
+ const msg = `attempt to create session for ${session.to} but active session (${activeSession.id}) already exists`;
1178
+ this.log?.error(msg, {
1179
+ ...session.loggingMetadata,
1180
+ tags: ["invariant-violation"]
1181
+ });
1182
+ throw new Error(msg);
1183
+ }
1184
+ this.sessions.set(session.to, session);
1185
+ this.eventDispatcher.dispatchEvent("sessionStatus", {
1186
+ status: "created",
1187
+ session
1188
+ });
1189
+ this.eventDispatcher.dispatchEvent("sessionTransition", {
1190
+ state: session.state,
1191
+ id: session.id
1192
+ });
1193
+ }
1194
+ updateSession(session) {
1195
+ const activeSession = this.sessions.get(session.to);
1196
+ if (!activeSession) {
1197
+ const msg = `attempt to transition session for ${session.to} but no active session exists`;
1198
+ this.log?.error(msg, {
1199
+ ...session.loggingMetadata,
1200
+ tags: ["invariant-violation"]
1201
+ });
1202
+ throw new Error(msg);
1203
+ }
1204
+ if (activeSession.id !== session.id) {
1205
+ const msg = `attempt to transition active session for ${session.to} but active session (${activeSession.id}) is different from handle (${session.id})`;
1206
+ this.log?.error(msg, {
1207
+ ...session.loggingMetadata,
1208
+ tags: ["invariant-violation"]
1209
+ });
1210
+ throw new Error(msg);
1211
+ }
1212
+ this.sessions.set(session.to, session);
1213
+ this.eventDispatcher.dispatchEvent("sessionTransition", {
1214
+ state: session.state,
1215
+ id: session.id
1216
+ });
1217
+ }
1218
+ deleteSession(session, options) {
1219
+ if (session._isConsumed) return;
1220
+ const loggingMetadata = session.loggingMetadata;
1221
+ if (loggingMetadata.tags && options?.unhealthy) {
1222
+ loggingMetadata.tags.push("unhealthy-session");
1223
+ }
1224
+ session.log?.info(`closing session ${session.id}`, loggingMetadata);
1225
+ this.eventDispatcher.dispatchEvent("sessionStatus", {
1226
+ status: "closing",
1227
+ session
1228
+ });
1229
+ const to = session.to;
1230
+ session.close();
1231
+ this.sessions.delete(to);
1232
+ this.eventDispatcher.dispatchEvent("sessionStatus", {
1233
+ status: "closed",
1234
+ session: { id: session.id, to }
1235
+ });
1236
+ }
1237
+ // common listeners
1238
+ onSessionGracePeriodElapsed(session) {
1239
+ this.log?.info(
1240
+ `session to ${session.to} grace period elapsed, closing`,
1241
+ session.loggingMetadata
1242
+ );
1243
+ this.deleteSession(session);
1244
+ }
1245
+ onConnectingFailed(session) {
1246
+ const noConnectionSession = SessionStateGraph.transition.ConnectingToNoConnection(session, {
1247
+ onSessionGracePeriodElapsed: () => {
1248
+ this.onSessionGracePeriodElapsed(noConnectionSession);
1249
+ }
1250
+ });
1251
+ this.updateSession(noConnectionSession);
1252
+ return noConnectionSession;
1253
+ }
1254
+ onConnClosed(session) {
1255
+ let noConnectionSession;
1256
+ if (session.state === "Handshaking" /* Handshaking */) {
1257
+ noConnectionSession = SessionStateGraph.transition.HandshakingToNoConnection(session, {
1258
+ onSessionGracePeriodElapsed: () => {
1259
+ this.onSessionGracePeriodElapsed(noConnectionSession);
1260
+ }
1261
+ });
1262
+ } else {
1263
+ noConnectionSession = SessionStateGraph.transition.ConnectedToNoConnection(session, {
1264
+ onSessionGracePeriodElapsed: () => {
1265
+ this.onSessionGracePeriodElapsed(noConnectionSession);
1266
+ }
1267
+ });
1268
+ }
1269
+ this.updateSession(noConnectionSession);
1270
+ return noConnectionSession;
1271
+ }
1272
+ /**
1273
+ * Gets a send closure scoped to a specific session. Sending using the returned
1274
+ * closure after the session has transitioned to a different state will be a noop.
1275
+ *
1276
+ * Session objects themselves can become stale as they transition between
1277
+ * states. As stale sessions cannot be used again (and will throw), holding
1278
+ * onto a session object is not recommended.
1279
+ */
1280
+ getSessionBoundSendFn(to, sessionId) {
1281
+ if (this.getStatus() !== "open") {
1282
+ throw new Error("cannot get a bound send function on a closed transport");
1283
+ }
1284
+ return (msg) => {
1285
+ const session = this.sessions.get(to);
1286
+ if (!session) {
1287
+ throw new Error(
1288
+ `session scope for ${sessionId} has ended (close), can't send`
1289
+ );
1290
+ }
1291
+ const sameSession = session.id === sessionId;
1292
+ if (!sameSession || session._isConsumed) {
1293
+ throw new Error(
1294
+ `session scope for ${sessionId} has ended (transition), can't send`
1295
+ );
1296
+ }
1297
+ const res = session.send(msg);
1298
+ if (!res.ok) {
1299
+ throw new Error(res.reason);
1300
+ }
1301
+ return res.value;
1302
+ };
1303
+ }
1304
+ };
1305
+
1306
+ // transport/client.ts
1307
+ import { SpanStatusCode as SpanStatusCode2 } from "@opentelemetry/api";
1308
+
1309
+ // transport/rateLimit.ts
1310
+ var LeakyBucketRateLimit = class {
1311
+ budgetConsumed;
1312
+ intervalHandle;
1313
+ options;
1314
+ constructor(options) {
1315
+ this.options = options;
1316
+ this.budgetConsumed = 0;
1317
+ }
1318
+ getBackoffMs() {
1319
+ if (this.getBudgetConsumed() === 0) {
1320
+ return 0;
1321
+ }
1322
+ const exponent = Math.max(0, this.getBudgetConsumed() - 1);
1323
+ const jitter = Math.floor(Math.random() * this.options.maxJitterMs);
1324
+ const backoffMs = Math.min(
1325
+ this.options.baseIntervalMs * 2 ** exponent,
1326
+ this.options.maxBackoffMs
1327
+ );
1328
+ return backoffMs + jitter;
1329
+ }
1330
+ get totalBudgetRestoreTime() {
1331
+ return this.options.budgetRestoreIntervalMs * this.options.attemptBudgetCapacity;
1332
+ }
1333
+ consumeBudget() {
1334
+ this.stopLeak();
1335
+ this.budgetConsumed = this.getBudgetConsumed() + 1;
1336
+ }
1337
+ getBudgetConsumed() {
1338
+ return this.budgetConsumed;
1339
+ }
1340
+ hasBudget() {
1341
+ return this.getBudgetConsumed() < this.options.attemptBudgetCapacity;
1342
+ }
1343
+ startRestoringBudget() {
1344
+ if (this.intervalHandle) {
1345
+ return;
1346
+ }
1347
+ const restoreBudgetForUser = () => {
1348
+ const currentBudget = this.budgetConsumed;
1349
+ if (!currentBudget) {
1350
+ this.stopLeak();
1351
+ return;
1352
+ }
1353
+ const newBudget = currentBudget - 1;
1354
+ if (newBudget === 0) {
1355
+ return;
1356
+ }
1357
+ this.budgetConsumed = newBudget;
1358
+ };
1359
+ this.intervalHandle = setInterval(
1360
+ restoreBudgetForUser,
1361
+ this.options.budgetRestoreIntervalMs
1362
+ );
1363
+ }
1364
+ stopLeak() {
1365
+ if (!this.intervalHandle) {
1366
+ return;
1367
+ }
1368
+ clearInterval(this.intervalHandle);
1369
+ this.intervalHandle = void 0;
1370
+ }
1371
+ close() {
1372
+ this.stopLeak();
1373
+ }
1374
+ };
1375
+
1376
+ // transport/client.ts
1377
+ import { Value as Value2 } from "@sinclair/typebox/value";
1378
+ var ClientTransport = class extends Transport {
1379
+ /**
1380
+ * The options for this transport.
1381
+ */
1382
+ options;
1383
+ retryBudget;
1384
+ /**
1385
+ * A flag indicating whether the transport should automatically reconnect
1386
+ * when a connection is dropped.
1387
+ * Realistically, this should always be true for clients unless you are writing
1388
+ * tests or a special case where you don't want to reconnect.
1389
+ */
1390
+ reconnectOnConnectionDrop = true;
1391
+ /**
1392
+ * Optional handshake options for this client.
1393
+ */
1394
+ handshakeExtensions;
1395
+ sessions;
1396
+ constructor(clientId, providedOptions) {
1397
+ super(clientId, providedOptions);
1398
+ this.sessions = /* @__PURE__ */ new Map();
1399
+ this.options = {
1400
+ ...defaultClientTransportOptions,
1401
+ ...providedOptions
1402
+ };
1403
+ this.retryBudget = new LeakyBucketRateLimit(this.options);
1404
+ }
1405
+ extendHandshake(options) {
1406
+ this.handshakeExtensions = options;
1407
+ }
1408
+ tryReconnecting(to) {
1409
+ const oldSession = this.sessions.get(to);
1410
+ if (!this.options.enableTransparentSessionReconnects && oldSession) {
1411
+ this.deleteSession(oldSession);
1412
+ }
1413
+ if (this.reconnectOnConnectionDrop && this.getStatus() === "open") {
1414
+ this.connect(to);
1415
+ }
1416
+ }
1417
+ /*
1418
+ * Creates a raw unconnected session object.
1419
+ * This is mostly a River internal, you shouldn't need to use this directly.
1420
+ */
1421
+ createUnconnectedSession(to) {
1422
+ const session = ClientSessionStateGraph.entrypoint(
1423
+ to,
1424
+ this.clientId,
1425
+ {
1426
+ onSessionGracePeriodElapsed: () => {
1427
+ this.onSessionGracePeriodElapsed(session);
1428
+ }
1429
+ },
1430
+ this.options,
1431
+ currentProtocolVersion,
1432
+ this.tracer,
1433
+ this.log
1434
+ );
1435
+ this.createSession(session);
1436
+ return session;
1437
+ }
1438
+ // listeners
1439
+ onConnectingFailed(session) {
1440
+ const noConnectionSession = super.onConnectingFailed(session);
1441
+ this.tryReconnecting(noConnectionSession.to);
1442
+ return noConnectionSession;
1443
+ }
1444
+ onConnClosed(session) {
1445
+ const noConnectionSession = super.onConnClosed(session);
1446
+ this.tryReconnecting(noConnectionSession.to);
1447
+ return noConnectionSession;
1448
+ }
1449
+ onConnectionEstablished(session, conn) {
1450
+ const handshakingSession = ClientSessionStateGraph.transition.ConnectingToHandshaking(
1451
+ session,
1452
+ conn,
1453
+ {
1454
+ onConnectionErrored: (err) => {
1455
+ const errStr = coerceErrorString(err);
1456
+ this.log?.error(
1457
+ `connection to ${handshakingSession.to} errored during handshake: ${errStr}`,
1458
+ handshakingSession.loggingMetadata
1459
+ );
1460
+ },
1461
+ onConnectionClosed: () => {
1462
+ this.log?.warn(
1463
+ `connection to ${handshakingSession.to} closed during handshake`,
1464
+ handshakingSession.loggingMetadata
1465
+ );
1466
+ this.onConnClosed(handshakingSession);
1467
+ },
1468
+ onHandshake: (msg) => {
1469
+ this.onHandshakeResponse(handshakingSession, msg);
1470
+ },
1471
+ onInvalidHandshake: (reason, code) => {
1472
+ this.log?.error(
1473
+ `invalid handshake: ${reason}`,
1474
+ handshakingSession.loggingMetadata
1475
+ );
1476
+ this.deleteSession(session, { unhealthy: true });
1477
+ this.protocolError({
1478
+ type: ProtocolError.HandshakeFailed,
1479
+ code,
1480
+ message: reason
1481
+ });
1482
+ },
1483
+ onHandshakeTimeout: () => {
1484
+ this.log?.error(
1485
+ `connection to ${handshakingSession.to} timed out during handshake`,
1486
+ handshakingSession.loggingMetadata
1487
+ );
1488
+ this.onConnClosed(handshakingSession);
1489
+ },
1490
+ onSessionGracePeriodElapsed: () => {
1491
+ this.onSessionGracePeriodElapsed(handshakingSession);
1492
+ }
1493
+ }
1494
+ );
1495
+ this.updateSession(handshakingSession);
1496
+ void this.sendHandshake(handshakingSession);
1497
+ return handshakingSession;
1498
+ }
1499
+ rejectHandshakeResponse(session, reason, metadata) {
1500
+ session.conn.telemetry?.span.setStatus({
1501
+ code: SpanStatusCode2.ERROR,
1502
+ message: reason
1503
+ });
1504
+ this.log?.warn(reason, metadata);
1505
+ this.deleteSession(session, { unhealthy: true });
1506
+ }
1507
+ onHandshakeResponse(session, msg) {
1508
+ if (!Value2.Check(ControlMessageHandshakeResponseSchema, msg.payload)) {
1509
+ const reason = `received invalid handshake response`;
1510
+ this.rejectHandshakeResponse(session, reason, {
1511
+ ...session.loggingMetadata,
1512
+ transportMessage: msg,
1513
+ validationErrors: [
1514
+ ...Value2.Errors(ControlMessageHandshakeResponseSchema, msg.payload)
1515
+ ]
1516
+ });
1517
+ return;
1518
+ }
1519
+ if (!msg.payload.status.ok) {
1520
+ const retriable = Value2.Check(
1521
+ HandshakeErrorRetriableResponseCodes,
1522
+ msg.payload.status.code
1523
+ );
1524
+ const reason = `handshake failed: ${msg.payload.status.reason}`;
1525
+ const to = session.to;
1526
+ this.rejectHandshakeResponse(session, reason, {
1527
+ ...session.loggingMetadata,
1528
+ transportMessage: msg
1529
+ });
1530
+ if (retriable) {
1531
+ this.tryReconnecting(to);
1532
+ } else {
1533
+ this.protocolError({
1534
+ type: ProtocolError.HandshakeFailed,
1535
+ code: msg.payload.status.code,
1536
+ message: reason
1537
+ });
1538
+ }
1539
+ return;
1540
+ }
1541
+ if (msg.payload.status.sessionId !== session.id) {
1542
+ const reason = `session id mismatch: expected ${session.id}, got ${msg.payload.status.sessionId}`;
1543
+ this.rejectHandshakeResponse(session, reason, {
1544
+ ...session.loggingMetadata,
1545
+ transportMessage: msg
1546
+ });
1547
+ return;
1548
+ }
1549
+ this.log?.info(`handshake from ${msg.from} ok`, {
1550
+ ...session.loggingMetadata,
1551
+ transportMessage: msg
1552
+ });
1553
+ const connectedSession = ClientSessionStateGraph.transition.HandshakingToConnected(session, {
1554
+ onConnectionErrored: (err) => {
1555
+ const errStr = coerceErrorString(err);
1556
+ this.log?.warn(
1557
+ `connection to ${connectedSession.to} errored: ${errStr}`,
1558
+ connectedSession.loggingMetadata
1559
+ );
1560
+ },
1561
+ onConnectionClosed: () => {
1562
+ this.log?.info(
1563
+ `connection to ${connectedSession.to} closed`,
1564
+ connectedSession.loggingMetadata
1565
+ );
1566
+ this.onConnClosed(connectedSession);
1567
+ },
1568
+ onMessage: (msg2) => {
1569
+ this.handleMsg(msg2);
1570
+ },
1571
+ onInvalidMessage: (reason) => {
1572
+ this.log?.error(`invalid message: ${reason}`, {
1573
+ ...connectedSession.loggingMetadata,
1574
+ transportMessage: msg
1575
+ });
1576
+ this.protocolError({
1577
+ type: ProtocolError.InvalidMessage,
1578
+ message: reason
1579
+ });
1580
+ this.deleteSession(connectedSession, { unhealthy: true });
1581
+ },
1582
+ onMessageSendFailure: (msg2, reason) => {
1583
+ this.log?.error(`failed to send message: ${reason}`, {
1584
+ ...connectedSession.loggingMetadata,
1585
+ transportMessage: msg2
1586
+ });
1587
+ this.protocolError({
1588
+ type: ProtocolError.MessageSendFailure,
1589
+ message: reason
1590
+ });
1591
+ this.deleteSession(connectedSession, { unhealthy: true });
1592
+ }
1593
+ });
1594
+ const res = connectedSession.sendBufferedMessages();
1595
+ if (!res.ok) {
1596
+ return;
1597
+ }
1598
+ this.updateSession(connectedSession);
1599
+ this.retryBudget.startRestoringBudget();
1600
+ }
1601
+ /**
1602
+ * Manually attempts to connect to a client.
1603
+ * @param to The client ID of the node to connect to.
1604
+ */
1605
+ connect(to) {
1606
+ if (this.getStatus() !== "open") {
1607
+ this.log?.info(
1608
+ `transport state is no longer open, cancelling attempt to connect to ${to}`
1609
+ );
1610
+ return;
1611
+ }
1612
+ const session = this.sessions.get(to) ?? this.createUnconnectedSession(to);
1613
+ if (session.state !== "NoConnection" /* NoConnection */) {
1614
+ this.log?.debug(
1615
+ `session to ${to} has state ${session.state}, skipping connect attempt`,
1616
+ session.loggingMetadata
1617
+ );
1618
+ return;
1619
+ }
1620
+ if (!this.retryBudget.hasBudget()) {
1621
+ const budgetConsumed = this.retryBudget.getBudgetConsumed();
1622
+ const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
1623
+ this.log?.error(errMsg, session.loggingMetadata);
1624
+ this.protocolError({
1625
+ type: ProtocolError.RetriesExceeded,
1626
+ message: errMsg
1627
+ });
1628
+ return;
1629
+ }
1630
+ const backoffMs = this.retryBudget.getBackoffMs();
1631
+ this.log?.info(
1632
+ `attempting connection to ${to} (${backoffMs}ms backoff)`,
1633
+ session.loggingMetadata
1634
+ );
1635
+ this.retryBudget.consumeBudget();
1636
+ const backingOffSession = ClientSessionStateGraph.transition.NoConnectionToBackingOff(
1637
+ session,
1638
+ backoffMs,
1639
+ {
1640
+ onBackoffFinished: () => {
1641
+ this.onBackoffFinished(backingOffSession);
1642
+ },
1643
+ onSessionGracePeriodElapsed: () => {
1644
+ this.onSessionGracePeriodElapsed(backingOffSession);
1645
+ }
1646
+ }
1647
+ );
1648
+ this.updateSession(backingOffSession);
1649
+ }
1650
+ /**
1651
+ * Manually kills all sessions to the server (including all pending state).
1652
+ * This is useful for when you want to close all connections to a server
1653
+ * and don't want to wait for the grace period to elapse.
1654
+ */
1655
+ hardDisconnect() {
1656
+ const sessions = Array.from(this.sessions.values());
1657
+ for (const session of sessions) {
1658
+ this.deleteSession(session);
1659
+ }
1660
+ }
1661
+ onBackoffFinished(session) {
1662
+ const connPromise = session.tracer.startActiveSpan(
1663
+ "connect",
1664
+ async (span) => {
1665
+ try {
1666
+ return await this.createNewOutgoingConnection(session.to);
1667
+ } catch (err) {
1668
+ const errStr = coerceErrorString(err);
1669
+ span.recordException(errStr);
1670
+ span.setStatus({ code: SpanStatusCode2.ERROR });
1671
+ throw err;
1672
+ } finally {
1673
+ span.end();
1674
+ }
1675
+ }
1676
+ );
1677
+ const connectingSession = ClientSessionStateGraph.transition.BackingOffToConnecting(
1678
+ session,
1679
+ connPromise,
1680
+ {
1681
+ onConnectionEstablished: (conn) => {
1682
+ this.log?.debug(
1683
+ `connection to ${connectingSession.to} established`,
1684
+ {
1685
+ ...conn.loggingMetadata,
1686
+ ...connectingSession.loggingMetadata
1687
+ }
1688
+ );
1689
+ this.onConnectionEstablished(connectingSession, conn);
1690
+ },
1691
+ onConnectionFailed: (error) => {
1692
+ const errStr = coerceErrorString(error);
1693
+ this.log?.error(
1694
+ `error connecting to ${connectingSession.to}: ${errStr}`,
1695
+ connectingSession.loggingMetadata
1696
+ );
1697
+ this.onConnectingFailed(connectingSession);
1698
+ },
1699
+ onConnectionTimeout: () => {
1700
+ this.log?.error(
1701
+ `connection to ${connectingSession.to} timed out`,
1702
+ connectingSession.loggingMetadata
1703
+ );
1704
+ this.onConnectingFailed(connectingSession);
1705
+ },
1706
+ onSessionGracePeriodElapsed: () => {
1707
+ this.onSessionGracePeriodElapsed(connectingSession);
1708
+ }
1709
+ }
1710
+ );
1711
+ this.updateSession(connectingSession);
1712
+ }
1713
+ async sendHandshake(session) {
1714
+ let metadata = void 0;
1715
+ if (this.handshakeExtensions) {
1716
+ metadata = await this.handshakeExtensions.construct();
1717
+ }
1718
+ if (session._isConsumed) {
1719
+ return;
1720
+ }
1721
+ const requestMsg = handshakeRequestMessage({
1722
+ from: this.clientId,
1723
+ to: session.to,
1724
+ sessionId: session.id,
1725
+ expectedSessionState: {
1726
+ nextExpectedSeq: session.ack,
1727
+ nextSentSeq: session.nextSeq()
1728
+ },
1729
+ metadata,
1730
+ tracing: getPropagationContext(session.telemetry.ctx)
1731
+ });
1732
+ this.log?.debug(`sending handshake request to ${session.to}`, {
1733
+ ...session.loggingMetadata,
1734
+ transportMessage: requestMsg
1735
+ });
1736
+ const res = session.sendHandshake(requestMsg);
1737
+ if (!res.ok) {
1738
+ this.log?.error(`failed to send handshake request: ${res.reason}`, {
1739
+ ...session.loggingMetadata,
1740
+ transportMessage: requestMsg
1741
+ });
1742
+ this.protocolError({
1743
+ type: ProtocolError.MessageSendFailure,
1744
+ message: res.reason
1745
+ });
1746
+ this.deleteSession(session, { unhealthy: true });
1747
+ }
1748
+ }
1749
+ close() {
1750
+ this.retryBudget.close();
1751
+ super.close();
1752
+ }
1753
+ };
1754
+
1755
+ // transport/server.ts
1756
+ import { SpanStatusCode as SpanStatusCode3 } from "@opentelemetry/api";
1757
+ import { Value as Value3 } from "@sinclair/typebox/value";
1758
+ var ServerTransport = class extends Transport {
1759
+ /**
1760
+ * The options for this transport.
1761
+ */
1762
+ options;
1763
+ /**
1764
+ * Optional handshake options for the server.
1765
+ */
1766
+ handshakeExtensions;
1767
+ /**
1768
+ * A map of session handshake data for each session.
1769
+ */
1770
+ sessionHandshakeMetadata = /* @__PURE__ */ new Map();
1771
+ sessions = /* @__PURE__ */ new Map();
1772
+ pendingSessions = /* @__PURE__ */ new Set();
1773
+ constructor(clientId, providedOptions) {
1774
+ super(clientId, providedOptions);
1775
+ this.sessions = /* @__PURE__ */ new Map();
1776
+ this.options = {
1777
+ ...defaultServerTransportOptions,
1778
+ ...providedOptions
1779
+ };
1780
+ this.log?.info(`initiated server transport`, {
1781
+ clientId: this.clientId,
1782
+ protocolVersion: currentProtocolVersion
1783
+ });
1784
+ }
1785
+ extendHandshake(options) {
1786
+ this.handshakeExtensions = options;
1787
+ }
1788
+ deletePendingSession(pendingSession) {
1789
+ pendingSession.close();
1790
+ this.pendingSessions.delete(pendingSession);
1791
+ }
1792
+ deleteSession(session, options) {
1793
+ this.sessionHandshakeMetadata.delete(session.to);
1794
+ super.deleteSession(session, options);
1795
+ }
1796
+ handleConnection(conn) {
1797
+ if (this.getStatus() !== "open") return;
1798
+ this.log?.info(`new incoming connection`, {
1799
+ ...conn.loggingMetadata,
1800
+ clientId: this.clientId
1801
+ });
1802
+ let receivedHandshake = false;
1803
+ const pendingSession = ServerSessionStateGraph.entrypoint(
1804
+ this.clientId,
1805
+ conn,
1806
+ {
1807
+ onConnectionClosed: () => {
1808
+ this.log?.warn(
1809
+ `connection from unknown closed before handshake finished`,
1810
+ pendingSession.loggingMetadata
1811
+ );
1812
+ this.deletePendingSession(pendingSession);
1813
+ },
1814
+ onConnectionErrored: (err) => {
1815
+ const errorString = coerceErrorString(err);
1816
+ this.log?.warn(
1817
+ `connection from unknown errored before handshake finished: ${errorString}`,
1818
+ pendingSession.loggingMetadata
1819
+ );
1820
+ this.deletePendingSession(pendingSession);
1821
+ },
1822
+ onHandshakeTimeout: () => {
1823
+ this.log?.warn(
1824
+ `connection from unknown timed out before handshake finished`,
1825
+ pendingSession.loggingMetadata
1826
+ );
1827
+ this.deletePendingSession(pendingSession);
1828
+ },
1829
+ onHandshake: (msg) => {
1830
+ if (receivedHandshake) {
1831
+ this.log?.error(
1832
+ `received multiple handshake messages from pending session`,
1833
+ {
1834
+ ...pendingSession.loggingMetadata,
1835
+ connectedTo: msg.from,
1836
+ transportMessage: msg
1837
+ }
1838
+ );
1839
+ this.deletePendingSession(pendingSession);
1840
+ return;
1841
+ }
1842
+ receivedHandshake = true;
1843
+ void this.onHandshakeRequest(pendingSession, msg);
1844
+ },
1845
+ onInvalidHandshake: (reason, code) => {
1846
+ this.log?.error(
1847
+ `invalid handshake: ${reason}`,
1848
+ pendingSession.loggingMetadata
1849
+ );
1850
+ this.deletePendingSession(pendingSession);
1851
+ this.protocolError({
1852
+ type: ProtocolError.HandshakeFailed,
1853
+ code,
1854
+ message: reason
1855
+ });
1856
+ }
1857
+ },
1858
+ this.options,
1859
+ this.tracer,
1860
+ this.log
1861
+ );
1862
+ this.pendingSessions.add(pendingSession);
1863
+ }
1864
+ rejectHandshakeRequest(session, to, reason, code, metadata) {
1865
+ session.conn.telemetry?.span.setStatus({
1866
+ code: SpanStatusCode3.ERROR,
1867
+ message: reason
1868
+ });
1869
+ this.log?.warn(reason, metadata);
1870
+ const responseMsg = handshakeResponseMessage({
1871
+ from: this.clientId,
1872
+ to,
1873
+ status: {
1874
+ ok: false,
1875
+ code,
1876
+ reason
1877
+ }
1878
+ });
1879
+ const res = session.sendHandshake(responseMsg);
1880
+ if (!res.ok) {
1881
+ this.log?.error(`failed to send handshake response: ${res.reason}`, {
1882
+ ...session.loggingMetadata,
1883
+ transportMessage: responseMsg
1884
+ });
1885
+ this.protocolError({
1886
+ type: ProtocolError.MessageSendFailure,
1887
+ message: res.reason
1888
+ });
1889
+ this.deletePendingSession(session);
1890
+ return;
1891
+ }
1892
+ this.protocolError({
1893
+ type: ProtocolError.HandshakeFailed,
1894
+ code,
1895
+ message: reason
1896
+ });
1897
+ this.deletePendingSession(session);
1898
+ }
1899
+ async onHandshakeRequest(session, msg) {
1900
+ if (!Value3.Check(ControlMessageHandshakeRequestSchema, msg.payload)) {
1901
+ this.rejectHandshakeRequest(
1902
+ session,
1903
+ msg.from,
1904
+ "received invalid handshake request",
1905
+ "MALFORMED_HANDSHAKE",
1906
+ {
1907
+ ...session.loggingMetadata,
1908
+ transportMessage: msg,
1909
+ connectedTo: msg.from,
1910
+ validationErrors: [
1911
+ ...Value3.Errors(ControlMessageHandshakeRequestSchema, msg.payload)
1912
+ ]
1913
+ }
1914
+ );
1915
+ return;
1916
+ }
1917
+ const gotVersion = msg.payload.protocolVersion;
1918
+ if (!isAcceptedProtocolVersion(gotVersion)) {
1919
+ this.rejectHandshakeRequest(
1920
+ session,
1921
+ msg.from,
1922
+ `expected protocol version oneof [${acceptedProtocolVersions.toString()}], got ${gotVersion}`,
1923
+ "PROTOCOL_VERSION_MISMATCH",
1924
+ {
1925
+ ...session.loggingMetadata,
1926
+ connectedTo: msg.from,
1927
+ transportMessage: msg
1928
+ }
1929
+ );
1930
+ return;
1931
+ }
1932
+ let parsedMetadata = {};
1933
+ if (this.handshakeExtensions) {
1934
+ if (!Value3.Check(this.handshakeExtensions.schema, msg.payload.metadata)) {
1935
+ this.rejectHandshakeRequest(
1936
+ session,
1937
+ msg.from,
1938
+ "received malformed handshake metadata",
1939
+ "MALFORMED_HANDSHAKE_META",
1940
+ {
1941
+ ...session.loggingMetadata,
1942
+ connectedTo: msg.from,
1943
+ validationErrors: [
1944
+ ...Value3.Errors(
1945
+ this.handshakeExtensions.schema,
1946
+ msg.payload.metadata
1947
+ )
1948
+ ]
1949
+ }
1950
+ );
1951
+ return;
1952
+ }
1953
+ const previousParsedMetadata = this.sessionHandshakeMetadata.get(
1954
+ msg.from
1955
+ );
1956
+ const parsedMetadataOrFailureCode = await this.handshakeExtensions.validate(
1957
+ msg.payload.metadata,
1958
+ previousParsedMetadata
1959
+ );
1960
+ if (session._isConsumed) {
1961
+ return;
1962
+ }
1963
+ if (Value3.Check(
1964
+ HandshakeErrorCustomHandlerFatalResponseCodes,
1965
+ parsedMetadataOrFailureCode
1966
+ )) {
1967
+ this.rejectHandshakeRequest(
1968
+ session,
1969
+ msg.from,
1970
+ "rejected by handshake handler",
1971
+ parsedMetadataOrFailureCode,
1972
+ {
1973
+ ...session.loggingMetadata,
1974
+ connectedTo: msg.from,
1975
+ clientId: this.clientId
1976
+ }
1977
+ );
1978
+ return;
1979
+ }
1980
+ parsedMetadata = parsedMetadataOrFailureCode;
1981
+ }
1982
+ let connectCase = "new session";
1983
+ const clientNextExpectedSeq = msg.payload.expectedSessionState.nextExpectedSeq;
1984
+ const clientNextSentSeq = msg.payload.expectedSessionState.nextSentSeq;
1985
+ let oldSession = this.sessions.get(msg.from);
1986
+ if (this.options.enableTransparentSessionReconnects && oldSession && oldSession.id === msg.payload.sessionId) {
1987
+ connectCase = "transparent reconnection";
1988
+ const ourNextSeq = oldSession.nextSeq();
1989
+ const ourAck = oldSession.ack;
1990
+ if (clientNextSentSeq > ourAck) {
1991
+ this.rejectHandshakeRequest(
1992
+ session,
1993
+ msg.from,
1994
+ `client is in the future: server wanted next message to be ${ourAck} but client would have sent ${clientNextSentSeq}`,
1995
+ "SESSION_STATE_MISMATCH",
1996
+ {
1997
+ ...session.loggingMetadata,
1998
+ connectedTo: msg.from,
1999
+ transportMessage: msg
2000
+ }
2001
+ );
2002
+ return;
2003
+ }
2004
+ if (ourNextSeq > clientNextExpectedSeq) {
2005
+ this.rejectHandshakeRequest(
2006
+ session,
2007
+ msg.from,
2008
+ `server is in the future: client wanted next message to be ${clientNextExpectedSeq} but server would have sent ${ourNextSeq}`,
2009
+ "SESSION_STATE_MISMATCH",
2010
+ {
2011
+ ...session.loggingMetadata,
2012
+ connectedTo: msg.from,
2013
+ transportMessage: msg
2014
+ }
2015
+ );
2016
+ return;
2017
+ }
2018
+ if (oldSession.state !== "NoConnection" /* NoConnection */) {
2019
+ const noConnectionSession = ServerSessionStateGraph.transition.ConnectedToNoConnection(
2020
+ oldSession,
2021
+ {
2022
+ onSessionGracePeriodElapsed: () => {
2023
+ this.onSessionGracePeriodElapsed(noConnectionSession);
2024
+ }
2025
+ }
2026
+ );
2027
+ oldSession = noConnectionSession;
2028
+ this.updateSession(oldSession);
2029
+ }
2030
+ } else if (oldSession) {
2031
+ connectCase = "hard reconnection";
2032
+ this.log?.info(
2033
+ `client is reconnecting to a new session (${msg.payload.sessionId}) with an old session (${oldSession.id}) already existing, closing old session`,
2034
+ {
2035
+ ...session.loggingMetadata,
2036
+ connectedTo: msg.from,
2037
+ sessionId: msg.payload.sessionId
2038
+ }
2039
+ );
2040
+ this.deleteSession(oldSession);
2041
+ oldSession = void 0;
2042
+ }
2043
+ if (!oldSession && (clientNextSentSeq > 0 || clientNextExpectedSeq > 0)) {
2044
+ connectCase = "unknown session";
2045
+ const rejectionMessage = this.options.enableTransparentSessionReconnects ? `client is trying to reconnect to a session the server don't know about: ${msg.payload.sessionId}` : `client is attempting a transparent reconnect to a session but the server does not support it: ${msg.payload.sessionId}`;
2046
+ this.rejectHandshakeRequest(
2047
+ session,
2048
+ msg.from,
2049
+ rejectionMessage,
2050
+ "SESSION_STATE_MISMATCH",
2051
+ {
2052
+ ...session.loggingMetadata,
2053
+ connectedTo: msg.from,
2054
+ transportMessage: msg
2055
+ }
2056
+ );
2057
+ return;
2058
+ }
2059
+ const sessionId = msg.payload.sessionId;
2060
+ this.log?.info(
2061
+ `handshake from ${msg.from} ok (${connectCase}), responding with handshake success`,
2062
+ {
2063
+ ...session.loggingMetadata,
2064
+ connectedTo: msg.from
2065
+ }
2066
+ );
2067
+ const responseMsg = handshakeResponseMessage({
2068
+ from: this.clientId,
2069
+ to: msg.from,
2070
+ status: {
2071
+ ok: true,
2072
+ sessionId
2073
+ }
2074
+ });
2075
+ const res = session.sendHandshake(responseMsg);
2076
+ if (!res.ok) {
2077
+ this.log?.error(`failed to send handshake response: ${res.reason}`, {
2078
+ ...session.loggingMetadata,
2079
+ transportMessage: responseMsg
2080
+ });
2081
+ this.protocolError({
2082
+ type: ProtocolError.MessageSendFailure,
2083
+ message: res.reason
2084
+ });
2085
+ this.deletePendingSession(session);
2086
+ return;
2087
+ }
2088
+ this.pendingSessions.delete(session);
2089
+ const connectedSession = ServerSessionStateGraph.transition.WaitingForHandshakeToConnected(
2090
+ session,
2091
+ // by this point oldSession is either no connection or we dont have an old session
2092
+ oldSession,
2093
+ sessionId,
2094
+ msg.from,
2095
+ msg.tracing,
2096
+ {
2097
+ onConnectionErrored: (err) => {
2098
+ const errStr = coerceErrorString(err);
2099
+ this.log?.warn(
2100
+ `connection to ${connectedSession.to} errored: ${errStr}`,
2101
+ connectedSession.loggingMetadata
2102
+ );
2103
+ },
2104
+ onConnectionClosed: () => {
2105
+ this.log?.info(
2106
+ `connection to ${connectedSession.to} closed`,
2107
+ connectedSession.loggingMetadata
2108
+ );
2109
+ this.onConnClosed(connectedSession);
2110
+ },
2111
+ onMessage: (msg2) => {
2112
+ this.handleMsg(msg2);
2113
+ },
2114
+ onInvalidMessage: (reason) => {
2115
+ this.log?.error(`invalid message: ${reason}`, {
2116
+ ...connectedSession.loggingMetadata,
2117
+ transportMessage: msg
2118
+ });
2119
+ this.protocolError({
2120
+ type: ProtocolError.InvalidMessage,
2121
+ message: reason
2122
+ });
2123
+ this.deleteSession(connectedSession, { unhealthy: true });
2124
+ },
2125
+ onMessageSendFailure: (msg2, reason) => {
2126
+ this.log?.error(`failed to send message: ${reason}`, {
2127
+ ...connectedSession.loggingMetadata,
2128
+ transportMessage: msg2
2129
+ });
2130
+ this.protocolError({
2131
+ type: ProtocolError.MessageSendFailure,
2132
+ message: reason
2133
+ });
2134
+ this.deleteSession(connectedSession, { unhealthy: true });
2135
+ }
2136
+ },
2137
+ gotVersion
2138
+ );
2139
+ const bufferSendRes = connectedSession.sendBufferedMessages();
2140
+ if (!bufferSendRes.ok) {
2141
+ return;
2142
+ }
2143
+ this.sessionHandshakeMetadata.set(connectedSession.to, parsedMetadata);
2144
+ if (oldSession) {
2145
+ this.updateSession(connectedSession);
2146
+ } else {
2147
+ this.createSession(connectedSession);
2148
+ }
2149
+ connectedSession.startActiveHeartbeat();
2150
+ }
2151
+ };
2152
+
2153
+ // transport/connection.ts
2154
+ var Connection = class {
2155
+ id;
2156
+ telemetry;
2157
+ constructor() {
2158
+ this.id = `conn-${generateId()}`;
2159
+ }
2160
+ get loggingMetadata() {
2161
+ const metadata = { connId: this.id };
2162
+ if (this.telemetry?.span.isRecording()) {
2163
+ const spanContext = this.telemetry.span.spanContext();
2164
+ metadata.telemetry = {
2165
+ traceId: spanContext.traceId,
2166
+ spanId: spanContext.spanId
2167
+ };
2168
+ }
2169
+ return metadata;
2170
+ }
2171
+ dataListener;
2172
+ closeListener;
2173
+ errorListener;
2174
+ onData(msg) {
2175
+ this.dataListener?.(msg);
2176
+ }
2177
+ onError(err) {
2178
+ this.errorListener?.(err);
2179
+ }
2180
+ onClose() {
2181
+ this.closeListener?.();
2182
+ this.telemetry?.span.end();
2183
+ }
2184
+ /**
2185
+ * Set the callback for when a message is received.
2186
+ * @param cb The message handler callback.
2187
+ */
2188
+ setDataListener(cb) {
2189
+ this.dataListener = cb;
2190
+ }
2191
+ removeDataListener() {
2192
+ this.dataListener = void 0;
2193
+ }
2194
+ /**
2195
+ * Set the callback for when the connection is closed.
2196
+ * This should also be called if an error happens and after notifying the error listener.
2197
+ * @param cb The callback to call when the connection is closed.
2198
+ */
2199
+ setCloseListener(cb) {
2200
+ this.closeListener = cb;
2201
+ }
2202
+ removeCloseListener() {
2203
+ this.closeListener = void 0;
2204
+ }
2205
+ /**
2206
+ * Set the callback for when an error is received.
2207
+ * This should only be used for logging errors, all cleanup
2208
+ * should be delegated to setCloseListener.
2209
+ *
2210
+ * The implementer should take care such that the implemented
2211
+ * connection will call both the close and error callbacks
2212
+ * on an error.
2213
+ *
2214
+ * @param cb The callback to call when an error is received.
2215
+ */
2216
+ setErrorListener(cb) {
2217
+ this.errorListener = cb;
2218
+ }
2219
+ removeErrorListener() {
2220
+ this.errorListener = void 0;
2221
+ }
2222
+ };
2223
+
2224
+ export {
2225
+ BinaryCodec,
2226
+ NaiveJsonCodec,
2227
+ ProtocolError,
2228
+ defaultTransportOptions,
2229
+ defaultClientTransportOptions,
2230
+ SessionState,
2231
+ SessionStateGraph,
2232
+ Transport,
2233
+ ClientTransport,
2234
+ ServerTransport,
2235
+ Connection,
2236
+ CodecMessageAdapter
2237
+ };
2238
+ //# sourceMappingURL=chunk-2JNVDUMN.js.map