@replit/river 0.23.18 → 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 (84) hide show
  1. package/README.md +17 -16
  2. package/dist/{chunk-AVL32IMG.js → chunk-AASMR3CQ.js} +20 -16
  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-EV5HW4IC.js → chunk-KYYB4DUR.js} +65 -53
  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-R2HAS3GM.js → chunk-PJGGC3LV.js} +55 -41
  13. package/dist/chunk-PJGGC3LV.js.map +1 -0
  14. package/dist/{chunk-7MJYOL32.js → chunk-RXJLI2OP.js} +15 -23
  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-5776a6bb.d.ts → client-ba0d3315.d.ts} +12 -15
  21. package/dist/{connection-bd35d442.d.ts → connection-c3a96d09.d.ts} +1 -5
  22. package/dist/connection-d33e3246.d.ts +11 -0
  23. package/dist/{handshake-a947c234.d.ts → handshake-cdead82a.d.ts} +148 -183
  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 +104 -63
  30. package/dist/router/index.cjs.map +1 -1
  31. package/dist/router/index.d.cts +11 -10
  32. package/dist/router/index.d.ts +11 -10
  33. package/dist/router/index.js +2 -2
  34. package/dist/server-2ef5e6ec.d.ts +42 -0
  35. package/dist/{services-38b3f758.d.ts → services-e1417b33.d.ts} +3 -3
  36. package/dist/transport/impls/uds/client.cjs +1246 -1230
  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 +1298 -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 +976 -965
  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 +1433 -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 -310
  64. package/dist/util/testHelpers.cjs.map +1 -1
  65. package/dist/util/testHelpers.d.cts +9 -6
  66. package/dist/util/testHelpers.d.ts +9 -6
  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-7MJYOL32.js.map +0 -1
  72. package/dist/chunk-AVL32IMG.js.map +0 -1
  73. package/dist/chunk-DPKOJQWF.js +0 -476
  74. package/dist/chunk-DPKOJQWF.js.map +0 -1
  75. package/dist/chunk-EV5HW4IC.js.map +0 -1
  76. package/dist/chunk-J6N6H2WU.js +0 -476
  77. package/dist/chunk-J6N6H2WU.js.map +0 -1
  78. package/dist/chunk-MW5JXLHY.js +0 -348
  79. package/dist/chunk-MW5JXLHY.js.map +0 -1
  80. package/dist/chunk-R2HAS3GM.js.map +0 -1
  81. package/dist/chunk-RJOWZIWB.js +0 -335
  82. package/dist/chunk-RJOWZIWB.js.map +0 -1
  83. package/dist/connection-df85db7e.d.ts +0 -17
  84. package/dist/server-53cd5b7e.d.ts +0 -24
@@ -34,12 +34,15 @@ __export(testHelpers_exports, {
34
34
  asClientStream: () => asClientStream,
35
35
  asClientSubscription: () => asClientSubscription,
36
36
  asClientUpload: () => asClientUpload,
37
+ closeAllConnections: () => closeAllConnections,
37
38
  createDummyTransportMessage: () => createDummyTransportMessage,
38
39
  createLocalWebSocketClient: () => createLocalWebSocketClient,
39
40
  createWebSocketServer: () => createWebSocketServer,
40
41
  dummySession: () => dummySession,
42
+ getTransportConnections: () => getTransportConnections,
41
43
  getUnixSocketPath: () => getUnixSocketPath,
42
44
  iterNext: () => iterNext,
45
+ numberOfConnections: () => numberOfConnections,
43
46
  onUdsServeReady: () => onUdsServeReady,
44
47
  onWsServerReady: () => onWsServerReady,
45
48
  payloadToTransportMessage: () => payloadToTransportMessage,
@@ -331,16 +334,112 @@ function _pushable(getNext, options) {
331
334
  return pushable2;
332
335
  }
333
336
 
334
- // router/result.ts
337
+ // transport/message.ts
335
338
  var import_typebox = require("@sinclair/typebox");
339
+
340
+ // transport/id.ts
341
+ var import_nanoid = require("nanoid");
342
+ var alphabet = (0, import_nanoid.customAlphabet)(
343
+ "1234567890abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ"
344
+ );
345
+ var generateId = () => alphabet(12);
346
+
347
+ // transport/message.ts
348
+ var TransportMessageSchema = (t) => import_typebox.Type.Object({
349
+ id: import_typebox.Type.String(),
350
+ from: import_typebox.Type.String(),
351
+ to: import_typebox.Type.String(),
352
+ seq: import_typebox.Type.Integer(),
353
+ ack: import_typebox.Type.Integer(),
354
+ serviceName: import_typebox.Type.Optional(import_typebox.Type.String()),
355
+ procedureName: import_typebox.Type.Optional(import_typebox.Type.String()),
356
+ streamId: import_typebox.Type.String(),
357
+ controlFlags: import_typebox.Type.Integer(),
358
+ tracing: import_typebox.Type.Optional(
359
+ import_typebox.Type.Object({
360
+ traceparent: import_typebox.Type.String(),
361
+ tracestate: import_typebox.Type.String()
362
+ })
363
+ ),
364
+ payload: t
365
+ });
366
+ var ControlMessageAckSchema = import_typebox.Type.Object({
367
+ type: import_typebox.Type.Literal("ACK")
368
+ });
369
+ var ControlMessageCloseSchema = import_typebox.Type.Object({
370
+ type: import_typebox.Type.Literal("CLOSE")
371
+ });
372
+ var ControlMessageHandshakeRequestSchema = import_typebox.Type.Object({
373
+ type: import_typebox.Type.Literal("HANDSHAKE_REQ"),
374
+ protocolVersion: import_typebox.Type.String(),
375
+ sessionId: import_typebox.Type.String(),
376
+ /**
377
+ * Specifies what the server's expected session state (from the pov of the client). This can be
378
+ * used by the server to know whether this is a new or a reestablished connection, and whether it
379
+ * is compatible with what it already has.
380
+ */
381
+ expectedSessionState: import_typebox.Type.Object({
382
+ // what the client expects the server to send next
383
+ nextExpectedSeq: import_typebox.Type.Integer(),
384
+ // TODO: remove optional once we know all servers
385
+ // are nextSentSeq here
386
+ // what the server expects the client to send next
387
+ nextSentSeq: import_typebox.Type.Optional(import_typebox.Type.Integer())
388
+ }),
389
+ metadata: import_typebox.Type.Optional(import_typebox.Type.Unknown())
390
+ });
391
+ var HandshakeErrorRetriableResponseCodes = import_typebox.Type.Union([
392
+ import_typebox.Type.Literal("SESSION_STATE_MISMATCH")
393
+ ]);
394
+ var HandshakeErrorFatalResponseCodes = import_typebox.Type.Union([
395
+ import_typebox.Type.Literal("MALFORMED_HANDSHAKE_META"),
396
+ import_typebox.Type.Literal("MALFORMED_HANDSHAKE"),
397
+ import_typebox.Type.Literal("PROTOCOL_VERSION_MISMATCH"),
398
+ import_typebox.Type.Literal("REJECTED_BY_CUSTOM_HANDLER")
399
+ ]);
400
+ var HandshakeErrorResponseCodes = import_typebox.Type.Union([
401
+ HandshakeErrorRetriableResponseCodes,
402
+ HandshakeErrorFatalResponseCodes
403
+ ]);
404
+ var ControlMessageHandshakeResponseSchema = import_typebox.Type.Object({
405
+ type: import_typebox.Type.Literal("HANDSHAKE_RESP"),
406
+ status: import_typebox.Type.Union([
407
+ import_typebox.Type.Object({
408
+ ok: import_typebox.Type.Literal(true),
409
+ sessionId: import_typebox.Type.String()
410
+ }),
411
+ import_typebox.Type.Object({
412
+ ok: import_typebox.Type.Literal(false),
413
+ reason: import_typebox.Type.String(),
414
+ // TODO: remove optional once we know all servers
415
+ // are sending code here
416
+ code: import_typebox.Type.Optional(HandshakeErrorResponseCodes)
417
+ })
418
+ ])
419
+ });
420
+ var ControlMessagePayloadSchema = import_typebox.Type.Union([
421
+ ControlMessageCloseSchema,
422
+ ControlMessageAckSchema,
423
+ ControlMessageHandshakeRequestSchema,
424
+ ControlMessageHandshakeResponseSchema
425
+ ]);
426
+ var OpaqueTransportMessageSchema = TransportMessageSchema(
427
+ import_typebox.Type.Unknown()
428
+ );
429
+ function isAck(controlFlag) {
430
+ return (controlFlag & 1 /* AckBit */) === 1 /* AckBit */;
431
+ }
432
+
433
+ // router/result.ts
434
+ var import_typebox2 = require("@sinclair/typebox");
336
435
  var UNCAUGHT_ERROR = "UNCAUGHT_ERROR";
337
436
  var UNEXPECTED_DISCONNECT = "UNEXPECTED_DISCONNECT";
338
- var RiverUncaughtSchema = import_typebox.Type.Object({
339
- code: import_typebox.Type.Union([
340
- import_typebox.Type.Literal(UNCAUGHT_ERROR),
341
- import_typebox.Type.Literal(UNEXPECTED_DISCONNECT)
437
+ var RiverUncaughtSchema = import_typebox2.Type.Object({
438
+ code: import_typebox2.Type.Union([
439
+ import_typebox2.Type.Literal(UNCAUGHT_ERROR),
440
+ import_typebox2.Type.Literal(UNEXPECTED_DISCONNECT)
342
441
  ]),
343
- message: import_typebox.Type.String()
442
+ message: import_typebox2.Type.String()
344
443
  });
345
444
  function Err(error) {
346
445
  return {
@@ -353,19 +452,19 @@ function Err(error) {
353
452
  var import_api = require("@opentelemetry/api");
354
453
 
355
454
  // package.json
356
- var version = "0.23.18";
455
+ var version = "0.24.0";
357
456
 
358
457
  // tracing/index.ts
359
- function createSessionTelemetryInfo(session, propagationCtx) {
458
+ function createSessionTelemetryInfo(sessionId, to, from, propagationCtx) {
360
459
  const parentCtx = propagationCtx ? import_api.propagation.extract(import_api.context.active(), propagationCtx) : import_api.context.active();
361
460
  const span = tracer.startSpan(
362
- `session ${session.id}`,
461
+ `session ${sessionId}`,
363
462
  {
364
463
  attributes: {
365
464
  component: "river",
366
- "river.session.id": session.id,
367
- "river.session.to": session.to,
368
- "river.session.from": session.from
465
+ "river.session.id": sessionId,
466
+ "river.session.to": to,
467
+ "river.session.from": from
369
468
  }
370
469
  },
371
470
  parentCtx
@@ -386,298 +485,6 @@ function coerceErrorString(err) {
386
485
  // util/testHelpers.ts
387
486
  var import_nanoid2 = require("nanoid");
388
487
 
389
- // transport/session.ts
390
- var import_nanoid = require("nanoid");
391
- var import_api2 = require("@opentelemetry/api");
392
- var nanoid = (0, import_nanoid.customAlphabet)("1234567890abcdefghijklmnopqrstuvxyz", 6);
393
- var unsafeId = () => nanoid();
394
- var Session = class {
395
- codec;
396
- options;
397
- telemetry;
398
- /**
399
- * The buffer of messages that have been sent but not yet acknowledged.
400
- */
401
- sendBuffer = [];
402
- /**
403
- * The active connection associated with this session
404
- */
405
- connection;
406
- /**
407
- * A connection that is currently undergoing handshaking. Used to distinguish between the active
408
- * connection, but still be able to close it if needed.
409
- */
410
- handshakingConnection;
411
- from;
412
- to;
413
- /**
414
- * The unique ID of this session.
415
- */
416
- id;
417
- /**
418
- * What the other side advertised as their session ID
419
- * for this session.
420
- */
421
- advertisedSessionId;
422
- /**
423
- * Number of messages we've sent along this session (excluding handshake and acks)
424
- */
425
- seq = 0;
426
- /**
427
- * Number of unique messages we've received this session (excluding handshake and acks)
428
- */
429
- ack = 0;
430
- /**
431
- * The grace period between when the inner connection is disconnected
432
- * and when we should consider the entire session disconnected.
433
- */
434
- disconnectionGrace;
435
- /**
436
- * Number of heartbeats we've sent without a response.
437
- */
438
- heartbeatMisses;
439
- /**
440
- * The interval for sending heartbeats.
441
- */
442
- heartbeat;
443
- log;
444
- constructor(conn, from, to, options, propagationCtx) {
445
- this.id = `session-${nanoid(12)}`;
446
- this.options = options;
447
- this.from = from;
448
- this.to = to;
449
- this.connection = conn;
450
- this.codec = options.codec;
451
- this.heartbeatMisses = 0;
452
- this.heartbeat = setInterval(
453
- () => this.sendHeartbeat(),
454
- options.heartbeatIntervalMs
455
- );
456
- this.telemetry = createSessionTelemetryInfo(this, propagationCtx);
457
- }
458
- bindLogger(log) {
459
- this.log = log;
460
- }
461
- get loggingMetadata() {
462
- const spanContext = this.telemetry.span.spanContext();
463
- return {
464
- clientId: this.from,
465
- connectedTo: this.to,
466
- sessionId: this.id,
467
- connId: this.connection?.id,
468
- telemetry: {
469
- traceId: spanContext.traceId,
470
- spanId: spanContext.spanId
471
- }
472
- };
473
- }
474
- /**
475
- * Sends a message over the session's connection.
476
- * If the connection is not ready or the message fails to send, the message can be buffered for retry unless skipped.
477
- *
478
- * @param msg The partial message to be sent, which will be constructed into a full message.
479
- * @param addToSendBuff Whether to add the message to the send buffer for retry.
480
- * @returns The full transport ID of the message that was attempted to be sent.
481
- */
482
- send(msg) {
483
- const fullMsg = this.constructMsg(msg);
484
- this.log?.debug(`sending msg`, {
485
- ...this.loggingMetadata,
486
- transportMessage: fullMsg
487
- });
488
- if (this.connection) {
489
- const ok = this.connection.send(this.codec.toBuffer(fullMsg));
490
- if (ok)
491
- return fullMsg.id;
492
- this.log?.info(
493
- `failed to send msg to ${fullMsg.to}, connection is probably dead`,
494
- {
495
- ...this.loggingMetadata,
496
- transportMessage: fullMsg
497
- }
498
- );
499
- } else {
500
- this.log?.debug(
501
- `buffering msg to ${fullMsg.to}, connection not ready yet`,
502
- { ...this.loggingMetadata, transportMessage: fullMsg }
503
- );
504
- }
505
- return fullMsg.id;
506
- }
507
- sendHeartbeat() {
508
- const misses = this.heartbeatMisses;
509
- const missDuration = misses * this.options.heartbeatIntervalMs;
510
- if (misses > this.options.heartbeatsUntilDead) {
511
- if (this.connection) {
512
- this.log?.info(
513
- `closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
514
- this.loggingMetadata
515
- );
516
- this.telemetry.span.addEvent("closing connection due to inactivity");
517
- this.closeStaleConnection();
518
- }
519
- return;
520
- }
521
- this.send({
522
- streamId: "heartbeat",
523
- controlFlags: 1 /* AckBit */,
524
- payload: {
525
- type: "ACK"
526
- }
527
- });
528
- this.heartbeatMisses++;
529
- }
530
- resetBufferedMessages() {
531
- this.sendBuffer = [];
532
- this.seq = 0;
533
- this.ack = 0;
534
- }
535
- sendBufferedMessages(conn) {
536
- this.log?.info(`resending ${this.sendBuffer.length} buffered messages`, {
537
- ...this.loggingMetadata,
538
- connId: conn.id
539
- });
540
- for (const msg of this.sendBuffer) {
541
- this.log?.debug(`resending msg`, {
542
- ...this.loggingMetadata,
543
- transportMessage: msg,
544
- connId: conn.id
545
- });
546
- const ok = conn.send(this.codec.toBuffer(msg));
547
- if (!ok) {
548
- const errMsg = `failed to send buffered message to ${this.to} (sus, this is a fresh connection)`;
549
- conn.telemetry?.span.setStatus({
550
- code: import_api2.SpanStatusCode.ERROR,
551
- message: errMsg
552
- });
553
- this.log?.error(errMsg, {
554
- ...this.loggingMetadata,
555
- transportMessage: msg,
556
- connId: conn.id,
557
- tags: ["invariant-violation"]
558
- });
559
- conn.close();
560
- return;
561
- }
562
- }
563
- }
564
- updateBookkeeping(ack, seq) {
565
- if (seq + 1 < this.ack) {
566
- this.log?.error(`received stale seq ${seq} + 1 < ${this.ack}`, {
567
- ...this.loggingMetadata,
568
- tags: ["invariant-violation"]
569
- });
570
- return;
571
- }
572
- this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
573
- this.ack = seq + 1;
574
- }
575
- closeStaleConnection(conn) {
576
- if (this.connection === void 0 || this.connection === conn)
577
- return;
578
- this.log?.info(
579
- `closing old inner connection from session to ${this.to}`,
580
- this.loggingMetadata
581
- );
582
- this.connection.close();
583
- this.connection = void 0;
584
- }
585
- replaceWithNewConnection(newConn, isTransparentReconnect) {
586
- this.closeStaleConnection(newConn);
587
- this.cancelGrace();
588
- if (isTransparentReconnect) {
589
- this.sendBufferedMessages(newConn);
590
- }
591
- this.connection = newConn;
592
- this.handshakingConnection = void 0;
593
- }
594
- replaceWithNewHandshakingConnection(newConn) {
595
- this.handshakingConnection = newConn;
596
- }
597
- beginGrace(cb) {
598
- this.log?.info(
599
- `starting ${this.options.sessionDisconnectGraceMs}ms grace period until session to ${this.to} is closed`,
600
- this.loggingMetadata
601
- );
602
- this.cancelGrace();
603
- this.disconnectionGrace = setTimeout(() => {
604
- this.log?.info(
605
- `grace period for ${this.to} elapsed`,
606
- this.loggingMetadata
607
- );
608
- cb();
609
- }, this.options.sessionDisconnectGraceMs);
610
- }
611
- // called on reconnect of the underlying session
612
- cancelGrace() {
613
- this.heartbeatMisses = 0;
614
- clearTimeout(this.disconnectionGrace);
615
- this.disconnectionGrace = void 0;
616
- }
617
- /**
618
- * Used to close the handshaking connection, if set.
619
- */
620
- closeHandshakingConnection(expectedHandshakingConn) {
621
- if (this.handshakingConnection === void 0)
622
- return;
623
- if (expectedHandshakingConn !== void 0 && this.handshakingConnection === expectedHandshakingConn) {
624
- return;
625
- }
626
- this.handshakingConnection.close();
627
- this.handshakingConnection = void 0;
628
- }
629
- // closed when we want to discard the whole session
630
- // (i.e. shutdown or session disconnect)
631
- close() {
632
- this.closeStaleConnection();
633
- this.cancelGrace();
634
- this.resetBufferedMessages();
635
- clearInterval(this.heartbeat);
636
- }
637
- get connected() {
638
- return this.connection !== void 0;
639
- }
640
- get nextExpectedAck() {
641
- return this.seq;
642
- }
643
- get nextExpectedSeq() {
644
- return this.ack;
645
- }
646
- /**
647
- * Check that the peer's next expected seq number matches something that is in our send buffer
648
- * _or_ matches our actual next seq.
649
- */
650
- nextExpectedSeqInRange(nextExpectedSeq) {
651
- for (const msg of this.sendBuffer) {
652
- if (nextExpectedSeq === msg.seq) {
653
- return true;
654
- }
655
- }
656
- return nextExpectedSeq === this.seq;
657
- }
658
- // This is only used in tests to make the session misbehave.
659
- /* @internal */
660
- advanceAckForTesting(by) {
661
- this.ack += by;
662
- }
663
- constructMsg(partialMsg) {
664
- const msg = {
665
- ...partialMsg,
666
- id: unsafeId(),
667
- to: this.to,
668
- from: this.from,
669
- seq: this.seq,
670
- ack: this.ack
671
- };
672
- this.seq++;
673
- this.sendBuffer.push(msg);
674
- return msg;
675
- }
676
- inspectSendBuffer() {
677
- return this.sendBuffer;
678
- }
679
- };
680
-
681
488
  // codec/json.ts
682
489
  var encoder = new TextEncoder();
683
490
  var decoder = new TextDecoder();
@@ -735,7 +542,8 @@ var defaultTransportOptions = {
735
542
  heartbeatIntervalMs: 1e3,
736
543
  heartbeatsUntilDead: 2,
737
544
  sessionDisconnectGraceMs: 5e3,
738
- handshakeTimeoutMs: 5e3,
545
+ connectionTimeoutMs: 2e3,
546
+ handshakeTimeoutMs: 1e3,
739
547
  codec: NaiveJsonCodec
740
548
  };
741
549
  var defaultConnectionRetryOptions = {
@@ -753,6 +561,609 @@ var defaultServerTransportOptions = {
753
561
  ...defaultTransportOptions
754
562
  };
755
563
 
564
+ // transport/sessionStateMachine/common.ts
565
+ var import_value = require("@sinclair/typebox/value");
566
+ var ERR_CONSUMED = `session state has been consumed and is no longer valid`;
567
+ var StateMachineState = class {
568
+ /*
569
+ * Whether this state has been consumed
570
+ * and we've moved on to another state
571
+ */
572
+ _isConsumed;
573
+ close() {
574
+ this._handleClose();
575
+ }
576
+ constructor() {
577
+ this._isConsumed = false;
578
+ return new Proxy(this, {
579
+ get(target, prop) {
580
+ if (prop === "_isConsumed" || prop === "id" || prop === "state") {
581
+ return Reflect.get(target, prop);
582
+ }
583
+ if (prop === "_handleStateExit") {
584
+ return () => {
585
+ target._isConsumed = true;
586
+ target._handleStateExit();
587
+ };
588
+ }
589
+ if (prop === "_handleClose") {
590
+ return () => {
591
+ target._handleStateExit();
592
+ target._handleClose();
593
+ };
594
+ }
595
+ if (target._isConsumed) {
596
+ throw new Error(
597
+ `${ERR_CONSUMED}: getting ${prop.toString()} on consumed state`
598
+ );
599
+ }
600
+ return Reflect.get(target, prop);
601
+ },
602
+ set(target, prop, value) {
603
+ if (target._isConsumed) {
604
+ throw new Error(
605
+ `${ERR_CONSUMED}: setting ${prop.toString()} on consumed state`
606
+ );
607
+ }
608
+ return Reflect.set(target, prop, value);
609
+ }
610
+ });
611
+ }
612
+ };
613
+ var CommonSession = class extends StateMachineState {
614
+ from;
615
+ options;
616
+ log;
617
+ constructor(from, options, log) {
618
+ super();
619
+ this.from = from;
620
+ this.options = options;
621
+ this.log = log;
622
+ }
623
+ parseMsg(msg) {
624
+ const parsedMsg = this.options.codec.fromBuffer(msg);
625
+ if (parsedMsg === null) {
626
+ const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));
627
+ this.log?.error(
628
+ `received malformed msg: ${decodedBuffer}`,
629
+ this.loggingMetadata
630
+ );
631
+ return null;
632
+ }
633
+ if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
634
+ this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
635
+ ...this.loggingMetadata,
636
+ validationErrors: [
637
+ ...import_value.Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
638
+ ]
639
+ });
640
+ return null;
641
+ }
642
+ return parsedMsg;
643
+ }
644
+ };
645
+ var IdentifiedSession = class extends CommonSession {
646
+ id;
647
+ telemetry;
648
+ to;
649
+ /**
650
+ * Index of the message we will send next (excluding handshake)
651
+ */
652
+ seq;
653
+ /**
654
+ * Number of unique messages we've received this session (excluding handshake)
655
+ */
656
+ ack;
657
+ sendBuffer;
658
+ constructor(id, from, to, seq, ack, sendBuffer, telemetry, options, log) {
659
+ super(from, options, log);
660
+ this.id = id;
661
+ this.to = to;
662
+ this.seq = seq;
663
+ this.ack = ack;
664
+ this.sendBuffer = sendBuffer;
665
+ this.telemetry = telemetry;
666
+ this.log = log;
667
+ }
668
+ get loggingMetadata() {
669
+ const spanContext = this.telemetry.span.spanContext();
670
+ return {
671
+ clientId: this.from,
672
+ connectedTo: this.to,
673
+ sessionId: this.id,
674
+ telemetry: {
675
+ traceId: spanContext.traceId,
676
+ spanId: spanContext.spanId
677
+ }
678
+ };
679
+ }
680
+ constructMsg(partialMsg) {
681
+ const msg = {
682
+ ...partialMsg,
683
+ id: generateId(),
684
+ to: this.to,
685
+ from: this.from,
686
+ seq: this.seq,
687
+ ack: this.ack
688
+ };
689
+ this.seq++;
690
+ return msg;
691
+ }
692
+ nextSeq() {
693
+ return this.sendBuffer.length > 0 ? this.sendBuffer[0].seq : this.seq;
694
+ }
695
+ send(msg) {
696
+ const constructedMsg = this.constructMsg(msg);
697
+ this.sendBuffer.push(constructedMsg);
698
+ return constructedMsg.id;
699
+ }
700
+ _handleStateExit() {
701
+ }
702
+ _handleClose() {
703
+ this.sendBuffer.length = 0;
704
+ this.telemetry.span.end();
705
+ }
706
+ };
707
+
708
+ // transport/sessionStateMachine/SessionConnecting.ts
709
+ var SessionConnecting = class extends IdentifiedSession {
710
+ state = "Connecting" /* Connecting */;
711
+ connPromise;
712
+ listeners;
713
+ connectionTimeout;
714
+ constructor(connPromise, listeners, ...args) {
715
+ super(...args);
716
+ this.connPromise = connPromise;
717
+ this.listeners = listeners;
718
+ this.connectionTimeout = setTimeout(() => {
719
+ listeners.onConnectionTimeout();
720
+ }, this.options.connectionTimeoutMs);
721
+ connPromise.then(
722
+ (conn) => {
723
+ if (this._isConsumed)
724
+ return;
725
+ listeners.onConnectionEstablished(conn);
726
+ },
727
+ (err) => {
728
+ if (this._isConsumed)
729
+ return;
730
+ listeners.onConnectionFailed(err);
731
+ }
732
+ );
733
+ }
734
+ // close a pending connection if it resolves, ignore errors if the promise
735
+ // ends up rejected anyways
736
+ bestEffortClose() {
737
+ void this.connPromise.then((conn) => conn.close()).catch(() => {
738
+ });
739
+ }
740
+ _handleStateExit() {
741
+ super._handleStateExit();
742
+ clearTimeout(this.connectionTimeout);
743
+ this.connectionTimeout = void 0;
744
+ }
745
+ _handleClose() {
746
+ this.bestEffortClose();
747
+ super._handleClose();
748
+ }
749
+ };
750
+
751
+ // transport/sessionStateMachine/SessionNoConnection.ts
752
+ var SessionNoConnection = class extends IdentifiedSession {
753
+ state = "NoConnection" /* NoConnection */;
754
+ listeners;
755
+ gracePeriodTimeout;
756
+ constructor(listeners, ...args) {
757
+ super(...args);
758
+ this.listeners = listeners;
759
+ this.gracePeriodTimeout = setTimeout(() => {
760
+ this.listeners.onSessionGracePeriodElapsed();
761
+ }, this.options.sessionDisconnectGraceMs);
762
+ }
763
+ _handleClose() {
764
+ super._handleClose();
765
+ }
766
+ _handleStateExit() {
767
+ super._handleStateExit();
768
+ if (this.gracePeriodTimeout) {
769
+ clearTimeout(this.gracePeriodTimeout);
770
+ this.gracePeriodTimeout = void 0;
771
+ }
772
+ }
773
+ };
774
+
775
+ // transport/sessionStateMachine/SessionWaitingForHandshake.ts
776
+ var SessionWaitingForHandshake = class extends CommonSession {
777
+ state = "WaitingForHandshake" /* WaitingForHandshake */;
778
+ conn;
779
+ listeners;
780
+ handshakeTimeout;
781
+ constructor(conn, listeners, ...args) {
782
+ super(...args);
783
+ this.conn = conn;
784
+ this.listeners = listeners;
785
+ this.handshakeTimeout = setTimeout(() => {
786
+ listeners.onHandshakeTimeout();
787
+ }, this.options.handshakeTimeoutMs);
788
+ this.conn.addDataListener(this.onHandshakeData);
789
+ this.conn.addErrorListener(listeners.onConnectionErrored);
790
+ this.conn.addCloseListener(listeners.onConnectionClosed);
791
+ }
792
+ onHandshakeData = (msg) => {
793
+ const parsedMsg = this.parseMsg(msg);
794
+ if (parsedMsg === null) {
795
+ this.listeners.onInvalidHandshake("could not parse message");
796
+ return;
797
+ }
798
+ this.listeners.onHandshake(parsedMsg);
799
+ };
800
+ get loggingMetadata() {
801
+ return {
802
+ clientId: this.from,
803
+ connId: this.conn.id
804
+ };
805
+ }
806
+ sendHandshake(msg) {
807
+ return this.conn.send(this.options.codec.toBuffer(msg));
808
+ }
809
+ _handleStateExit() {
810
+ this.conn.removeDataListener(this.onHandshakeData);
811
+ this.conn.removeErrorListener(this.listeners.onConnectionErrored);
812
+ this.conn.removeCloseListener(this.listeners.onConnectionClosed);
813
+ clearTimeout(this.handshakeTimeout);
814
+ this.handshakeTimeout = void 0;
815
+ }
816
+ _handleClose() {
817
+ this.conn.close();
818
+ }
819
+ };
820
+
821
+ // transport/sessionStateMachine/SessionHandshaking.ts
822
+ var SessionHandshaking = class extends IdentifiedSession {
823
+ state = "Handshaking" /* Handshaking */;
824
+ conn;
825
+ listeners;
826
+ handshakeTimeout;
827
+ constructor(conn, listeners, ...args) {
828
+ super(...args);
829
+ this.conn = conn;
830
+ this.listeners = listeners;
831
+ this.handshakeTimeout = setTimeout(() => {
832
+ listeners.onHandshakeTimeout();
833
+ }, this.options.handshakeTimeoutMs);
834
+ this.conn.addDataListener(this.onHandshakeData);
835
+ this.conn.addErrorListener(listeners.onConnectionErrored);
836
+ this.conn.addCloseListener(listeners.onConnectionClosed);
837
+ }
838
+ onHandshakeData = (msg) => {
839
+ const parsedMsg = this.parseMsg(msg);
840
+ if (parsedMsg === null) {
841
+ this.listeners.onInvalidHandshake("could not parse message");
842
+ return;
843
+ }
844
+ this.listeners.onHandshake(parsedMsg);
845
+ };
846
+ sendHandshake(msg) {
847
+ return this.conn.send(this.options.codec.toBuffer(msg));
848
+ }
849
+ _handleStateExit() {
850
+ super._handleStateExit();
851
+ this.conn.removeDataListener(this.onHandshakeData);
852
+ this.conn.removeErrorListener(this.listeners.onConnectionErrored);
853
+ this.conn.removeCloseListener(this.listeners.onConnectionClosed);
854
+ clearTimeout(this.handshakeTimeout);
855
+ }
856
+ _handleClose() {
857
+ super._handleClose();
858
+ this.conn.close();
859
+ }
860
+ };
861
+
862
+ // transport/sessionStateMachine/SessionConnected.ts
863
+ var import_api2 = require("@opentelemetry/api");
864
+ var SessionConnected = class extends IdentifiedSession {
865
+ state = "Connected" /* Connected */;
866
+ conn;
867
+ listeners;
868
+ heartbeatHandle;
869
+ heartbeatMisses = 0;
870
+ get isActivelyHeartbeating() {
871
+ return this.heartbeatHandle !== void 0;
872
+ }
873
+ updateBookkeeping(ack, seq) {
874
+ this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
875
+ this.ack = seq + 1;
876
+ this.heartbeatMisses = 0;
877
+ }
878
+ send(msg) {
879
+ const constructedMsg = this.constructMsg(msg);
880
+ this.sendBuffer.push(constructedMsg);
881
+ this.conn.send(this.options.codec.toBuffer(constructedMsg));
882
+ return constructedMsg.id;
883
+ }
884
+ constructor(conn, listeners, ...args) {
885
+ super(...args);
886
+ this.conn = conn;
887
+ this.listeners = listeners;
888
+ this.conn.addDataListener(this.onMessageData);
889
+ this.conn.addCloseListener(listeners.onConnectionClosed);
890
+ this.conn.addErrorListener(listeners.onConnectionErrored);
891
+ if (this.sendBuffer.length > 0) {
892
+ this.log?.debug(
893
+ `sending ${this.sendBuffer.length} buffered messages`,
894
+ this.loggingMetadata
895
+ );
896
+ }
897
+ for (const msg of this.sendBuffer) {
898
+ conn.send(this.options.codec.toBuffer(msg));
899
+ }
900
+ }
901
+ startActiveHeartbeat() {
902
+ this.heartbeatHandle = setInterval(() => {
903
+ const misses = this.heartbeatMisses;
904
+ const missDuration = misses * this.options.heartbeatIntervalMs;
905
+ if (misses >= this.options.heartbeatsUntilDead) {
906
+ this.log?.info(
907
+ `closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
908
+ this.loggingMetadata
909
+ );
910
+ this.telemetry.span.addEvent("closing connection due to inactivity");
911
+ this.conn.close();
912
+ clearInterval(this.heartbeatHandle);
913
+ this.heartbeatHandle = void 0;
914
+ return;
915
+ }
916
+ this.sendHeartbeat();
917
+ this.heartbeatMisses++;
918
+ }, this.options.heartbeatIntervalMs);
919
+ }
920
+ sendHeartbeat() {
921
+ this.send({
922
+ streamId: "heartbeat",
923
+ controlFlags: 1 /* AckBit */,
924
+ payload: {
925
+ type: "ACK"
926
+ }
927
+ });
928
+ }
929
+ onMessageData = (msg) => {
930
+ const parsedMsg = this.parseMsg(msg);
931
+ if (parsedMsg === null)
932
+ return;
933
+ if (parsedMsg.seq !== this.ack) {
934
+ if (parsedMsg.seq < this.ack) {
935
+ this.log?.debug(
936
+ `received duplicate msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack}), discarding`,
937
+ {
938
+ ...this.loggingMetadata,
939
+ transportMessage: parsedMsg
940
+ }
941
+ );
942
+ } else {
943
+ const reason = `received out-of-order msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack})`;
944
+ this.log?.error(reason, {
945
+ ...this.loggingMetadata,
946
+ transportMessage: parsedMsg,
947
+ tags: ["invariant-violation"]
948
+ });
949
+ this.telemetry.span.setStatus({
950
+ code: import_api2.SpanStatusCode.ERROR,
951
+ message: reason
952
+ });
953
+ this.listeners.onInvalidMessage(reason);
954
+ }
955
+ return;
956
+ }
957
+ this.log?.debug(`received msg`, {
958
+ ...this.loggingMetadata,
959
+ transportMessage: parsedMsg
960
+ });
961
+ this.updateBookkeeping(parsedMsg.ack, parsedMsg.seq);
962
+ if (!isAck(parsedMsg.controlFlags)) {
963
+ this.listeners.onMessage(parsedMsg);
964
+ return;
965
+ }
966
+ this.log?.debug(`discarding msg (ack bit set)`, {
967
+ ...this.loggingMetadata,
968
+ transportMessage: parsedMsg
969
+ });
970
+ if (!this.isActivelyHeartbeating) {
971
+ this.sendHeartbeat();
972
+ }
973
+ };
974
+ _handleStateExit() {
975
+ super._handleStateExit();
976
+ this.conn.removeDataListener(this.onMessageData);
977
+ this.conn.removeCloseListener(this.listeners.onConnectionClosed);
978
+ this.conn.removeErrorListener(this.listeners.onConnectionErrored);
979
+ clearInterval(this.heartbeatHandle);
980
+ this.heartbeatHandle = void 0;
981
+ }
982
+ _handleClose() {
983
+ super._handleClose();
984
+ this.conn.close();
985
+ }
986
+ };
987
+
988
+ // transport/sessionStateMachine/transitions.ts
989
+ function inheritSharedSession(session) {
990
+ return [
991
+ session.id,
992
+ session.from,
993
+ session.to,
994
+ session.seq,
995
+ session.ack,
996
+ session.sendBuffer,
997
+ session.telemetry,
998
+ session.options,
999
+ session.log
1000
+ ];
1001
+ }
1002
+ var SessionStateGraph = {
1003
+ entrypoints: {
1004
+ NoConnection(to, from, listeners, options, log) {
1005
+ const id = `session-${generateId()}`;
1006
+ const telemetry = createSessionTelemetryInfo(id, to, from);
1007
+ const sendBuffer = [];
1008
+ const session = new SessionNoConnection(
1009
+ listeners,
1010
+ id,
1011
+ from,
1012
+ to,
1013
+ 0,
1014
+ 0,
1015
+ sendBuffer,
1016
+ telemetry,
1017
+ options,
1018
+ log
1019
+ );
1020
+ session.log?.info(`session ${session.id} created in NoConnection state`, {
1021
+ ...session.loggingMetadata,
1022
+ tags: ["state-transition"]
1023
+ });
1024
+ return session;
1025
+ },
1026
+ WaitingForHandshake(from, conn, listeners, options, log) {
1027
+ const session = new SessionWaitingForHandshake(
1028
+ conn,
1029
+ listeners,
1030
+ from,
1031
+ options,
1032
+ log
1033
+ );
1034
+ session.log?.info(`session created in WaitingForHandshake state`, {
1035
+ ...session.loggingMetadata,
1036
+ tags: ["state-transition"]
1037
+ });
1038
+ return session;
1039
+ }
1040
+ },
1041
+ // All of the transitions 'move'/'consume' the old session and return a new one.
1042
+ // After a session is transitioned, any usage of the old session will throw.
1043
+ transition: {
1044
+ // happy path transitions
1045
+ NoConnectionToConnecting(oldSession, connPromise, listeners) {
1046
+ const carriedState = inheritSharedSession(oldSession);
1047
+ oldSession._handleStateExit();
1048
+ const session = new SessionConnecting(
1049
+ connPromise,
1050
+ listeners,
1051
+ ...carriedState
1052
+ );
1053
+ session.log?.info(
1054
+ `session ${session.id} transition from NoConnection to Connecting`,
1055
+ {
1056
+ ...session.loggingMetadata,
1057
+ tags: ["state-transition"]
1058
+ }
1059
+ );
1060
+ return session;
1061
+ },
1062
+ ConnectingToHandshaking(oldSession, conn, listeners) {
1063
+ const carriedState = inheritSharedSession(oldSession);
1064
+ oldSession._handleStateExit();
1065
+ const session = new SessionHandshaking(conn, listeners, ...carriedState);
1066
+ session.log?.info(
1067
+ `session ${session.id} transition from Connecting to Handshaking`,
1068
+ {
1069
+ ...session.loggingMetadata,
1070
+ tags: ["state-transition"]
1071
+ }
1072
+ );
1073
+ return session;
1074
+ },
1075
+ HandshakingToConnected(oldSession, listeners) {
1076
+ const carriedState = inheritSharedSession(oldSession);
1077
+ const conn = oldSession.conn;
1078
+ oldSession._handleStateExit();
1079
+ const session = new SessionConnected(conn, listeners, ...carriedState);
1080
+ session.log?.info(
1081
+ `session ${session.id} transition from Handshaking to Connected`,
1082
+ {
1083
+ ...session.loggingMetadata,
1084
+ tags: ["state-transition"]
1085
+ }
1086
+ );
1087
+ return session;
1088
+ },
1089
+ WaitingForHandshakeToConnected(pendingSession, oldSession, sessionId, to, propagationCtx, listeners) {
1090
+ const conn = pendingSession.conn;
1091
+ const { from, options } = pendingSession;
1092
+ const carriedState = oldSession ? (
1093
+ // old session exists, inherit state
1094
+ inheritSharedSession(oldSession)
1095
+ ) : (
1096
+ // old session does not exist, create new state
1097
+ [
1098
+ sessionId,
1099
+ from,
1100
+ to,
1101
+ 0,
1102
+ 0,
1103
+ [],
1104
+ createSessionTelemetryInfo(sessionId, to, from, propagationCtx),
1105
+ options,
1106
+ pendingSession.log
1107
+ ]
1108
+ );
1109
+ pendingSession._handleStateExit();
1110
+ oldSession?._handleStateExit();
1111
+ const session = new SessionConnected(conn, listeners, ...carriedState);
1112
+ session.log?.info(
1113
+ `session ${session.id} transition from WaitingForHandshake to Connected`,
1114
+ {
1115
+ ...session.loggingMetadata,
1116
+ tags: ["state-transition"]
1117
+ }
1118
+ );
1119
+ return session;
1120
+ },
1121
+ // disconnect paths
1122
+ ConnectingToNoConnection(oldSession, listeners) {
1123
+ const carriedState = inheritSharedSession(oldSession);
1124
+ oldSession.bestEffortClose();
1125
+ oldSession._handleStateExit();
1126
+ const session = new SessionNoConnection(listeners, ...carriedState);
1127
+ session.log?.info(
1128
+ `session ${session.id} transition from Connecting to NoConnection`,
1129
+ {
1130
+ ...session.loggingMetadata,
1131
+ tags: ["state-transition"]
1132
+ }
1133
+ );
1134
+ return session;
1135
+ },
1136
+ HandshakingToNoConnection(oldSession, listeners) {
1137
+ const carriedState = inheritSharedSession(oldSession);
1138
+ oldSession.conn.close();
1139
+ oldSession._handleStateExit();
1140
+ const session = new SessionNoConnection(listeners, ...carriedState);
1141
+ session.log?.info(
1142
+ `session ${session.id} transition from Handshaking to NoConnection`,
1143
+ {
1144
+ ...session.loggingMetadata,
1145
+ tags: ["state-transition"]
1146
+ }
1147
+ );
1148
+ return session;
1149
+ },
1150
+ ConnectedToNoConnection(oldSession, listeners) {
1151
+ const carriedState = inheritSharedSession(oldSession);
1152
+ oldSession.conn.close();
1153
+ oldSession._handleStateExit();
1154
+ const session = new SessionNoConnection(listeners, ...carriedState);
1155
+ session.log?.info(
1156
+ `session ${session.id} transition from Connected to NoConnection`,
1157
+ {
1158
+ ...session.loggingMetadata,
1159
+ tags: ["state-transition"]
1160
+ }
1161
+ );
1162
+ return session;
1163
+ }
1164
+ }
1165
+ };
1166
+
756
1167
  // util/testHelpers.ts
757
1168
  function createLocalWebSocketClient(port) {
758
1169
  const sock = new import_ws.default(`ws://localhost:${port}`);
@@ -818,10 +1229,13 @@ function catchProcError(err) {
818
1229
  }
819
1230
  var testingSessionOptions = defaultTransportOptions;
820
1231
  function dummySession() {
821
- return new Session(
822
- void 0,
1232
+ return SessionStateGraph.entrypoints.NoConnection(
823
1233
  "client",
824
1234
  "server",
1235
+ {
1236
+ onSessionGracePeriodElapsed: () => {
1237
+ }
1238
+ },
825
1239
  testingSessionOptions
826
1240
  );
827
1241
  }
@@ -831,8 +1245,7 @@ function dummyCtx(state, session, extendedContext) {
831
1245
  state,
832
1246
  to: session.to,
833
1247
  from: session.from,
834
- streamId: (0, import_nanoid2.nanoid)(),
835
- session,
1248
+ streamId: generateId(),
836
1249
  metadata: {}
837
1250
  };
838
1251
  }
@@ -881,20 +1294,40 @@ function asClientUpload(state, proc, init, extendedContext, session = dummySessi
881
1294
  }
882
1295
  }
883
1296
  var getUnixSocketPath = () => {
884
- return process.platform === "win32" ? `\\\\?\\pipe\\${(0, import_nanoid2.nanoid)()}` : `/tmp/${(0, import_nanoid2.nanoid)()}.sock`;
1297
+ return `/tmp/${(0, import_nanoid2.nanoid)()}.sock`;
885
1298
  };
1299
+ function getTransportConnections(transport) {
1300
+ const connections = [];
1301
+ for (const session of transport.sessions.values()) {
1302
+ if (session.state === "Connected" /* Connected */) {
1303
+ connections.push(session.conn);
1304
+ }
1305
+ }
1306
+ return connections;
1307
+ }
1308
+ function numberOfConnections(transport) {
1309
+ return getTransportConnections(transport).length;
1310
+ }
1311
+ function closeAllConnections(transport) {
1312
+ for (const conn of getTransportConnections(transport)) {
1313
+ conn.close();
1314
+ }
1315
+ }
886
1316
  // Annotate the CommonJS export names for ESM import in node:
887
1317
  0 && (module.exports = {
888
1318
  asClientRpc,
889
1319
  asClientStream,
890
1320
  asClientSubscription,
891
1321
  asClientUpload,
1322
+ closeAllConnections,
892
1323
  createDummyTransportMessage,
893
1324
  createLocalWebSocketClient,
894
1325
  createWebSocketServer,
895
1326
  dummySession,
1327
+ getTransportConnections,
896
1328
  getUnixSocketPath,
897
1329
  iterNext,
1330
+ numberOfConnections,
898
1331
  onUdsServeReady,
899
1332
  onWsServerReady,
900
1333
  payloadToTransportMessage,