@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
@@ -33,7 +33,6 @@ module.exports = __toCommonJS(transport_exports);
33
33
 
34
34
  // transport/transport.ts
35
35
  var import_value = require("@sinclair/typebox/value");
36
- var import_api2 = require("@opentelemetry/api");
37
36
 
38
37
  // transport/message.ts
39
38
  var import_typebox = require("@sinclair/typebox");
@@ -167,17 +166,71 @@ var EventDispatcher = class {
167
166
 
168
167
  // transport/session.ts
169
168
  var import_nanoid2 = require("nanoid");
169
+
170
+ // tracing/index.ts
171
+ var import_api = require("@opentelemetry/api");
172
+
173
+ // package.json
174
+ var version = "0.23.0";
175
+
176
+ // tracing/index.ts
177
+ function getPropagationContext(ctx) {
178
+ const tracing = {
179
+ traceparent: "",
180
+ tracestate: ""
181
+ };
182
+ import_api.propagation.inject(ctx, tracing);
183
+ return tracing;
184
+ }
185
+ function createSessionTelemetryInfo(session, propagationCtx) {
186
+ const ctx = propagationCtx ? import_api.propagation.extract(import_api.context.active(), propagationCtx) : import_api.context.active();
187
+ const span = tracer.startSpan(
188
+ `session ${session.id}`,
189
+ {
190
+ attributes: {
191
+ component: "river",
192
+ "river.session.id": session.id,
193
+ "river.session.to": session.to,
194
+ "river.session.from": session.from
195
+ }
196
+ },
197
+ ctx
198
+ );
199
+ return { span, ctx };
200
+ }
201
+ function createConnectionTelemetryInfo(connection, sessionSpan) {
202
+ const ctx = import_api.trace.setSpan(import_api.context.active(), sessionSpan);
203
+ const span = tracer.startSpan(
204
+ `connection ${connection.id}`,
205
+ {
206
+ attributes: {
207
+ component: "river",
208
+ "river.connection.id": connection.id
209
+ },
210
+ links: [{ context: sessionSpan.spanContext() }]
211
+ },
212
+ ctx
213
+ );
214
+ return { span, ctx };
215
+ }
216
+ var tracer = import_api.trace.getTracer("river", version);
217
+ var tracing_default = tracer;
218
+
219
+ // transport/session.ts
220
+ var import_api2 = require("@opentelemetry/api");
170
221
  var nanoid2 = (0, import_nanoid2.customAlphabet)("1234567890abcdefghijklmnopqrstuvxyz", 6);
171
222
  var unsafeId = () => nanoid2();
172
223
  var Connection = class {
173
- debugId;
224
+ id;
225
+ telemetry;
174
226
  constructor() {
175
- this.debugId = `conn-${unsafeId()}`;
227
+ this.id = `conn-${nanoid2(12)}`;
176
228
  }
177
229
  };
178
230
  var Session = class {
179
231
  codec;
180
232
  options;
233
+ telemetry;
181
234
  /**
182
235
  * The buffer of messages that have been sent but not yet acknowledged.
183
236
  */
@@ -197,12 +250,6 @@ var Session = class {
197
250
  * for this session.
198
251
  */
199
252
  advertisedSessionId;
200
- /**
201
- * The metadata for this session, as parsed from the handshake.
202
- *
203
- * Will only ever be populated on the server side.
204
- */
205
- metadata;
206
253
  /**
207
254
  * Number of messages we've sent along this session (excluding handshake and acks)
208
255
  */
@@ -224,7 +271,7 @@ var Session = class {
224
271
  * The interval for sending heartbeats.
225
272
  */
226
273
  heartbeat;
227
- constructor(conn, from, to, options) {
274
+ constructor(conn, from, to, options, propagationCtx) {
228
275
  this.id = `session-${nanoid2(12)}`;
229
276
  this.options = options;
230
277
  this.from = from;
@@ -236,13 +283,14 @@ var Session = class {
236
283
  () => this.sendHeartbeat(),
237
284
  options.heartbeatIntervalMs
238
285
  );
286
+ this.telemetry = createSessionTelemetryInfo(this, propagationCtx);
239
287
  }
240
288
  get loggingMetadata() {
241
289
  return {
242
290
  clientId: this.from,
243
291
  connectedTo: this.to,
244
292
  sessionId: this.id,
245
- connId: this.connection?.debugId
293
+ connId: this.connection?.id
246
294
  };
247
295
  }
248
296
  /**
@@ -287,6 +335,7 @@ var Session = class {
287
335
  `closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
288
336
  this.loggingMetadata
289
337
  );
338
+ this.telemetry.span.addEvent("closing connection due to inactivity");
290
339
  this.closeStaleConnection();
291
340
  }
292
341
  return;
@@ -308,21 +357,25 @@ var Session = class {
308
357
  sendBufferedMessages(conn) {
309
358
  log?.info(`resending ${this.sendBuffer.length} buffered messages`, {
310
359
  ...this.loggingMetadata,
311
- connId: conn.debugId
360
+ connId: conn.id
312
361
  });
313
362
  for (const msg of this.sendBuffer) {
314
363
  log?.debug(`resending msg`, {
315
364
  ...this.loggingMetadata,
316
365
  fullTransportMessage: msg,
317
- connId: conn.debugId
366
+ connId: conn.id
318
367
  });
319
368
  const ok = conn.send(this.codec.toBuffer(msg));
320
369
  if (!ok) {
321
370
  const errMsg = `failed to send buffered message to ${this.to} (sus, this is a fresh connection)`;
371
+ conn.telemetry?.span.setStatus({
372
+ code: import_api2.SpanStatusCode.ERROR,
373
+ message: errMsg
374
+ });
322
375
  log?.error(errMsg, {
323
376
  ...this.loggingMetadata,
324
377
  fullTransportMessage: msg,
325
- connId: conn.debugId,
378
+ connId: conn.id,
326
379
  tags: ["invariant-violation"]
327
380
  });
328
381
  conn.close();
@@ -405,11 +458,6 @@ var Session = class {
405
458
  }
406
459
  };
407
460
 
408
- // tracing/index.ts
409
- var import_api = require("@opentelemetry/api");
410
- var tracer = import_api.trace.getTracer("river");
411
- var tracing_default = tracer;
412
-
413
461
  // util/stringify.ts
414
462
  function coerceErrorString(err) {
415
463
  if (err instanceof Error) {
@@ -542,6 +590,7 @@ var NaiveJsonCodec = {
542
590
  };
543
591
 
544
592
  // transport/transport.ts
593
+ var import_api3 = require("@opentelemetry/api");
545
594
  var defaultTransportOptions = {
546
595
  heartbeatIntervalMs: 1e3,
547
596
  heartbeatsUntilDead: 2,
@@ -620,17 +669,22 @@ var Transport = class {
620
669
  status: "connect",
621
670
  conn
622
671
  });
672
+ conn.telemetry = createConnectionTelemetryInfo(
673
+ conn,
674
+ session.telemetry.span
675
+ );
623
676
  if (isReconnect) {
624
677
  session.replaceWithNewConnection(conn);
625
678
  log?.info(`reconnected to ${connectedTo}`, session.loggingMetadata);
626
679
  }
627
680
  }
628
- createSession(to, conn) {
681
+ createSession(to, conn, propagationCtx) {
629
682
  const session = new Session(
630
683
  conn,
631
684
  this.clientId,
632
685
  to,
633
- this.options
686
+ this.options,
687
+ propagationCtx
634
688
  );
635
689
  this.sessions.set(session.to, session);
636
690
  this.eventDispatcher.dispatchEvent("sessionStatus", {
@@ -639,11 +693,11 @@ var Transport = class {
639
693
  });
640
694
  return session;
641
695
  }
642
- getOrCreateSession(to, conn, sessionId) {
696
+ getOrCreateSession(to, conn, sessionId, propagationCtx) {
643
697
  let session = this.sessions.get(to);
644
698
  let isReconnect = session !== void 0;
645
699
  if (session?.advertisedSessionId !== void 0 && sessionId !== void 0 && session.advertisedSessionId !== sessionId) {
646
- log?.warn(
700
+ log?.info(
647
701
  `session for ${to} already exists but has a different session id (expected: ${session.advertisedSessionId}, got: ${sessionId}), creating a new one`,
648
702
  session.loggingMetadata
649
703
  );
@@ -652,7 +706,7 @@ var Transport = class {
652
706
  session = void 0;
653
707
  }
654
708
  if (!session) {
655
- session = this.createSession(to, conn);
709
+ session = this.createSession(to, conn, propagationCtx);
656
710
  log?.info(
657
711
  `no session for ${to}, created a new one`,
658
712
  session.loggingMetadata
@@ -665,6 +719,7 @@ var Transport = class {
665
719
  }
666
720
  deleteSession(session) {
667
721
  session.close();
722
+ session.telemetry.span.end();
668
723
  this.sessions.delete(session.to);
669
724
  log?.info(
670
725
  `session ${session.id} disconnect from ${session.to}`,
@@ -681,12 +736,16 @@ var Transport = class {
681
736
  * @param connectedTo The peer we are connected to.
682
737
  */
683
738
  onDisconnect(conn, session) {
739
+ conn.telemetry?.span.end();
684
740
  this.eventDispatcher.dispatchEvent("connectionStatus", {
685
741
  status: "disconnect",
686
742
  conn
687
743
  });
688
744
  session.connection = void 0;
689
- session.beginGrace(() => this.deleteSession(session));
745
+ session.beginGrace(() => {
746
+ session.telemetry.span.addEvent("session grace period expired");
747
+ this.deleteSession(session);
748
+ });
690
749
  }
691
750
  /**
692
751
  * Parses a message from a Uint8Array into a {@link OpaqueTransportMessage}.
@@ -746,6 +805,10 @@ var Transport = class {
746
805
  tags: ["invariant-violation"]
747
806
  });
748
807
  this.protocolError(ProtocolError.MessageOrderingViolated, errMsg);
808
+ session.telemetry.span.setStatus({
809
+ code: import_api3.SpanStatusCode.ERROR,
810
+ message: "message order violated"
811
+ });
749
812
  session.close();
750
813
  }
751
814
  return;
@@ -856,6 +919,10 @@ var ClientTransport = class extends Transport {
856
919
  * tests or a special case where you don't want to reconnect.
857
920
  */
858
921
  reconnectOnConnectionDrop = true;
922
+ /**
923
+ * Optional handshake options for this client.
924
+ */
925
+ handshakeExtensions;
859
926
  constructor(clientId, providedOptions) {
860
927
  super(clientId, providedOptions);
861
928
  this.options = {
@@ -865,6 +932,9 @@ var ClientTransport = class extends Transport {
865
932
  this.inflightConnectionPromises = /* @__PURE__ */ new Map();
866
933
  this.retryBudget = new LeakyBucketRateLimit(this.options);
867
934
  }
935
+ extendHandshake(options) {
936
+ this.handshakeExtensions = options;
937
+ }
868
938
  handleConnection(conn, to) {
869
939
  if (this.state !== "open")
870
940
  return;
@@ -873,7 +943,7 @@ var ClientTransport = class extends Transport {
873
943
  if (!session) {
874
944
  log?.warn(
875
945
  `connection to ${to} timed out waiting for handshake, closing`,
876
- { clientId: this.clientId, connectedTo: to, connId: conn.debugId }
946
+ { clientId: this.clientId, connectedTo: to, connId: conn.id }
877
947
  );
878
948
  conn.close();
879
949
  }
@@ -891,6 +961,10 @@ var ClientTransport = class extends Transport {
891
961
  conn.addDataListener((data2) => {
892
962
  const parsed = this.parseMsg(data2);
893
963
  if (!parsed) {
964
+ conn.telemetry?.span.setStatus({
965
+ code: import_api3.SpanStatusCode.ERROR,
966
+ message: "message parse failure"
967
+ });
894
968
  conn.close();
895
969
  return;
896
970
  }
@@ -913,6 +987,10 @@ var ClientTransport = class extends Transport {
913
987
  }
914
988
  });
915
989
  conn.addErrorListener((err) => {
990
+ conn.telemetry?.span.setStatus({
991
+ code: import_api3.SpanStatusCode.ERROR,
992
+ message: "connection error"
993
+ });
916
994
  log?.warn(`error in connection to ${to}: ${coerceErrorString(err)}`, {
917
995
  ...session?.loggingMetadata,
918
996
  clientId: this.clientId,
@@ -923,6 +1001,10 @@ var ClientTransport = class extends Transport {
923
1001
  receiveHandshakeResponseMessage(data, conn) {
924
1002
  const parsed = this.parseMsg(data);
925
1003
  if (!parsed) {
1004
+ conn.telemetry?.span.setStatus({
1005
+ code: import_api3.SpanStatusCode.ERROR,
1006
+ message: "non-transport message"
1007
+ });
926
1008
  this.protocolError(
927
1009
  ProtocolError.HandshakeFailed,
928
1010
  "received non-transport message"
@@ -930,6 +1012,10 @@ var ClientTransport = class extends Transport {
930
1012
  return false;
931
1013
  }
932
1014
  if (!import_value.Value.Check(ControlMessageHandshakeResponseSchema, parsed.payload)) {
1015
+ conn.telemetry?.span.setStatus({
1016
+ code: import_api3.SpanStatusCode.ERROR,
1017
+ message: "invalid handshake response"
1018
+ });
933
1019
  log?.warn(`received invalid handshake resp`, {
934
1020
  clientId: this.clientId,
935
1021
  connectedTo: parsed.from,
@@ -942,7 +1028,11 @@ var ClientTransport = class extends Transport {
942
1028
  return false;
943
1029
  }
944
1030
  if (!parsed.payload.status.ok) {
945
- log?.warn(`received invalid handshake resp`, {
1031
+ conn.telemetry?.span.setStatus({
1032
+ code: import_api3.SpanStatusCode.ERROR,
1033
+ message: "handshake rejected"
1034
+ });
1035
+ log?.warn(`received handshake rejection`, {
946
1036
  clientId: this.clientId,
947
1037
  connectedTo: parsed.from,
948
1038
  fullTransportMessage: parsed
@@ -972,142 +1062,94 @@ var ClientTransport = class extends Transport {
972
1062
  * @param to The client ID of the node to connect to.
973
1063
  */
974
1064
  async connect(to) {
975
- return tracing_default.startActiveSpan(
976
- "connect",
977
- {
978
- attributes: {
979
- component: "river",
980
- "span.kind": "client"
981
- },
982
- kind: import_api2.SpanKind.CLIENT
983
- },
984
- async (span) => {
985
- try {
986
- await this.connectAttempt(to);
987
- } catch (e) {
988
- if (e instanceof Error) {
989
- span.recordException(e);
990
- } else {
991
- span.recordException(coerceErrorString(e));
992
- }
993
- span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
994
- } finally {
995
- span.end();
996
- }
1065
+ const canProceedWithConnection = () => this.state === "open";
1066
+ if (!canProceedWithConnection()) {
1067
+ log?.info(
1068
+ `transport state is no longer open, cancelling attempt to connect to ${to}`,
1069
+ { clientId: this.clientId, connectedTo: to }
1070
+ );
1071
+ return;
1072
+ }
1073
+ let reconnectPromise = this.inflightConnectionPromises.get(to);
1074
+ if (!reconnectPromise) {
1075
+ const budgetConsumed = this.retryBudget.getBudgetConsumed(to);
1076
+ if (!this.retryBudget.hasBudget(to)) {
1077
+ const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
1078
+ log?.warn(errMsg, { clientId: this.clientId, connectedTo: to });
1079
+ this.protocolError(ProtocolError.RetriesExceeded, errMsg);
1080
+ return;
997
1081
  }
998
- );
999
- }
1000
- async connectAttempt(to, attempt = 0) {
1001
- const retry = await tracing_default.startActiveSpan(
1002
- "connect",
1003
- {
1004
- attributes: {
1005
- component: "river",
1006
- "river.attempt": attempt,
1007
- "span.kind": "client"
1008
- },
1009
- kind: import_api2.SpanKind.CLIENT
1010
- },
1011
- async (span) => {
1082
+ let sleep = Promise.resolve();
1083
+ const backoffMs = this.retryBudget.getBackoffMs(to);
1084
+ if (backoffMs > 0) {
1085
+ sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
1086
+ }
1087
+ log?.info(`attempting connection to ${to} (${backoffMs}ms backoff)`, {
1088
+ clientId: this.clientId,
1089
+ connectedTo: to
1090
+ });
1091
+ this.retryBudget.consumeBudget(to);
1092
+ reconnectPromise = tracing_default.startActiveSpan("connect", async (span) => {
1012
1093
  try {
1013
- const canProceedWithConnection = () => this.state === "open";
1094
+ span.addEvent("backoff", { backoffMs });
1095
+ await sleep;
1014
1096
  if (!canProceedWithConnection()) {
1015
- log?.info(
1016
- `transport state is no longer open, cancelling attempt to connect to ${to}`,
1017
- { clientId: this.clientId, connectedTo: to }
1018
- );
1019
- return false;
1097
+ throw new Error("transport state is no longer open");
1020
1098
  }
1021
- let reconnectPromise = this.inflightConnectionPromises.get(to);
1022
- if (!reconnectPromise) {
1023
- const budgetConsumed = this.retryBudget.getBudgetConsumed(to);
1024
- if (!this.retryBudget.hasBudget(to)) {
1025
- const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
1026
- log?.warn(errMsg, { clientId: this.clientId, connectedTo: to });
1027
- this.protocolError(ProtocolError.RetriesExceeded, errMsg);
1028
- return false;
1029
- }
1030
- let sleep = Promise.resolve();
1031
- const backoffMs = this.retryBudget.getBackoffMs(to);
1032
- if (backoffMs > 0) {
1033
- sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
1034
- }
1035
- log?.info(
1036
- `attempting connection to ${to} (${backoffMs}ms backoff)`,
1037
- {
1038
- clientId: this.clientId,
1039
- connectedTo: to
1040
- }
1041
- );
1042
- this.retryBudget.consumeBudget(to);
1043
- reconnectPromise = sleep.then(() => {
1044
- if (!canProceedWithConnection()) {
1045
- throw new Error("transport state is no longer open");
1046
- }
1047
- }).then(() => this.createNewOutgoingConnection(to)).then((conn) => {
1048
- if (!canProceedWithConnection()) {
1049
- log?.info(
1050
- `transport state is no longer open, closing pre-handshake connection to ${to}`,
1051
- {
1052
- clientId: this.clientId,
1053
- connectedTo: to,
1054
- connId: conn.debugId
1055
- }
1056
- );
1057
- conn.close();
1058
- throw new Error("transport state is no longer open");
1059
- }
1060
- return this.sendHandshake(to, conn).then((ok) => {
1061
- if (!ok) {
1062
- conn.close();
1063
- throw new Error("failed to send handshake");
1064
- }
1065
- return conn;
1066
- });
1067
- });
1068
- this.inflightConnectionPromises.set(to, reconnectPromise);
1069
- } else {
1099
+ span.addEvent("connecting");
1100
+ const conn = await this.createNewOutgoingConnection(to);
1101
+ if (!canProceedWithConnection()) {
1070
1102
  log?.info(
1071
- `attempting connection to ${to} (reusing previous attempt)`,
1103
+ `transport state is no longer open, closing pre-handshake connection to ${to}`,
1072
1104
  {
1073
1105
  clientId: this.clientId,
1074
- connectedTo: to
1106
+ connectedTo: to,
1107
+ connId: conn.id
1075
1108
  }
1076
1109
  );
1110
+ conn.close();
1111
+ throw new Error("transport state is no longer open");
1077
1112
  }
1078
- try {
1079
- await reconnectPromise;
1080
- } catch (error) {
1081
- this.inflightConnectionPromises.delete(to);
1082
- const errStr = coerceErrorString(error);
1083
- if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {
1084
- log?.warn(`connection to ${to} failed (${errStr})`, {
1085
- clientId: this.clientId,
1086
- connectedTo: to
1087
- });
1088
- } else {
1089
- log?.warn(`connection to ${to} failed (${errStr}), retrying`, {
1090
- clientId: this.clientId,
1091
- connectedTo: to
1092
- });
1093
- return true;
1094
- }
1095
- }
1096
- } catch (e) {
1097
- if (e instanceof Error) {
1098
- span.recordException(e);
1099
- } else {
1100
- span.recordException(coerceErrorString(e));
1113
+ span.addEvent("sending handshake");
1114
+ const ok = await this.sendHandshake(to, conn);
1115
+ if (!ok) {
1116
+ conn.close();
1117
+ throw new Error("failed to send handshake");
1101
1118
  }
1102
- span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
1119
+ return conn;
1120
+ } catch (err) {
1121
+ const errStr = coerceErrorString(err);
1122
+ span.recordException(errStr);
1123
+ span.setStatus({ code: import_api3.SpanStatusCode.ERROR });
1124
+ throw err;
1103
1125
  } finally {
1104
1126
  span.end();
1105
1127
  }
1106
- return false;
1128
+ });
1129
+ this.inflightConnectionPromises.set(to, reconnectPromise);
1130
+ } else {
1131
+ log?.info(`attempting connection to ${to} (reusing previous attempt)`, {
1132
+ clientId: this.clientId,
1133
+ connectedTo: to
1134
+ });
1135
+ }
1136
+ try {
1137
+ await reconnectPromise;
1138
+ } catch (error) {
1139
+ this.inflightConnectionPromises.delete(to);
1140
+ const errStr = coerceErrorString(error);
1141
+ if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {
1142
+ log?.warn(`connection to ${to} failed (${errStr})`, {
1143
+ clientId: this.clientId,
1144
+ connectedTo: to
1145
+ });
1146
+ } else {
1147
+ log?.warn(`connection to ${to} failed (${errStr}), retrying`, {
1148
+ clientId: this.clientId,
1149
+ connectedTo: to
1150
+ });
1151
+ return this.connect(to);
1107
1152
  }
1108
- );
1109
- if (retry) {
1110
- return this.connectAttempt(to, attempt + 1);
1111
1153
  }
1112
1154
  }
1113
1155
  deleteSession(session) {
@@ -1115,13 +1157,11 @@ var ClientTransport = class extends Transport {
1115
1157
  super.deleteSession(session);
1116
1158
  }
1117
1159
  async sendHandshake(to, conn) {
1118
- const tracing = { traceparent: "", tracestate: "" };
1119
- import_api2.propagation.inject(import_api2.context.active(), tracing);
1120
1160
  let metadata;
1121
- if (this.options.handshake) {
1122
- metadata = await this.options.handshake.get();
1123
- if (!import_value.Value.Check(this.options.handshake.schema, metadata)) {
1124
- log?.error(`handshake metadata did not match schema`, {
1161
+ if (this.handshakeExtensions) {
1162
+ metadata = await this.handshakeExtensions.construct();
1163
+ if (!import_value.Value.Check(this.handshakeExtensions.schema, metadata)) {
1164
+ log?.error(`constructed handshake metadata did not match schema`, {
1125
1165
  clientId: this.clientId,
1126
1166
  connectedTo: to,
1127
1167
  tags: ["invariant-violation"]
@@ -1130,6 +1170,10 @@ var ClientTransport = class extends Transport {
1130
1170
  ProtocolError.HandshakeFailed,
1131
1171
  "handshake metadata did not match schema"
1132
1172
  );
1173
+ conn.telemetry?.span.setStatus({
1174
+ code: import_api3.SpanStatusCode.ERROR,
1175
+ message: "handshake meta mismatch"
1176
+ });
1133
1177
  return false;
1134
1178
  }
1135
1179
  }
@@ -1139,7 +1183,7 @@ var ClientTransport = class extends Transport {
1139
1183
  to,
1140
1184
  session.id,
1141
1185
  metadata,
1142
- tracing
1186
+ getPropagationContext(session.telemetry.ctx)
1143
1187
  );
1144
1188
  log?.debug(`sending handshake request to ${to}`, {
1145
1189
  clientId: this.clientId,
@@ -1158,280 +1202,240 @@ var ServerTransport = class extends Transport {
1158
1202
  * The options for this transport.
1159
1203
  */
1160
1204
  options;
1205
+ /**
1206
+ * Optional handshake options for the server.
1207
+ */
1208
+ handshakeExtensions;
1209
+ /**
1210
+ * A map of session handshake data for each session.
1211
+ */
1212
+ sessionHandshakeMetadata;
1161
1213
  constructor(clientId, providedOptions) {
1162
1214
  super(clientId, providedOptions);
1163
1215
  this.options = {
1164
1216
  ...defaultServerTransportOptions,
1165
1217
  ...providedOptions
1166
1218
  };
1219
+ this.sessionHandshakeMetadata = /* @__PURE__ */ new WeakMap();
1167
1220
  log?.info(`initiated server transport`, {
1168
1221
  clientId: this.clientId,
1169
1222
  protocolVersion: PROTOCOL_VERSION
1170
1223
  });
1171
1224
  }
1225
+ extendHandshake(options) {
1226
+ this.handshakeExtensions = options;
1227
+ }
1172
1228
  handleConnection(conn) {
1173
- tracing_default.startActiveSpan(
1174
- "handleConnection",
1175
- {
1176
- attributes: {
1177
- component: "river",
1178
- "span.kind": "server"
1179
- },
1180
- kind: import_api2.SpanKind.SERVER
1181
- },
1182
- (span) => {
1183
- if (this.state !== "open")
1184
- return;
1185
- log?.info(`new incoming connection`, {
1186
- clientId: this.clientId,
1187
- connId: conn.debugId
1229
+ if (this.state !== "open")
1230
+ return;
1231
+ log?.info(`new incoming connection`, {
1232
+ clientId: this.clientId,
1233
+ connId: conn.id
1234
+ });
1235
+ let session = void 0;
1236
+ const client = () => session?.to ?? "unknown";
1237
+ const handshakeTimeout = setTimeout(() => {
1238
+ if (!session) {
1239
+ log?.warn(
1240
+ `connection to ${client()} timed out waiting for handshake, closing`,
1241
+ {
1242
+ clientId: this.clientId,
1243
+ connectedTo: client(),
1244
+ connId: conn.id
1245
+ }
1246
+ );
1247
+ conn.telemetry?.span.setStatus({
1248
+ code: import_api3.SpanStatusCode.ERROR,
1249
+ message: "handshake timeout"
1188
1250
  });
1189
- let session = void 0;
1190
- const client = () => session?.to ?? "unknown";
1191
- const handshakeTimeout = setTimeout(() => {
1192
- if (!session) {
1193
- log?.warn(
1194
- `connection to ${client()} timed out waiting for handshake, closing`,
1195
- {
1196
- clientId: this.clientId,
1197
- connectedTo: client(),
1198
- connId: conn.debugId
1199
- }
1200
- );
1201
- span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
1202
- span.end();
1251
+ conn.close();
1252
+ }
1253
+ }, this.options.sessionDisconnectGraceMs);
1254
+ const buffer = [];
1255
+ let receivedHandshakeMessage = false;
1256
+ const handshakeHandler = (data) => {
1257
+ if (receivedHandshakeMessage) {
1258
+ buffer.push(data);
1259
+ return;
1260
+ }
1261
+ receivedHandshakeMessage = true;
1262
+ clearTimeout(handshakeTimeout);
1263
+ void this.receiveHandshakeRequestMessage(data, conn).then(
1264
+ (maybeSession) => {
1265
+ if (!maybeSession) {
1203
1266
  conn.close();
1204
- }
1205
- }, this.options.sessionDisconnectGraceMs);
1206
- const buffer = [];
1207
- let receivedHandshakeMessage = false;
1208
- const handshakeHandler = (data) => {
1209
- if (receivedHandshakeMessage) {
1210
- buffer.push(data);
1211
1267
  return;
1212
1268
  }
1213
- receivedHandshakeMessage = true;
1214
- clearTimeout(handshakeTimeout);
1215
- void this.receiveHandshakeRequestMessage(data, conn).then(
1216
- (maybeSession) => {
1217
- if (!maybeSession) {
1218
- span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
1219
- span.end();
1220
- conn.close();
1221
- return;
1222
- }
1223
- session = maybeSession;
1224
- const dataHandler = (data2) => {
1225
- const parsed = this.parseMsg(data2);
1226
- if (!parsed) {
1227
- conn.close();
1228
- return;
1229
- }
1230
- this.handleMsg(parsed);
1231
- };
1232
- conn.removeDataListener(handshakeHandler);
1233
- conn.addDataListener(dataHandler);
1234
- for (const data2 of buffer) {
1235
- dataHandler(data2);
1236
- }
1237
- buffer.length = 0;
1269
+ session = maybeSession;
1270
+ const dataHandler = (data2) => {
1271
+ const parsed = this.parseMsg(data2);
1272
+ if (!parsed) {
1273
+ conn.close();
1274
+ return;
1238
1275
  }
1239
- );
1240
- };
1241
- conn.addDataListener(handshakeHandler);
1242
- conn.addCloseListener(() => {
1243
- if (session) {
1244
- log?.info(`connection to ${client()} disconnected`, {
1245
- clientId: this.clientId,
1246
- connId: conn.debugId
1247
- });
1248
- this.onDisconnect(conn, session);
1276
+ this.handleMsg(parsed);
1277
+ };
1278
+ for (const data2 of buffer) {
1279
+ dataHandler(data2);
1249
1280
  }
1250
- span.setStatus({ code: import_api2.SpanStatusCode.OK });
1251
- span.end();
1281
+ conn.removeDataListener(handshakeHandler);
1282
+ conn.addDataListener(dataHandler);
1283
+ buffer.length = 0;
1284
+ }
1285
+ );
1286
+ };
1287
+ conn.addDataListener(handshakeHandler);
1288
+ conn.addCloseListener(() => {
1289
+ if (!session)
1290
+ return;
1291
+ log?.info(`connection to ${client()} disconnected`, {
1292
+ clientId: this.clientId,
1293
+ connId: conn.id
1294
+ });
1295
+ this.onDisconnect(conn, session);
1296
+ });
1297
+ conn.addErrorListener((err) => {
1298
+ conn.telemetry?.span.setStatus({
1299
+ code: import_api3.SpanStatusCode.ERROR,
1300
+ message: "connection error"
1301
+ });
1302
+ if (!session)
1303
+ return;
1304
+ log?.warn(
1305
+ `connection to ${client()} got an error: ${coerceErrorString(err)}`,
1306
+ { clientId: this.clientId, connId: conn.id }
1307
+ );
1308
+ });
1309
+ }
1310
+ async validateHandshakeMetadata(conn, session, rawMetadata, from) {
1311
+ let parsedMetadata = {};
1312
+ if (this.handshakeExtensions) {
1313
+ if (!import_value.Value.Check(this.handshakeExtensions.schema, rawMetadata)) {
1314
+ conn.telemetry?.span.setStatus({
1315
+ code: import_api3.SpanStatusCode.ERROR,
1316
+ message: "malformed handshake meta"
1252
1317
  });
1253
- conn.addErrorListener((err) => {
1254
- if (session) {
1255
- log?.warn(
1256
- `connection to ${client()} got an error: ${coerceErrorString(
1257
- err
1258
- )}`,
1259
- { clientId: this.clientId, connId: conn.debugId }
1260
- );
1261
- }
1262
- span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
1263
- span.end();
1318
+ const reason = "received malformed handshake metadata";
1319
+ const responseMsg = handshakeResponseMessage(this.clientId, from, {
1320
+ ok: false,
1321
+ reason
1264
1322
  });
1323
+ conn.send(this.codec.toBuffer(responseMsg));
1324
+ log?.warn(`received malformed handshake metadata from ${from}`, {
1325
+ clientId: this.clientId,
1326
+ connId: conn.id
1327
+ });
1328
+ this.protocolError(ProtocolError.HandshakeFailed, reason);
1329
+ return false;
1265
1330
  }
1266
- );
1331
+ parsedMetadata = await this.handshakeExtensions.validate(
1332
+ rawMetadata,
1333
+ session
1334
+ );
1335
+ if (parsedMetadata === false) {
1336
+ const reason = "rejected by handshake handler";
1337
+ conn.telemetry?.span.setStatus({
1338
+ code: import_api3.SpanStatusCode.ERROR,
1339
+ message: reason
1340
+ });
1341
+ const responseMsg = handshakeResponseMessage(this.clientId, from, {
1342
+ ok: false,
1343
+ reason
1344
+ });
1345
+ conn.send(this.codec.toBuffer(responseMsg));
1346
+ log?.warn(`rejected handshake from ${from}`, {
1347
+ clientId: this.clientId,
1348
+ connId: conn.id
1349
+ });
1350
+ this.protocolError(ProtocolError.HandshakeFailed, reason);
1351
+ return false;
1352
+ }
1353
+ }
1354
+ return parsedMetadata;
1267
1355
  }
1268
1356
  async receiveHandshakeRequestMessage(data, conn) {
1269
1357
  const parsed = this.parseMsg(data);
1270
1358
  if (!parsed) {
1359
+ conn.telemetry?.span.setStatus({
1360
+ code: import_api3.SpanStatusCode.ERROR,
1361
+ message: "non-transport message"
1362
+ });
1271
1363
  this.protocolError(
1272
1364
  ProtocolError.HandshakeFailed,
1273
1365
  "received non-transport message"
1274
1366
  );
1275
1367
  return false;
1276
1368
  }
1277
- let activeContext = import_api2.context.active();
1278
- if (parsed.tracing) {
1279
- activeContext = import_api2.propagation.extract(activeContext, parsed.tracing);
1369
+ if (!import_value.Value.Check(ControlMessageHandshakeRequestSchema, parsed.payload)) {
1370
+ conn.telemetry?.span.setStatus({
1371
+ code: import_api3.SpanStatusCode.ERROR,
1372
+ message: "invalid handshake request"
1373
+ });
1374
+ const reason = "received invalid handshake msg";
1375
+ const responseMsg2 = handshakeResponseMessage(this.clientId, parsed.from, {
1376
+ ok: false,
1377
+ reason
1378
+ });
1379
+ conn.send(this.codec.toBuffer(responseMsg2));
1380
+ const logData = { ...parsed.payload ?? {}, metadata: "redacted" };
1381
+ log?.warn(reason, {
1382
+ clientId: this.clientId,
1383
+ connId: conn.id,
1384
+ partialTransportMessage: { ...parsed, payload: logData }
1385
+ });
1386
+ this.protocolError(
1387
+ ProtocolError.HandshakeFailed,
1388
+ "invalid handshake request"
1389
+ );
1390
+ return false;
1280
1391
  }
1281
- return tracing_default.startActiveSpan(
1282
- "receiveHandshakeRequestMessage",
1283
- {
1284
- attributes: {
1285
- component: "river",
1286
- "span.kind": "server"
1287
- },
1288
- kind: import_api2.SpanKind.SERVER
1289
- },
1290
- activeContext,
1291
- async (span) => {
1292
- if (!import_value.Value.Check(ControlMessageHandshakeRequestSchema, parsed.payload)) {
1293
- const reason = "received invalid handshake msg";
1294
- const responseMsg2 = handshakeResponseMessage(
1295
- this.clientId,
1296
- parsed.from,
1297
- {
1298
- ok: false,
1299
- reason
1300
- }
1301
- );
1302
- conn.send(this.codec.toBuffer(responseMsg2));
1303
- const logData = typeof parsed.payload === "object" ? {
1304
- ...parsed,
1305
- payload: { ...parsed.payload, metadata: "redacted" }
1306
- } : { ...parsed };
1307
- log?.warn(`${reason}: ${JSON.stringify(logData)}`, {
1308
- clientId: this.clientId,
1309
- connId: conn.debugId
1310
- });
1311
- this.protocolError(
1312
- ProtocolError.HandshakeFailed,
1313
- "invalid handshake request"
1314
- );
1315
- span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
1316
- span.end();
1317
- return false;
1318
- }
1319
- const gotVersion = parsed.payload.protocolVersion;
1320
- if (gotVersion !== PROTOCOL_VERSION) {
1321
- const reason = `incorrect version (got: ${gotVersion} wanted ${PROTOCOL_VERSION})`;
1322
- const responseMsg2 = handshakeResponseMessage(
1323
- this.clientId,
1324
- parsed.from,
1325
- {
1326
- ok: false,
1327
- reason
1328
- }
1329
- );
1330
- conn.send(this.codec.toBuffer(responseMsg2));
1331
- log?.warn(
1332
- `received handshake msg with incompatible protocol version (got: ${gotVersion}, expected: ${PROTOCOL_VERSION})`,
1333
- { clientId: this.clientId, connId: conn.debugId }
1334
- );
1335
- this.protocolError(ProtocolError.HandshakeFailed, reason);
1336
- span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
1337
- span.end();
1338
- return false;
1339
- }
1340
- const { session, isReconnect } = this.getOrCreateSession(
1341
- parsed.from,
1342
- conn,
1343
- parsed.payload.sessionId
1344
- );
1345
- let handshakeMetadata;
1346
- if (this.options.handshake) {
1347
- if (!import_value.Value.Check(
1348
- this.options.handshake.requestSchema,
1349
- parsed.payload.metadata
1350
- )) {
1351
- const reason = "received malformed handshake metadata";
1352
- const responseMsg2 = handshakeResponseMessage(
1353
- this.clientId,
1354
- parsed.from,
1355
- { ok: false, reason }
1356
- );
1357
- conn.send(this.codec.toBuffer(responseMsg2));
1358
- log?.warn(
1359
- `received malformed handshake metadata from ${parsed.from}`,
1360
- {
1361
- clientId: this.clientId,
1362
- connId: conn.debugId
1363
- }
1364
- );
1365
- this.protocolError(ProtocolError.HandshakeFailed, reason);
1366
- this.deleteSession(session);
1367
- span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
1368
- span.end();
1369
- return false;
1370
- }
1371
- const parsedMetadata = await this.options.handshake.parse(
1372
- parsed.payload.metadata,
1373
- session,
1374
- isReconnect
1375
- );
1376
- if (parsedMetadata === false) {
1377
- const reason = "rejected by server";
1378
- const responseMsg2 = handshakeResponseMessage(
1379
- this.clientId,
1380
- parsed.from,
1381
- { ok: false, reason }
1382
- );
1383
- conn.send(this.codec.toBuffer(responseMsg2));
1384
- log?.warn(`rejected handshake from ${parsed.from}`, {
1385
- clientId: this.clientId,
1386
- connId: conn.debugId
1387
- });
1388
- this.protocolError(ProtocolError.HandshakeFailed, reason);
1389
- this.deleteSession(session);
1390
- span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
1391
- span.end();
1392
- return false;
1393
- }
1394
- if (!import_value.Value.Check(this.options.handshake.parsedSchema, parsedMetadata)) {
1395
- const reason = "failed to parse handshake metadata";
1396
- const responseMsg2 = handshakeResponseMessage(
1397
- this.clientId,
1398
- parsed.from,
1399
- { ok: false, reason }
1400
- );
1401
- conn.send(this.codec.toBuffer(responseMsg2));
1402
- log?.error(`failed to parse handshake metadata`, {
1403
- clientId: this.clientId,
1404
- connId: conn.debugId,
1405
- tags: ["invariant-violation"]
1406
- });
1407
- this.protocolError(ProtocolError.HandshakeFailed, reason);
1408
- this.deleteSession(session);
1409
- span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
1410
- span.end();
1411
- return false;
1412
- }
1413
- handshakeMetadata = parsedMetadata;
1414
- }
1415
- handshakeMetadata ??= {};
1416
- session.metadata = handshakeMetadata;
1417
- log?.debug(
1418
- `handshake from ${parsed.from} ok, responding with handshake success`,
1419
- { clientId: this.clientId, connId: conn.debugId }
1420
- );
1421
- const responseMsg = handshakeResponseMessage(
1422
- this.clientId,
1423
- parsed.from,
1424
- {
1425
- ok: true,
1426
- sessionId: session.id
1427
- }
1428
- );
1429
- conn.send(this.codec.toBuffer(responseMsg));
1430
- this.onConnect(conn, parsed.from, session, isReconnect);
1431
- span.end();
1432
- return session;
1433
- }
1392
+ const gotVersion = parsed.payload.protocolVersion;
1393
+ if (gotVersion !== PROTOCOL_VERSION) {
1394
+ conn.telemetry?.span.setStatus({
1395
+ code: import_api3.SpanStatusCode.ERROR,
1396
+ message: "incorrect protocol version"
1397
+ });
1398
+ const reason = `incorrect version (got: ${gotVersion} wanted ${PROTOCOL_VERSION})`;
1399
+ const responseMsg2 = handshakeResponseMessage(this.clientId, parsed.from, {
1400
+ ok: false,
1401
+ reason
1402
+ });
1403
+ conn.send(this.codec.toBuffer(responseMsg2));
1404
+ log?.warn(
1405
+ `received handshake msg with incompatible protocol version (got: ${gotVersion}, expected: ${PROTOCOL_VERSION})`,
1406
+ { clientId: this.clientId, connId: conn.id }
1407
+ );
1408
+ this.protocolError(ProtocolError.HandshakeFailed, reason);
1409
+ return false;
1410
+ }
1411
+ const oldSession = this.sessions.get(parsed.from);
1412
+ const parsedMetadata = await this.validateHandshakeMetadata(
1413
+ conn,
1414
+ oldSession,
1415
+ parsed.payload.metadata,
1416
+ parsed.from
1434
1417
  );
1418
+ if (parsedMetadata === false) {
1419
+ return false;
1420
+ }
1421
+ const { session, isReconnect } = this.getOrCreateSession(
1422
+ parsed.from,
1423
+ conn,
1424
+ parsed.payload.sessionId,
1425
+ parsed.tracing
1426
+ );
1427
+ this.sessionHandshakeMetadata.set(session, parsedMetadata);
1428
+ log?.debug(
1429
+ `handshake from ${parsed.from} ok, responding with handshake success`,
1430
+ { clientId: this.clientId, connId: conn.id }
1431
+ );
1432
+ const responseMsg = handshakeResponseMessage(this.clientId, parsed.from, {
1433
+ ok: true,
1434
+ sessionId: session.id
1435
+ });
1436
+ conn.send(this.codec.toBuffer(responseMsg));
1437
+ this.onConnect(conn, parsed.from, session, isReconnect);
1438
+ return session;
1435
1439
  }
1436
1440
  };
1437
1441
  // Annotate the CommonJS export names for ESM import in node: