@replit/river 0.21.1 → 0.23.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 (63) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-FDLAPYCK.js → chunk-DZOATC6M.js} +2 -2
  3. package/dist/{chunk-JMXO5L2X.js → chunk-MJUFKPBT.js} +354 -398
  4. package/dist/chunk-MJUFKPBT.js.map +1 -0
  5. package/dist/{chunk-5WFL722S.js → chunk-PCKHBAVP.js} +94 -3
  6. package/dist/chunk-PCKHBAVP.js.map +1 -0
  7. package/dist/{chunk-NCXUFDVL.js → chunk-VOJVLWVX.js} +360 -516
  8. package/dist/chunk-VOJVLWVX.js.map +1 -0
  9. package/dist/chunk-ZF2UFTNN.js +60 -0
  10. package/dist/chunk-ZF2UFTNN.js.map +1 -0
  11. package/dist/{connection-76c5ed01.d.ts → connection-5685d817.d.ts} +6 -4
  12. package/dist/{connection-975b25c9.d.ts → connection-7582fb92.d.ts} +1 -1
  13. package/dist/{index-dfad460e.d.ts → index-a6fe0edd.d.ts} +55 -59
  14. package/dist/logging/index.d.cts +2 -1
  15. package/dist/logging/index.d.ts +2 -1
  16. package/dist/router/index.cjs +405 -502
  17. package/dist/router/index.cjs.map +1 -1
  18. package/dist/router/index.d.cts +12 -6
  19. package/dist/router/index.d.ts +12 -6
  20. package/dist/router/index.js +4 -3
  21. package/dist/{services-9c496c6e.d.ts → services-be91b485.d.ts} +21 -52
  22. package/dist/{services-7b716dcf.d.ts → services-eb9326a1.d.ts} +21 -52
  23. package/dist/transport/impls/uds/client.cjs +197 -155
  24. package/dist/transport/impls/uds/client.cjs.map +1 -1
  25. package/dist/transport/impls/uds/client.d.cts +3 -2
  26. package/dist/transport/impls/uds/client.d.ts +3 -2
  27. package/dist/transport/impls/uds/client.js +3 -3
  28. package/dist/transport/impls/uds/server.cjs +280 -266
  29. package/dist/transport/impls/uds/server.cjs.map +1 -1
  30. package/dist/transport/impls/uds/server.d.cts +3 -2
  31. package/dist/transport/impls/uds/server.d.ts +3 -2
  32. package/dist/transport/impls/uds/server.js +3 -3
  33. package/dist/transport/impls/ws/client.cjs +251 -214
  34. package/dist/transport/impls/ws/client.cjs.map +1 -1
  35. package/dist/transport/impls/ws/client.d.cts +6 -6
  36. package/dist/transport/impls/ws/client.d.ts +6 -6
  37. package/dist/transport/impls/ws/client.js +33 -48
  38. package/dist/transport/impls/ws/client.js.map +1 -1
  39. package/dist/transport/impls/ws/server.cjs +302 -280
  40. package/dist/transport/impls/ws/server.cjs.map +1 -1
  41. package/dist/transport/impls/ws/server.d.cts +5 -4
  42. package/dist/transport/impls/ws/server.d.ts +5 -4
  43. package/dist/transport/impls/ws/server.js +3 -3
  44. package/dist/transport/impls/ws/server.js.map +1 -1
  45. package/dist/transport/index.cjs +400 -396
  46. package/dist/transport/index.cjs.map +1 -1
  47. package/dist/transport/index.d.cts +25 -14
  48. package/dist/transport/index.d.ts +25 -14
  49. package/dist/transport/index.js +2 -2
  50. package/dist/util/testHelpers.cjs +59 -14
  51. package/dist/util/testHelpers.cjs.map +1 -1
  52. package/dist/util/testHelpers.d.cts +14 -5
  53. package/dist/util/testHelpers.d.ts +14 -5
  54. package/dist/util/testHelpers.js +12 -5
  55. package/dist/util/testHelpers.js.map +1 -1
  56. package/dist/wslike-e0b32dd5.d.ts +40 -0
  57. package/package.json +4 -5
  58. package/dist/chunk-3Y7AB5EB.js +0 -42
  59. package/dist/chunk-3Y7AB5EB.js.map +0 -1
  60. package/dist/chunk-5WFL722S.js.map +0 -1
  61. package/dist/chunk-JMXO5L2X.js.map +0 -1
  62. package/dist/chunk-NCXUFDVL.js.map +0 -1
  63. /package/dist/{chunk-FDLAPYCK.js.map → chunk-DZOATC6M.js.map} +0 -0
@@ -4,11 +4,14 @@ import {
4
4
  OpaqueTransportMessageSchema,
5
5
  PROTOCOL_VERSION,
6
6
  coerceErrorString,
7
+ createConnectionTelemetryInfo,
8
+ createSessionTelemetryInfo,
9
+ getPropagationContext,
7
10
  handshakeRequestMessage,
8
11
  handshakeResponseMessage,
9
12
  isAck,
10
13
  tracing_default
11
- } from "./chunk-5WFL722S.js";
14
+ } from "./chunk-PCKHBAVP.js";
12
15
  import {
13
16
  log
14
17
  } from "./chunk-OTQNCLFH.js";
@@ -18,17 +21,20 @@ import {
18
21
 
19
22
  // transport/session.ts
20
23
  import { customAlphabet } from "nanoid";
24
+ import { SpanStatusCode } from "@opentelemetry/api";
21
25
  var nanoid = customAlphabet("1234567890abcdefghijklmnopqrstuvxyz", 6);
22
26
  var unsafeId = () => nanoid();
23
27
  var Connection = class {
24
- debugId;
28
+ id;
29
+ telemetry;
25
30
  constructor() {
26
- this.debugId = `conn-${unsafeId()}`;
31
+ this.id = `conn-${nanoid(12)}`;
27
32
  }
28
33
  };
29
34
  var Session = class {
30
35
  codec;
31
36
  options;
37
+ telemetry;
32
38
  /**
33
39
  * The buffer of messages that have been sent but not yet acknowledged.
34
40
  */
@@ -48,12 +54,6 @@ var Session = class {
48
54
  * for this session.
49
55
  */
50
56
  advertisedSessionId;
51
- /**
52
- * The metadata for this session, as parsed from the handshake.
53
- *
54
- * Will only ever be populated on the server side.
55
- */
56
- metadata;
57
57
  /**
58
58
  * Number of messages we've sent along this session (excluding handshake and acks)
59
59
  */
@@ -75,7 +75,7 @@ var Session = class {
75
75
  * The interval for sending heartbeats.
76
76
  */
77
77
  heartbeat;
78
- constructor(conn, from, to, options) {
78
+ constructor(conn, from, to, options, propagationCtx) {
79
79
  this.id = `session-${nanoid(12)}`;
80
80
  this.options = options;
81
81
  this.from = from;
@@ -87,13 +87,14 @@ var Session = class {
87
87
  () => this.sendHeartbeat(),
88
88
  options.heartbeatIntervalMs
89
89
  );
90
+ this.telemetry = createSessionTelemetryInfo(this, propagationCtx);
90
91
  }
91
92
  get loggingMetadata() {
92
93
  return {
93
94
  clientId: this.from,
94
95
  connectedTo: this.to,
95
96
  sessionId: this.id,
96
- connId: this.connection?.debugId
97
+ connId: this.connection?.id
97
98
  };
98
99
  }
99
100
  /**
@@ -138,6 +139,7 @@ var Session = class {
138
139
  `closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
139
140
  this.loggingMetadata
140
141
  );
142
+ this.telemetry.span.addEvent("closing connection due to inactivity");
141
143
  this.closeStaleConnection();
142
144
  }
143
145
  return;
@@ -159,21 +161,25 @@ var Session = class {
159
161
  sendBufferedMessages(conn) {
160
162
  log?.info(`resending ${this.sendBuffer.length} buffered messages`, {
161
163
  ...this.loggingMetadata,
162
- connId: conn.debugId
164
+ connId: conn.id
163
165
  });
164
166
  for (const msg of this.sendBuffer) {
165
167
  log?.debug(`resending msg`, {
166
168
  ...this.loggingMetadata,
167
169
  fullTransportMessage: msg,
168
- connId: conn.debugId
170
+ connId: conn.id
169
171
  });
170
172
  const ok = conn.send(this.codec.toBuffer(msg));
171
173
  if (!ok) {
172
174
  const errMsg = `failed to send buffered message to ${this.to} (sus, this is a fresh connection)`;
175
+ conn.telemetry?.span.setStatus({
176
+ code: SpanStatusCode.ERROR,
177
+ message: errMsg
178
+ });
173
179
  log?.error(errMsg, {
174
180
  ...this.loggingMetadata,
175
181
  fullTransportMessage: msg,
176
- connId: conn.debugId,
182
+ connId: conn.id,
177
183
  tags: ["invariant-violation"]
178
184
  });
179
185
  conn.close();
@@ -292,12 +298,6 @@ var EventDispatcher = class {
292
298
 
293
299
  // transport/transport.ts
294
300
  import { Value } from "@sinclair/typebox/value";
295
- import {
296
- context,
297
- propagation,
298
- SpanKind,
299
- SpanStatusCode
300
- } from "@opentelemetry/api";
301
301
 
302
302
  // transport/rateLimit.ts
303
303
  var LeakyBucketRateLimit = class {
@@ -371,6 +371,7 @@ var LeakyBucketRateLimit = class {
371
371
  };
372
372
 
373
373
  // transport/transport.ts
374
+ import { SpanStatusCode as SpanStatusCode2 } from "@opentelemetry/api";
374
375
  var defaultTransportOptions = {
375
376
  heartbeatIntervalMs: 1e3,
376
377
  heartbeatsUntilDead: 2,
@@ -449,17 +450,22 @@ var Transport = class {
449
450
  status: "connect",
450
451
  conn
451
452
  });
453
+ conn.telemetry = createConnectionTelemetryInfo(
454
+ conn,
455
+ session.telemetry.span
456
+ );
452
457
  if (isReconnect) {
453
458
  session.replaceWithNewConnection(conn);
454
459
  log?.info(`reconnected to ${connectedTo}`, session.loggingMetadata);
455
460
  }
456
461
  }
457
- createSession(to, conn) {
462
+ createSession(to, conn, propagationCtx) {
458
463
  const session = new Session(
459
464
  conn,
460
465
  this.clientId,
461
466
  to,
462
- this.options
467
+ this.options,
468
+ propagationCtx
463
469
  );
464
470
  this.sessions.set(session.to, session);
465
471
  this.eventDispatcher.dispatchEvent("sessionStatus", {
@@ -468,11 +474,11 @@ var Transport = class {
468
474
  });
469
475
  return session;
470
476
  }
471
- getOrCreateSession(to, conn, sessionId) {
477
+ getOrCreateSession(to, conn, sessionId, propagationCtx) {
472
478
  let session = this.sessions.get(to);
473
479
  let isReconnect = session !== void 0;
474
480
  if (session?.advertisedSessionId !== void 0 && sessionId !== void 0 && session.advertisedSessionId !== sessionId) {
475
- log?.warn(
481
+ log?.info(
476
482
  `session for ${to} already exists but has a different session id (expected: ${session.advertisedSessionId}, got: ${sessionId}), creating a new one`,
477
483
  session.loggingMetadata
478
484
  );
@@ -481,7 +487,7 @@ var Transport = class {
481
487
  session = void 0;
482
488
  }
483
489
  if (!session) {
484
- session = this.createSession(to, conn);
490
+ session = this.createSession(to, conn, propagationCtx);
485
491
  log?.info(
486
492
  `no session for ${to}, created a new one`,
487
493
  session.loggingMetadata
@@ -494,6 +500,7 @@ var Transport = class {
494
500
  }
495
501
  deleteSession(session) {
496
502
  session.close();
503
+ session.telemetry.span.end();
497
504
  this.sessions.delete(session.to);
498
505
  log?.info(
499
506
  `session ${session.id} disconnect from ${session.to}`,
@@ -510,12 +517,16 @@ var Transport = class {
510
517
  * @param connectedTo The peer we are connected to.
511
518
  */
512
519
  onDisconnect(conn, session) {
520
+ conn.telemetry?.span.end();
513
521
  this.eventDispatcher.dispatchEvent("connectionStatus", {
514
522
  status: "disconnect",
515
523
  conn
516
524
  });
517
525
  session.connection = void 0;
518
- session.beginGrace(() => this.deleteSession(session));
526
+ session.beginGrace(() => {
527
+ session.telemetry.span.addEvent("session grace period expired");
528
+ this.deleteSession(session);
529
+ });
519
530
  }
520
531
  /**
521
532
  * Parses a message from a Uint8Array into a {@link OpaqueTransportMessage}.
@@ -575,6 +586,10 @@ var Transport = class {
575
586
  tags: ["invariant-violation"]
576
587
  });
577
588
  this.protocolError(ProtocolError.MessageOrderingViolated, errMsg);
589
+ session.telemetry.span.setStatus({
590
+ code: SpanStatusCode2.ERROR,
591
+ message: "message order violated"
592
+ });
578
593
  session.close();
579
594
  }
580
595
  return;
@@ -685,6 +700,10 @@ var ClientTransport = class extends Transport {
685
700
  * tests or a special case where you don't want to reconnect.
686
701
  */
687
702
  reconnectOnConnectionDrop = true;
703
+ /**
704
+ * Optional handshake options for this client.
705
+ */
706
+ handshakeExtensions;
688
707
  constructor(clientId, providedOptions) {
689
708
  super(clientId, providedOptions);
690
709
  this.options = {
@@ -694,6 +713,9 @@ var ClientTransport = class extends Transport {
694
713
  this.inflightConnectionPromises = /* @__PURE__ */ new Map();
695
714
  this.retryBudget = new LeakyBucketRateLimit(this.options);
696
715
  }
716
+ extendHandshake(options) {
717
+ this.handshakeExtensions = options;
718
+ }
697
719
  handleConnection(conn, to) {
698
720
  if (this.state !== "open")
699
721
  return;
@@ -702,7 +724,7 @@ var ClientTransport = class extends Transport {
702
724
  if (!session) {
703
725
  log?.warn(
704
726
  `connection to ${to} timed out waiting for handshake, closing`,
705
- { clientId: this.clientId, connectedTo: to, connId: conn.debugId }
727
+ { clientId: this.clientId, connectedTo: to, connId: conn.id }
706
728
  );
707
729
  conn.close();
708
730
  }
@@ -720,6 +742,10 @@ var ClientTransport = class extends Transport {
720
742
  conn.addDataListener((data2) => {
721
743
  const parsed = this.parseMsg(data2);
722
744
  if (!parsed) {
745
+ conn.telemetry?.span.setStatus({
746
+ code: SpanStatusCode2.ERROR,
747
+ message: "message parse failure"
748
+ });
723
749
  conn.close();
724
750
  return;
725
751
  }
@@ -742,6 +768,10 @@ var ClientTransport = class extends Transport {
742
768
  }
743
769
  });
744
770
  conn.addErrorListener((err) => {
771
+ conn.telemetry?.span.setStatus({
772
+ code: SpanStatusCode2.ERROR,
773
+ message: "connection error"
774
+ });
745
775
  log?.warn(`error in connection to ${to}: ${coerceErrorString(err)}`, {
746
776
  ...session?.loggingMetadata,
747
777
  clientId: this.clientId,
@@ -752,6 +782,10 @@ var ClientTransport = class extends Transport {
752
782
  receiveHandshakeResponseMessage(data, conn) {
753
783
  const parsed = this.parseMsg(data);
754
784
  if (!parsed) {
785
+ conn.telemetry?.span.setStatus({
786
+ code: SpanStatusCode2.ERROR,
787
+ message: "non-transport message"
788
+ });
755
789
  this.protocolError(
756
790
  ProtocolError.HandshakeFailed,
757
791
  "received non-transport message"
@@ -759,6 +793,10 @@ var ClientTransport = class extends Transport {
759
793
  return false;
760
794
  }
761
795
  if (!Value.Check(ControlMessageHandshakeResponseSchema, parsed.payload)) {
796
+ conn.telemetry?.span.setStatus({
797
+ code: SpanStatusCode2.ERROR,
798
+ message: "invalid handshake response"
799
+ });
762
800
  log?.warn(`received invalid handshake resp`, {
763
801
  clientId: this.clientId,
764
802
  connectedTo: parsed.from,
@@ -771,7 +809,11 @@ var ClientTransport = class extends Transport {
771
809
  return false;
772
810
  }
773
811
  if (!parsed.payload.status.ok) {
774
- log?.warn(`received invalid handshake resp`, {
812
+ conn.telemetry?.span.setStatus({
813
+ code: SpanStatusCode2.ERROR,
814
+ message: "handshake rejected"
815
+ });
816
+ log?.warn(`received handshake rejection`, {
775
817
  clientId: this.clientId,
776
818
  connectedTo: parsed.from,
777
819
  fullTransportMessage: parsed
@@ -801,142 +843,94 @@ var ClientTransport = class extends Transport {
801
843
  * @param to The client ID of the node to connect to.
802
844
  */
803
845
  async connect(to) {
804
- return tracing_default.startActiveSpan(
805
- "connect",
806
- {
807
- attributes: {
808
- component: "river",
809
- "span.kind": "client"
810
- },
811
- kind: SpanKind.CLIENT
812
- },
813
- async (span) => {
814
- try {
815
- await this.connectAttempt(to);
816
- } catch (e) {
817
- if (e instanceof Error) {
818
- span.recordException(e);
819
- } else {
820
- span.recordException(coerceErrorString(e));
821
- }
822
- span.setStatus({ code: SpanStatusCode.ERROR });
823
- } finally {
824
- span.end();
825
- }
846
+ const canProceedWithConnection = () => this.state === "open";
847
+ if (!canProceedWithConnection()) {
848
+ log?.info(
849
+ `transport state is no longer open, cancelling attempt to connect to ${to}`,
850
+ { clientId: this.clientId, connectedTo: to }
851
+ );
852
+ return;
853
+ }
854
+ let reconnectPromise = this.inflightConnectionPromises.get(to);
855
+ if (!reconnectPromise) {
856
+ const budgetConsumed = this.retryBudget.getBudgetConsumed(to);
857
+ if (!this.retryBudget.hasBudget(to)) {
858
+ const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
859
+ log?.warn(errMsg, { clientId: this.clientId, connectedTo: to });
860
+ this.protocolError(ProtocolError.RetriesExceeded, errMsg);
861
+ return;
826
862
  }
827
- );
828
- }
829
- async connectAttempt(to, attempt = 0) {
830
- const retry = await tracing_default.startActiveSpan(
831
- "connect",
832
- {
833
- attributes: {
834
- component: "river",
835
- "river.attempt": attempt,
836
- "span.kind": "client"
837
- },
838
- kind: SpanKind.CLIENT
839
- },
840
- async (span) => {
863
+ let sleep = Promise.resolve();
864
+ const backoffMs = this.retryBudget.getBackoffMs(to);
865
+ if (backoffMs > 0) {
866
+ sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
867
+ }
868
+ log?.info(`attempting connection to ${to} (${backoffMs}ms backoff)`, {
869
+ clientId: this.clientId,
870
+ connectedTo: to
871
+ });
872
+ this.retryBudget.consumeBudget(to);
873
+ reconnectPromise = tracing_default.startActiveSpan("connect", async (span) => {
841
874
  try {
842
- const canProceedWithConnection = () => this.state === "open";
875
+ span.addEvent("backoff", { backoffMs });
876
+ await sleep;
843
877
  if (!canProceedWithConnection()) {
844
- log?.info(
845
- `transport state is no longer open, cancelling attempt to connect to ${to}`,
846
- { clientId: this.clientId, connectedTo: to }
847
- );
848
- return false;
878
+ throw new Error("transport state is no longer open");
849
879
  }
850
- let reconnectPromise = this.inflightConnectionPromises.get(to);
851
- if (!reconnectPromise) {
852
- const budgetConsumed = this.retryBudget.getBudgetConsumed(to);
853
- if (!this.retryBudget.hasBudget(to)) {
854
- const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
855
- log?.warn(errMsg, { clientId: this.clientId, connectedTo: to });
856
- this.protocolError(ProtocolError.RetriesExceeded, errMsg);
857
- return false;
858
- }
859
- let sleep = Promise.resolve();
860
- const backoffMs = this.retryBudget.getBackoffMs(to);
861
- if (backoffMs > 0) {
862
- sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
863
- }
864
- log?.info(
865
- `attempting connection to ${to} (${backoffMs}ms backoff)`,
866
- {
867
- clientId: this.clientId,
868
- connectedTo: to
869
- }
870
- );
871
- this.retryBudget.consumeBudget(to);
872
- reconnectPromise = sleep.then(() => {
873
- if (!canProceedWithConnection()) {
874
- throw new Error("transport state is no longer open");
875
- }
876
- }).then(() => this.createNewOutgoingConnection(to)).then((conn) => {
877
- if (!canProceedWithConnection()) {
878
- log?.info(
879
- `transport state is no longer open, closing pre-handshake connection to ${to}`,
880
- {
881
- clientId: this.clientId,
882
- connectedTo: to,
883
- connId: conn.debugId
884
- }
885
- );
886
- conn.close();
887
- throw new Error("transport state is no longer open");
888
- }
889
- return this.sendHandshake(to, conn).then((ok) => {
890
- if (!ok) {
891
- conn.close();
892
- throw new Error("failed to send handshake");
893
- }
894
- return conn;
895
- });
896
- });
897
- this.inflightConnectionPromises.set(to, reconnectPromise);
898
- } else {
880
+ span.addEvent("connecting");
881
+ const conn = await this.createNewOutgoingConnection(to);
882
+ if (!canProceedWithConnection()) {
899
883
  log?.info(
900
- `attempting connection to ${to} (reusing previous attempt)`,
884
+ `transport state is no longer open, closing pre-handshake connection to ${to}`,
901
885
  {
902
886
  clientId: this.clientId,
903
- connectedTo: to
887
+ connectedTo: to,
888
+ connId: conn.id
904
889
  }
905
890
  );
891
+ conn.close();
892
+ throw new Error("transport state is no longer open");
906
893
  }
907
- try {
908
- await reconnectPromise;
909
- } catch (error) {
910
- this.inflightConnectionPromises.delete(to);
911
- const errStr = coerceErrorString(error);
912
- if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {
913
- log?.warn(`connection to ${to} failed (${errStr})`, {
914
- clientId: this.clientId,
915
- connectedTo: to
916
- });
917
- } else {
918
- log?.warn(`connection to ${to} failed (${errStr}), retrying`, {
919
- clientId: this.clientId,
920
- connectedTo: to
921
- });
922
- return true;
923
- }
924
- }
925
- } catch (e) {
926
- if (e instanceof Error) {
927
- span.recordException(e);
928
- } else {
929
- span.recordException(coerceErrorString(e));
894
+ span.addEvent("sending handshake");
895
+ const ok = await this.sendHandshake(to, conn);
896
+ if (!ok) {
897
+ conn.close();
898
+ throw new Error("failed to send handshake");
930
899
  }
931
- span.setStatus({ code: SpanStatusCode.ERROR });
900
+ return conn;
901
+ } catch (err) {
902
+ const errStr = coerceErrorString(err);
903
+ span.recordException(errStr);
904
+ span.setStatus({ code: SpanStatusCode2.ERROR });
905
+ throw err;
932
906
  } finally {
933
907
  span.end();
934
908
  }
935
- return false;
909
+ });
910
+ this.inflightConnectionPromises.set(to, reconnectPromise);
911
+ } else {
912
+ log?.info(`attempting connection to ${to} (reusing previous attempt)`, {
913
+ clientId: this.clientId,
914
+ connectedTo: to
915
+ });
916
+ }
917
+ try {
918
+ await reconnectPromise;
919
+ } catch (error) {
920
+ this.inflightConnectionPromises.delete(to);
921
+ const errStr = coerceErrorString(error);
922
+ if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {
923
+ log?.warn(`connection to ${to} failed (${errStr})`, {
924
+ clientId: this.clientId,
925
+ connectedTo: to
926
+ });
927
+ } else {
928
+ log?.warn(`connection to ${to} failed (${errStr}), retrying`, {
929
+ clientId: this.clientId,
930
+ connectedTo: to
931
+ });
932
+ return this.connect(to);
936
933
  }
937
- );
938
- if (retry) {
939
- return this.connectAttempt(to, attempt + 1);
940
934
  }
941
935
  }
942
936
  deleteSession(session) {
@@ -944,13 +938,11 @@ var ClientTransport = class extends Transport {
944
938
  super.deleteSession(session);
945
939
  }
946
940
  async sendHandshake(to, conn) {
947
- const tracing = { traceparent: "", tracestate: "" };
948
- propagation.inject(context.active(), tracing);
949
941
  let metadata;
950
- if (this.options.handshake) {
951
- metadata = await this.options.handshake.get();
952
- if (!Value.Check(this.options.handshake.schema, metadata)) {
953
- log?.error(`handshake metadata did not match schema`, {
942
+ if (this.handshakeExtensions) {
943
+ metadata = await this.handshakeExtensions.construct();
944
+ if (!Value.Check(this.handshakeExtensions.schema, metadata)) {
945
+ log?.error(`constructed handshake metadata did not match schema`, {
954
946
  clientId: this.clientId,
955
947
  connectedTo: to,
956
948
  tags: ["invariant-violation"]
@@ -959,6 +951,10 @@ var ClientTransport = class extends Transport {
959
951
  ProtocolError.HandshakeFailed,
960
952
  "handshake metadata did not match schema"
961
953
  );
954
+ conn.telemetry?.span.setStatus({
955
+ code: SpanStatusCode2.ERROR,
956
+ message: "handshake meta mismatch"
957
+ });
962
958
  return false;
963
959
  }
964
960
  }
@@ -968,7 +964,7 @@ var ClientTransport = class extends Transport {
968
964
  to,
969
965
  session.id,
970
966
  metadata,
971
- tracing
967
+ getPropagationContext(session.telemetry.ctx)
972
968
  );
973
969
  log?.debug(`sending handshake request to ${to}`, {
974
970
  clientId: this.clientId,
@@ -987,280 +983,240 @@ var ServerTransport = class extends Transport {
987
983
  * The options for this transport.
988
984
  */
989
985
  options;
986
+ /**
987
+ * Optional handshake options for the server.
988
+ */
989
+ handshakeExtensions;
990
+ /**
991
+ * A map of session handshake data for each session.
992
+ */
993
+ sessionHandshakeMetadata;
990
994
  constructor(clientId, providedOptions) {
991
995
  super(clientId, providedOptions);
992
996
  this.options = {
993
997
  ...defaultServerTransportOptions,
994
998
  ...providedOptions
995
999
  };
1000
+ this.sessionHandshakeMetadata = /* @__PURE__ */ new WeakMap();
996
1001
  log?.info(`initiated server transport`, {
997
1002
  clientId: this.clientId,
998
1003
  protocolVersion: PROTOCOL_VERSION
999
1004
  });
1000
1005
  }
1006
+ extendHandshake(options) {
1007
+ this.handshakeExtensions = options;
1008
+ }
1001
1009
  handleConnection(conn) {
1002
- tracing_default.startActiveSpan(
1003
- "handleConnection",
1004
- {
1005
- attributes: {
1006
- component: "river",
1007
- "span.kind": "server"
1008
- },
1009
- kind: SpanKind.SERVER
1010
- },
1011
- (span) => {
1012
- if (this.state !== "open")
1013
- return;
1014
- log?.info(`new incoming connection`, {
1015
- clientId: this.clientId,
1016
- connId: conn.debugId
1010
+ if (this.state !== "open")
1011
+ return;
1012
+ log?.info(`new incoming connection`, {
1013
+ clientId: this.clientId,
1014
+ connId: conn.id
1015
+ });
1016
+ let session = void 0;
1017
+ const client = () => session?.to ?? "unknown";
1018
+ const handshakeTimeout = setTimeout(() => {
1019
+ if (!session) {
1020
+ log?.warn(
1021
+ `connection to ${client()} timed out waiting for handshake, closing`,
1022
+ {
1023
+ clientId: this.clientId,
1024
+ connectedTo: client(),
1025
+ connId: conn.id
1026
+ }
1027
+ );
1028
+ conn.telemetry?.span.setStatus({
1029
+ code: SpanStatusCode2.ERROR,
1030
+ message: "handshake timeout"
1017
1031
  });
1018
- let session = void 0;
1019
- const client = () => session?.to ?? "unknown";
1020
- const handshakeTimeout = setTimeout(() => {
1021
- if (!session) {
1022
- log?.warn(
1023
- `connection to ${client()} timed out waiting for handshake, closing`,
1024
- {
1025
- clientId: this.clientId,
1026
- connectedTo: client(),
1027
- connId: conn.debugId
1028
- }
1029
- );
1030
- span.setStatus({ code: SpanStatusCode.ERROR });
1031
- span.end();
1032
+ conn.close();
1033
+ }
1034
+ }, this.options.sessionDisconnectGraceMs);
1035
+ const buffer = [];
1036
+ let receivedHandshakeMessage = false;
1037
+ const handshakeHandler = (data) => {
1038
+ if (receivedHandshakeMessage) {
1039
+ buffer.push(data);
1040
+ return;
1041
+ }
1042
+ receivedHandshakeMessage = true;
1043
+ clearTimeout(handshakeTimeout);
1044
+ void this.receiveHandshakeRequestMessage(data, conn).then(
1045
+ (maybeSession) => {
1046
+ if (!maybeSession) {
1032
1047
  conn.close();
1033
- }
1034
- }, this.options.sessionDisconnectGraceMs);
1035
- const buffer = [];
1036
- let receivedHandshakeMessage = false;
1037
- const handshakeHandler = (data) => {
1038
- if (receivedHandshakeMessage) {
1039
- buffer.push(data);
1040
1048
  return;
1041
1049
  }
1042
- receivedHandshakeMessage = true;
1043
- clearTimeout(handshakeTimeout);
1044
- void this.receiveHandshakeRequestMessage(data, conn).then(
1045
- (maybeSession) => {
1046
- if (!maybeSession) {
1047
- span.setStatus({ code: SpanStatusCode.ERROR });
1048
- span.end();
1049
- conn.close();
1050
- return;
1051
- }
1052
- session = maybeSession;
1053
- const dataHandler = (data2) => {
1054
- const parsed = this.parseMsg(data2);
1055
- if (!parsed) {
1056
- conn.close();
1057
- return;
1058
- }
1059
- this.handleMsg(parsed);
1060
- };
1061
- conn.removeDataListener(handshakeHandler);
1062
- conn.addDataListener(dataHandler);
1063
- for (const data2 of buffer) {
1064
- dataHandler(data2);
1065
- }
1066
- buffer.length = 0;
1050
+ session = maybeSession;
1051
+ const dataHandler = (data2) => {
1052
+ const parsed = this.parseMsg(data2);
1053
+ if (!parsed) {
1054
+ conn.close();
1055
+ return;
1067
1056
  }
1068
- );
1069
- };
1070
- conn.addDataListener(handshakeHandler);
1071
- conn.addCloseListener(() => {
1072
- if (session) {
1073
- log?.info(`connection to ${client()} disconnected`, {
1074
- clientId: this.clientId,
1075
- connId: conn.debugId
1076
- });
1077
- this.onDisconnect(conn, session);
1057
+ this.handleMsg(parsed);
1058
+ };
1059
+ for (const data2 of buffer) {
1060
+ dataHandler(data2);
1078
1061
  }
1079
- span.setStatus({ code: SpanStatusCode.OK });
1080
- span.end();
1062
+ conn.removeDataListener(handshakeHandler);
1063
+ conn.addDataListener(dataHandler);
1064
+ buffer.length = 0;
1065
+ }
1066
+ );
1067
+ };
1068
+ conn.addDataListener(handshakeHandler);
1069
+ conn.addCloseListener(() => {
1070
+ if (!session)
1071
+ return;
1072
+ log?.info(`connection to ${client()} disconnected`, {
1073
+ clientId: this.clientId,
1074
+ connId: conn.id
1075
+ });
1076
+ this.onDisconnect(conn, session);
1077
+ });
1078
+ conn.addErrorListener((err) => {
1079
+ conn.telemetry?.span.setStatus({
1080
+ code: SpanStatusCode2.ERROR,
1081
+ message: "connection error"
1082
+ });
1083
+ if (!session)
1084
+ return;
1085
+ log?.warn(
1086
+ `connection to ${client()} got an error: ${coerceErrorString(err)}`,
1087
+ { clientId: this.clientId, connId: conn.id }
1088
+ );
1089
+ });
1090
+ }
1091
+ async validateHandshakeMetadata(conn, session, rawMetadata, from) {
1092
+ let parsedMetadata = {};
1093
+ if (this.handshakeExtensions) {
1094
+ if (!Value.Check(this.handshakeExtensions.schema, rawMetadata)) {
1095
+ conn.telemetry?.span.setStatus({
1096
+ code: SpanStatusCode2.ERROR,
1097
+ message: "malformed handshake meta"
1081
1098
  });
1082
- conn.addErrorListener((err) => {
1083
- if (session) {
1084
- log?.warn(
1085
- `connection to ${client()} got an error: ${coerceErrorString(
1086
- err
1087
- )}`,
1088
- { clientId: this.clientId, connId: conn.debugId }
1089
- );
1090
- }
1091
- span.setStatus({ code: SpanStatusCode.ERROR });
1092
- span.end();
1099
+ const reason = "received malformed handshake metadata";
1100
+ const responseMsg = handshakeResponseMessage(this.clientId, from, {
1101
+ ok: false,
1102
+ reason
1103
+ });
1104
+ conn.send(this.codec.toBuffer(responseMsg));
1105
+ log?.warn(`received malformed handshake metadata from ${from}`, {
1106
+ clientId: this.clientId,
1107
+ connId: conn.id
1093
1108
  });
1109
+ this.protocolError(ProtocolError.HandshakeFailed, reason);
1110
+ return false;
1094
1111
  }
1095
- );
1112
+ parsedMetadata = await this.handshakeExtensions.validate(
1113
+ rawMetadata,
1114
+ session
1115
+ );
1116
+ if (parsedMetadata === false) {
1117
+ const reason = "rejected by handshake handler";
1118
+ conn.telemetry?.span.setStatus({
1119
+ code: SpanStatusCode2.ERROR,
1120
+ message: reason
1121
+ });
1122
+ const responseMsg = handshakeResponseMessage(this.clientId, from, {
1123
+ ok: false,
1124
+ reason
1125
+ });
1126
+ conn.send(this.codec.toBuffer(responseMsg));
1127
+ log?.warn(`rejected handshake from ${from}`, {
1128
+ clientId: this.clientId,
1129
+ connId: conn.id
1130
+ });
1131
+ this.protocolError(ProtocolError.HandshakeFailed, reason);
1132
+ return false;
1133
+ }
1134
+ }
1135
+ return parsedMetadata;
1096
1136
  }
1097
1137
  async receiveHandshakeRequestMessage(data, conn) {
1098
1138
  const parsed = this.parseMsg(data);
1099
1139
  if (!parsed) {
1140
+ conn.telemetry?.span.setStatus({
1141
+ code: SpanStatusCode2.ERROR,
1142
+ message: "non-transport message"
1143
+ });
1100
1144
  this.protocolError(
1101
1145
  ProtocolError.HandshakeFailed,
1102
1146
  "received non-transport message"
1103
1147
  );
1104
1148
  return false;
1105
1149
  }
1106
- let activeContext = context.active();
1107
- if (parsed.tracing) {
1108
- activeContext = propagation.extract(activeContext, parsed.tracing);
1150
+ if (!Value.Check(ControlMessageHandshakeRequestSchema, parsed.payload)) {
1151
+ conn.telemetry?.span.setStatus({
1152
+ code: SpanStatusCode2.ERROR,
1153
+ message: "invalid handshake request"
1154
+ });
1155
+ const reason = "received invalid handshake msg";
1156
+ const responseMsg2 = handshakeResponseMessage(this.clientId, parsed.from, {
1157
+ ok: false,
1158
+ reason
1159
+ });
1160
+ conn.send(this.codec.toBuffer(responseMsg2));
1161
+ const logData = { ...parsed.payload ?? {}, metadata: "redacted" };
1162
+ log?.warn(reason, {
1163
+ clientId: this.clientId,
1164
+ connId: conn.id,
1165
+ partialTransportMessage: { ...parsed, payload: logData }
1166
+ });
1167
+ this.protocolError(
1168
+ ProtocolError.HandshakeFailed,
1169
+ "invalid handshake request"
1170
+ );
1171
+ return false;
1109
1172
  }
1110
- return tracing_default.startActiveSpan(
1111
- "receiveHandshakeRequestMessage",
1112
- {
1113
- attributes: {
1114
- component: "river",
1115
- "span.kind": "server"
1116
- },
1117
- kind: SpanKind.SERVER
1118
- },
1119
- activeContext,
1120
- async (span) => {
1121
- if (!Value.Check(ControlMessageHandshakeRequestSchema, parsed.payload)) {
1122
- const reason = "received invalid handshake msg";
1123
- const responseMsg2 = handshakeResponseMessage(
1124
- this.clientId,
1125
- parsed.from,
1126
- {
1127
- ok: false,
1128
- reason
1129
- }
1130
- );
1131
- conn.send(this.codec.toBuffer(responseMsg2));
1132
- const logData = typeof parsed.payload === "object" ? {
1133
- ...parsed,
1134
- payload: { ...parsed.payload, metadata: "redacted" }
1135
- } : { ...parsed };
1136
- log?.warn(`${reason}: ${JSON.stringify(logData)}`, {
1137
- clientId: this.clientId,
1138
- connId: conn.debugId
1139
- });
1140
- this.protocolError(
1141
- ProtocolError.HandshakeFailed,
1142
- "invalid handshake request"
1143
- );
1144
- span.setStatus({ code: SpanStatusCode.ERROR });
1145
- span.end();
1146
- return false;
1147
- }
1148
- const gotVersion = parsed.payload.protocolVersion;
1149
- if (gotVersion !== PROTOCOL_VERSION) {
1150
- const reason = `incorrect version (got: ${gotVersion} wanted ${PROTOCOL_VERSION})`;
1151
- const responseMsg2 = handshakeResponseMessage(
1152
- this.clientId,
1153
- parsed.from,
1154
- {
1155
- ok: false,
1156
- reason
1157
- }
1158
- );
1159
- conn.send(this.codec.toBuffer(responseMsg2));
1160
- log?.warn(
1161
- `received handshake msg with incompatible protocol version (got: ${gotVersion}, expected: ${PROTOCOL_VERSION})`,
1162
- { clientId: this.clientId, connId: conn.debugId }
1163
- );
1164
- this.protocolError(ProtocolError.HandshakeFailed, reason);
1165
- span.setStatus({ code: SpanStatusCode.ERROR });
1166
- span.end();
1167
- return false;
1168
- }
1169
- const { session, isReconnect } = this.getOrCreateSession(
1170
- parsed.from,
1171
- conn,
1172
- parsed.payload.sessionId
1173
- );
1174
- let handshakeMetadata;
1175
- if (this.options.handshake) {
1176
- if (!Value.Check(
1177
- this.options.handshake.requestSchema,
1178
- parsed.payload.metadata
1179
- )) {
1180
- const reason = "received malformed handshake metadata";
1181
- const responseMsg2 = handshakeResponseMessage(
1182
- this.clientId,
1183
- parsed.from,
1184
- { ok: false, reason }
1185
- );
1186
- conn.send(this.codec.toBuffer(responseMsg2));
1187
- log?.warn(
1188
- `received malformed handshake metadata from ${parsed.from}`,
1189
- {
1190
- clientId: this.clientId,
1191
- connId: conn.debugId
1192
- }
1193
- );
1194
- this.protocolError(ProtocolError.HandshakeFailed, reason);
1195
- this.deleteSession(session);
1196
- span.setStatus({ code: SpanStatusCode.ERROR });
1197
- span.end();
1198
- return false;
1199
- }
1200
- const parsedMetadata = await this.options.handshake.parse(
1201
- parsed.payload.metadata,
1202
- session,
1203
- isReconnect
1204
- );
1205
- if (parsedMetadata === false) {
1206
- const reason = "rejected by server";
1207
- const responseMsg2 = handshakeResponseMessage(
1208
- this.clientId,
1209
- parsed.from,
1210
- { ok: false, reason }
1211
- );
1212
- conn.send(this.codec.toBuffer(responseMsg2));
1213
- log?.warn(`rejected handshake from ${parsed.from}`, {
1214
- clientId: this.clientId,
1215
- connId: conn.debugId
1216
- });
1217
- this.protocolError(ProtocolError.HandshakeFailed, reason);
1218
- this.deleteSession(session);
1219
- span.setStatus({ code: SpanStatusCode.ERROR });
1220
- span.end();
1221
- return false;
1222
- }
1223
- if (!Value.Check(this.options.handshake.parsedSchema, parsedMetadata)) {
1224
- const reason = "failed to parse handshake metadata";
1225
- const responseMsg2 = handshakeResponseMessage(
1226
- this.clientId,
1227
- parsed.from,
1228
- { ok: false, reason }
1229
- );
1230
- conn.send(this.codec.toBuffer(responseMsg2));
1231
- log?.error(`failed to parse handshake metadata`, {
1232
- clientId: this.clientId,
1233
- connId: conn.debugId,
1234
- tags: ["invariant-violation"]
1235
- });
1236
- this.protocolError(ProtocolError.HandshakeFailed, reason);
1237
- this.deleteSession(session);
1238
- span.setStatus({ code: SpanStatusCode.ERROR });
1239
- span.end();
1240
- return false;
1241
- }
1242
- handshakeMetadata = parsedMetadata;
1243
- }
1244
- handshakeMetadata ??= {};
1245
- session.metadata = handshakeMetadata;
1246
- log?.debug(
1247
- `handshake from ${parsed.from} ok, responding with handshake success`,
1248
- { clientId: this.clientId, connId: conn.debugId }
1249
- );
1250
- const responseMsg = handshakeResponseMessage(
1251
- this.clientId,
1252
- parsed.from,
1253
- {
1254
- ok: true,
1255
- sessionId: session.id
1256
- }
1257
- );
1258
- conn.send(this.codec.toBuffer(responseMsg));
1259
- this.onConnect(conn, parsed.from, session, isReconnect);
1260
- span.end();
1261
- return session;
1262
- }
1173
+ const gotVersion = parsed.payload.protocolVersion;
1174
+ if (gotVersion !== PROTOCOL_VERSION) {
1175
+ conn.telemetry?.span.setStatus({
1176
+ code: SpanStatusCode2.ERROR,
1177
+ message: "incorrect protocol version"
1178
+ });
1179
+ const reason = `incorrect version (got: ${gotVersion} wanted ${PROTOCOL_VERSION})`;
1180
+ const responseMsg2 = handshakeResponseMessage(this.clientId, parsed.from, {
1181
+ ok: false,
1182
+ reason
1183
+ });
1184
+ conn.send(this.codec.toBuffer(responseMsg2));
1185
+ log?.warn(
1186
+ `received handshake msg with incompatible protocol version (got: ${gotVersion}, expected: ${PROTOCOL_VERSION})`,
1187
+ { clientId: this.clientId, connId: conn.id }
1188
+ );
1189
+ this.protocolError(ProtocolError.HandshakeFailed, reason);
1190
+ return false;
1191
+ }
1192
+ const oldSession = this.sessions.get(parsed.from);
1193
+ const parsedMetadata = await this.validateHandshakeMetadata(
1194
+ conn,
1195
+ oldSession,
1196
+ parsed.payload.metadata,
1197
+ parsed.from
1263
1198
  );
1199
+ if (parsedMetadata === false) {
1200
+ return false;
1201
+ }
1202
+ const { session, isReconnect } = this.getOrCreateSession(
1203
+ parsed.from,
1204
+ conn,
1205
+ parsed.payload.sessionId,
1206
+ parsed.tracing
1207
+ );
1208
+ this.sessionHandshakeMetadata.set(session, parsedMetadata);
1209
+ log?.debug(
1210
+ `handshake from ${parsed.from} ok, responding with handshake success`,
1211
+ { clientId: this.clientId, connId: conn.id }
1212
+ );
1213
+ const responseMsg = handshakeResponseMessage(this.clientId, parsed.from, {
1214
+ ok: true,
1215
+ sessionId: session.id
1216
+ });
1217
+ conn.send(this.codec.toBuffer(responseMsg));
1218
+ this.onConnect(conn, parsed.from, session, isReconnect);
1219
+ return session;
1264
1220
  }
1265
1221
  };
1266
1222
 
@@ -1273,4 +1229,4 @@ export {
1273
1229
  ClientTransport,
1274
1230
  ServerTransport
1275
1231
  };
1276
- //# sourceMappingURL=chunk-JMXO5L2X.js.map
1232
+ //# sourceMappingURL=chunk-MJUFKPBT.js.map