@replit/river 0.21.0 → 0.22.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 (68) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-5WFL722S.js → chunk-3MFX6NXA.js} +94 -3
  3. package/dist/chunk-3MFX6NXA.js.map +1 -0
  4. package/dist/{chunk-NFV77C2M.js → chunk-GCLEWC26.js} +340 -507
  5. package/dist/chunk-GCLEWC26.js.map +1 -0
  6. package/dist/chunk-HUBFYN37.js +60 -0
  7. package/dist/chunk-HUBFYN37.js.map +1 -0
  8. package/dist/{chunk-DT5JS6TM.js → chunk-OTQNCLFH.js} +1 -1
  9. package/dist/chunk-OTQNCLFH.js.map +1 -0
  10. package/dist/{chunk-MJR36SUY.js → chunk-S3YKQT4J.js} +2 -2
  11. package/dist/{chunk-QU2EE6YU.js → chunk-ZPBWKBM5.js} +361 -394
  12. package/dist/chunk-ZPBWKBM5.js.map +1 -0
  13. package/dist/{connection-8a71dbe2.d.ts → connection-8b059ac4.d.ts} +6 -4
  14. package/dist/{connection-d49d5d56.d.ts → connection-bbfe1147.d.ts} +1 -1
  15. package/dist/{index-3ac92295.d.ts → index-2ece5234.d.ts} +18 -7
  16. package/dist/logging/index.cjs.map +1 -1
  17. package/dist/logging/index.d.cts +2 -1
  18. package/dist/logging/index.d.ts +2 -1
  19. package/dist/logging/index.js +1 -1
  20. package/dist/router/index.cjs +384 -492
  21. package/dist/router/index.cjs.map +1 -1
  22. package/dist/router/index.d.cts +5 -4
  23. package/dist/router/index.d.ts +5 -4
  24. package/dist/router/index.js +5 -4
  25. package/dist/{services-abc077db.d.ts → services-acbcc441.d.ts} +1 -1
  26. package/dist/{services-8496d6e8.d.ts → services-cb01a7a8.d.ts} +1 -1
  27. package/dist/transport/impls/uds/client.cjs +202 -155
  28. package/dist/transport/impls/uds/client.cjs.map +1 -1
  29. package/dist/transport/impls/uds/client.d.cts +3 -2
  30. package/dist/transport/impls/uds/client.d.ts +3 -2
  31. package/dist/transport/impls/uds/client.js +4 -4
  32. package/dist/transport/impls/uds/server.cjs +295 -264
  33. package/dist/transport/impls/uds/server.cjs.map +1 -1
  34. package/dist/transport/impls/uds/server.d.cts +3 -2
  35. package/dist/transport/impls/uds/server.d.ts +3 -2
  36. package/dist/transport/impls/uds/server.js +4 -4
  37. package/dist/transport/impls/ws/client.cjs +256 -214
  38. package/dist/transport/impls/ws/client.cjs.map +1 -1
  39. package/dist/transport/impls/ws/client.d.cts +6 -6
  40. package/dist/transport/impls/ws/client.d.ts +6 -6
  41. package/dist/transport/impls/ws/client.js +34 -49
  42. package/dist/transport/impls/ws/client.js.map +1 -1
  43. package/dist/transport/impls/ws/server.cjs +317 -278
  44. package/dist/transport/impls/ws/server.cjs.map +1 -1
  45. package/dist/transport/impls/ws/server.d.cts +5 -4
  46. package/dist/transport/impls/ws/server.d.ts +5 -4
  47. package/dist/transport/impls/ws/server.js +4 -4
  48. package/dist/transport/impls/ws/server.js.map +1 -1
  49. package/dist/transport/index.cjs +406 -391
  50. package/dist/transport/index.cjs.map +1 -1
  51. package/dist/transport/index.d.cts +5 -5
  52. package/dist/transport/index.d.ts +5 -5
  53. package/dist/transport/index.js +3 -3
  54. package/dist/util/testHelpers.cjs +71 -19
  55. package/dist/util/testHelpers.cjs.map +1 -1
  56. package/dist/util/testHelpers.d.cts +20 -8
  57. package/dist/util/testHelpers.d.ts +20 -8
  58. package/dist/util/testHelpers.js +13 -12
  59. package/dist/util/testHelpers.js.map +1 -1
  60. package/dist/wslike-e0b32dd5.d.ts +40 -0
  61. package/package.json +4 -5
  62. package/dist/chunk-2ERP6FUE.js +0 -42
  63. package/dist/chunk-2ERP6FUE.js.map +0 -1
  64. package/dist/chunk-5WFL722S.js.map +0 -1
  65. package/dist/chunk-DT5JS6TM.js.map +0 -1
  66. package/dist/chunk-NFV77C2M.js.map +0 -1
  67. package/dist/chunk-QU2EE6YU.js.map +0 -1
  68. /package/dist/{chunk-MJR36SUY.js.map → chunk-S3YKQT4J.js.map} +0 -0
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // transport/impls/ws/client.ts
@@ -33,11 +23,9 @@ __export(client_exports, {
33
23
  WebSocketClientTransport: () => WebSocketClientTransport
34
24
  });
35
25
  module.exports = __toCommonJS(client_exports);
36
- var import_agnostic_ws2 = __toESM(require("agnostic-ws"), 1);
37
26
 
38
27
  // transport/transport.ts
39
28
  var import_value = require("@sinclair/typebox/value");
40
- var import_api2 = require("@opentelemetry/api");
41
29
 
42
30
  // transport/message.ts
43
31
  var import_typebox = require("@sinclair/typebox");
@@ -156,17 +144,71 @@ var EventDispatcher = class {
156
144
 
157
145
  // transport/session.ts
158
146
  var import_nanoid2 = require("nanoid");
147
+
148
+ // tracing/index.ts
149
+ var import_api = require("@opentelemetry/api");
150
+
151
+ // package.json
152
+ var version = "0.22.0";
153
+
154
+ // tracing/index.ts
155
+ function getPropagationContext(ctx) {
156
+ const tracing = {
157
+ traceparent: "",
158
+ tracestate: ""
159
+ };
160
+ import_api.propagation.inject(ctx, tracing);
161
+ return tracing;
162
+ }
163
+ function createSessionTelemetryInfo(session, propagationCtx) {
164
+ const ctx = propagationCtx ? import_api.propagation.extract(import_api.context.active(), propagationCtx) : import_api.context.active();
165
+ const span = tracer.startSpan(
166
+ `session ${session.id}`,
167
+ {
168
+ attributes: {
169
+ component: "river",
170
+ "river.session.id": session.id,
171
+ "river.session.to": session.to,
172
+ "river.session.from": session.from
173
+ }
174
+ },
175
+ ctx
176
+ );
177
+ return { span, ctx };
178
+ }
179
+ function createConnectionTelemetryInfo(connection, sessionSpan) {
180
+ const ctx = import_api.trace.setSpan(import_api.context.active(), sessionSpan);
181
+ const span = tracer.startSpan(
182
+ `connection ${connection.id}`,
183
+ {
184
+ attributes: {
185
+ component: "river",
186
+ "river.connection.id": connection.id
187
+ },
188
+ links: [{ context: sessionSpan.spanContext() }]
189
+ },
190
+ ctx
191
+ );
192
+ return { span, ctx };
193
+ }
194
+ var tracer = import_api.trace.getTracer("river", version);
195
+ var tracing_default = tracer;
196
+
197
+ // transport/session.ts
198
+ var import_api2 = require("@opentelemetry/api");
159
199
  var nanoid2 = (0, import_nanoid2.customAlphabet)("1234567890abcdefghijklmnopqrstuvxyz", 6);
160
200
  var unsafeId = () => nanoid2();
161
201
  var Connection = class {
162
- debugId;
202
+ id;
203
+ telemetry;
163
204
  constructor() {
164
- this.debugId = `conn-${unsafeId()}`;
205
+ this.id = `conn-${nanoid2(12)}`;
165
206
  }
166
207
  };
167
208
  var Session = class {
168
209
  codec;
169
210
  options;
211
+ telemetry;
170
212
  /**
171
213
  * The buffer of messages that have been sent but not yet acknowledged.
172
214
  */
@@ -213,7 +255,7 @@ var Session = class {
213
255
  * The interval for sending heartbeats.
214
256
  */
215
257
  heartbeat;
216
- constructor(conn, from, to, options) {
258
+ constructor(conn, from, to, options, propagationCtx) {
217
259
  this.id = `session-${nanoid2(12)}`;
218
260
  this.options = options;
219
261
  this.from = from;
@@ -225,13 +267,14 @@ var Session = class {
225
267
  () => this.sendHeartbeat(),
226
268
  options.heartbeatIntervalMs
227
269
  );
270
+ this.telemetry = createSessionTelemetryInfo(this, propagationCtx);
228
271
  }
229
272
  get loggingMetadata() {
230
273
  return {
231
274
  clientId: this.from,
232
275
  connectedTo: this.to,
233
276
  sessionId: this.id,
234
- connId: this.connection?.debugId
277
+ connId: this.connection?.id
235
278
  };
236
279
  }
237
280
  /**
@@ -276,6 +319,7 @@ var Session = class {
276
319
  `closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
277
320
  this.loggingMetadata
278
321
  );
322
+ this.telemetry.span.addEvent("closing connection due to inactivity");
279
323
  this.closeStaleConnection();
280
324
  }
281
325
  return;
@@ -297,32 +341,38 @@ var Session = class {
297
341
  sendBufferedMessages(conn) {
298
342
  log?.info(`resending ${this.sendBuffer.length} buffered messages`, {
299
343
  ...this.loggingMetadata,
300
- connId: conn.debugId
344
+ connId: conn.id
301
345
  });
302
346
  for (const msg of this.sendBuffer) {
303
347
  log?.debug(`resending msg`, {
304
348
  ...this.loggingMetadata,
305
349
  fullTransportMessage: msg,
306
- connId: conn.debugId
350
+ connId: conn.id
307
351
  });
308
352
  const ok = conn.send(this.codec.toBuffer(msg));
309
353
  if (!ok) {
310
354
  const errMsg = `failed to send buffered message to ${this.to} (sus, this is a fresh connection)`;
355
+ conn.telemetry?.span.setStatus({
356
+ code: import_api2.SpanStatusCode.ERROR,
357
+ message: errMsg
358
+ });
311
359
  log?.error(errMsg, {
312
360
  ...this.loggingMetadata,
313
361
  fullTransportMessage: msg,
314
- connId: conn.debugId
362
+ connId: conn.id,
363
+ tags: ["invariant-violation"]
315
364
  });
316
- throw new Error(errMsg);
365
+ conn.close();
366
+ return;
317
367
  }
318
368
  }
319
369
  }
320
370
  updateBookkeeping(ack, seq) {
321
371
  if (seq + 1 < this.ack) {
322
- log?.error(
323
- `received stale seq ${seq} + 1 < ${this.ack}`,
324
- this.loggingMetadata
325
- );
372
+ log?.error(`received stale seq ${seq} + 1 < ${this.ack}`, {
373
+ ...this.loggingMetadata,
374
+ tags: ["invariant-violation"]
375
+ });
326
376
  return;
327
377
  }
328
378
  this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
@@ -392,11 +442,6 @@ var Session = class {
392
442
  }
393
443
  };
394
444
 
395
- // tracing/index.ts
396
- var import_api = require("@opentelemetry/api");
397
- var tracer = import_api.trace.getTracer("river");
398
- var tracing_default = tracer;
399
-
400
445
  // util/stringify.ts
401
446
  function coerceErrorString(err) {
402
447
  if (err instanceof Error) {
@@ -529,6 +574,7 @@ var NaiveJsonCodec = {
529
574
  };
530
575
 
531
576
  // transport/transport.ts
577
+ var import_api3 = require("@opentelemetry/api");
532
578
  var defaultTransportOptions = {
533
579
  heartbeatIntervalMs: 1e3,
534
580
  heartbeatsUntilDead: 2,
@@ -607,17 +653,22 @@ var Transport = class {
607
653
  status: "connect",
608
654
  conn
609
655
  });
656
+ conn.telemetry = createConnectionTelemetryInfo(
657
+ conn,
658
+ session.telemetry.span
659
+ );
610
660
  if (isReconnect) {
611
661
  session.replaceWithNewConnection(conn);
612
662
  log?.info(`reconnected to ${connectedTo}`, session.loggingMetadata);
613
663
  }
614
664
  }
615
- createSession(to, conn) {
665
+ createSession(to, conn, propagationCtx) {
616
666
  const session = new Session(
617
667
  conn,
618
668
  this.clientId,
619
669
  to,
620
- this.options
670
+ this.options,
671
+ propagationCtx
621
672
  );
622
673
  this.sessions.set(session.to, session);
623
674
  this.eventDispatcher.dispatchEvent("sessionStatus", {
@@ -626,11 +677,11 @@ var Transport = class {
626
677
  });
627
678
  return session;
628
679
  }
629
- getOrCreateSession(to, conn, sessionId) {
680
+ getOrCreateSession(to, conn, sessionId, propagationCtx) {
630
681
  let session = this.sessions.get(to);
631
682
  let isReconnect = session !== void 0;
632
683
  if (session?.advertisedSessionId !== void 0 && sessionId !== void 0 && session.advertisedSessionId !== sessionId) {
633
- log?.warn(
684
+ log?.info(
634
685
  `session for ${to} already exists but has a different session id (expected: ${session.advertisedSessionId}, got: ${sessionId}), creating a new one`,
635
686
  session.loggingMetadata
636
687
  );
@@ -639,7 +690,7 @@ var Transport = class {
639
690
  session = void 0;
640
691
  }
641
692
  if (!session) {
642
- session = this.createSession(to, conn);
693
+ session = this.createSession(to, conn, propagationCtx);
643
694
  log?.info(
644
695
  `no session for ${to}, created a new one`,
645
696
  session.loggingMetadata
@@ -652,6 +703,7 @@ var Transport = class {
652
703
  }
653
704
  deleteSession(session) {
654
705
  session.close();
706
+ session.telemetry.span.end();
655
707
  this.sessions.delete(session.to);
656
708
  log?.info(
657
709
  `session ${session.id} disconnect from ${session.to}`,
@@ -668,12 +720,16 @@ var Transport = class {
668
720
  * @param connectedTo The peer we are connected to.
669
721
  */
670
722
  onDisconnect(conn, session) {
723
+ conn.telemetry?.span.end();
671
724
  this.eventDispatcher.dispatchEvent("connectionStatus", {
672
725
  status: "disconnect",
673
726
  conn
674
727
  });
675
728
  session.connection = void 0;
676
- session.beginGrace(() => this.deleteSession(session));
729
+ session.beginGrace(() => {
730
+ session.telemetry.span.addEvent("session grace period expired");
731
+ this.deleteSession(session);
732
+ });
677
733
  }
678
734
  /**
679
735
  * Parses a message from a Uint8Array into a {@link OpaqueTransportMessage}.
@@ -707,9 +763,10 @@ var Transport = class {
707
763
  return;
708
764
  const session = this.sessions.get(msg.from);
709
765
  if (!session) {
710
- log?.error(`(invariant violation) no existing session for ${msg.from}`, {
766
+ log?.error(`no existing session for ${msg.from}`, {
711
767
  clientId: this.clientId,
712
- fullTransportMessage: msg
768
+ fullTransportMessage: msg,
769
+ tags: ["invariant-violation"]
713
770
  });
714
771
  return;
715
772
  }
@@ -728,9 +785,14 @@ var Transport = class {
728
785
  const errMsg = `received out-of-order msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq})`;
729
786
  log?.error(`${errMsg}, marking connection as dead`, {
730
787
  clientId: this.clientId,
731
- fullTransportMessage: msg
788
+ fullTransportMessage: msg,
789
+ tags: ["invariant-violation"]
732
790
  });
733
791
  this.protocolError(ProtocolError.MessageOrderingViolated, errMsg);
792
+ session.telemetry.span.setStatus({
793
+ code: import_api3.SpanStatusCode.ERROR,
794
+ message: "message order violated"
795
+ });
734
796
  session.close();
735
797
  }
736
798
  return;
@@ -772,7 +834,8 @@ var Transport = class {
772
834
  const err = "transport is destroyed, cant send";
773
835
  log?.error(err, {
774
836
  clientId: this.clientId,
775
- partialTransportMessage: msg
837
+ partialTransportMessage: msg,
838
+ tags: ["invariant-violation"]
776
839
  });
777
840
  this.protocolError(ProtocolError.UseAfterDestroy, err);
778
841
  return void 0;
@@ -857,7 +920,7 @@ var ClientTransport = class extends Transport {
857
920
  if (!session) {
858
921
  log?.warn(
859
922
  `connection to ${to} timed out waiting for handshake, closing`,
860
- { clientId: this.clientId, connectedTo: to, connId: conn.debugId }
923
+ { clientId: this.clientId, connectedTo: to, connId: conn.id }
861
924
  );
862
925
  conn.close();
863
926
  }
@@ -875,6 +938,10 @@ var ClientTransport = class extends Transport {
875
938
  conn.addDataListener((data2) => {
876
939
  const parsed = this.parseMsg(data2);
877
940
  if (!parsed) {
941
+ conn.telemetry?.span.setStatus({
942
+ code: import_api3.SpanStatusCode.ERROR,
943
+ message: "message parse failure"
944
+ });
878
945
  conn.close();
879
946
  return;
880
947
  }
@@ -897,6 +964,10 @@ var ClientTransport = class extends Transport {
897
964
  }
898
965
  });
899
966
  conn.addErrorListener((err) => {
967
+ conn.telemetry?.span.setStatus({
968
+ code: import_api3.SpanStatusCode.ERROR,
969
+ message: "connection error"
970
+ });
900
971
  log?.warn(`error in connection to ${to}: ${coerceErrorString(err)}`, {
901
972
  ...session?.loggingMetadata,
902
973
  clientId: this.clientId,
@@ -907,6 +978,10 @@ var ClientTransport = class extends Transport {
907
978
  receiveHandshakeResponseMessage(data, conn) {
908
979
  const parsed = this.parseMsg(data);
909
980
  if (!parsed) {
981
+ conn.telemetry?.span.setStatus({
982
+ code: import_api3.SpanStatusCode.ERROR,
983
+ message: "non-transport message"
984
+ });
910
985
  this.protocolError(
911
986
  ProtocolError.HandshakeFailed,
912
987
  "received non-transport message"
@@ -914,6 +989,10 @@ var ClientTransport = class extends Transport {
914
989
  return false;
915
990
  }
916
991
  if (!import_value.Value.Check(ControlMessageHandshakeResponseSchema, parsed.payload)) {
992
+ conn.telemetry?.span.setStatus({
993
+ code: import_api3.SpanStatusCode.ERROR,
994
+ message: "invalid handshake response"
995
+ });
917
996
  log?.warn(`received invalid handshake resp`, {
918
997
  clientId: this.clientId,
919
998
  connectedTo: parsed.from,
@@ -926,7 +1005,11 @@ var ClientTransport = class extends Transport {
926
1005
  return false;
927
1006
  }
928
1007
  if (!parsed.payload.status.ok) {
929
- log?.warn(`received invalid handshake resp`, {
1008
+ conn.telemetry?.span.setStatus({
1009
+ code: import_api3.SpanStatusCode.ERROR,
1010
+ message: "handshake rejected"
1011
+ });
1012
+ log?.warn(`received handshake rejection`, {
930
1013
  clientId: this.clientId,
931
1014
  connectedTo: parsed.from,
932
1015
  fullTransportMessage: parsed
@@ -956,142 +1039,94 @@ var ClientTransport = class extends Transport {
956
1039
  * @param to The client ID of the node to connect to.
957
1040
  */
958
1041
  async connect(to) {
959
- return tracing_default.startActiveSpan(
960
- "connect",
961
- {
962
- attributes: {
963
- component: "river",
964
- "span.kind": "client"
965
- },
966
- kind: import_api2.SpanKind.CLIENT
967
- },
968
- async (span) => {
969
- try {
970
- await this.connectAttempt(to);
971
- } catch (e) {
972
- if (e instanceof Error) {
973
- span.recordException(e);
974
- } else {
975
- span.recordException(coerceErrorString(e));
976
- }
977
- span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
978
- } finally {
979
- span.end();
980
- }
1042
+ const canProceedWithConnection = () => this.state === "open";
1043
+ if (!canProceedWithConnection()) {
1044
+ log?.info(
1045
+ `transport state is no longer open, cancelling attempt to connect to ${to}`,
1046
+ { clientId: this.clientId, connectedTo: to }
1047
+ );
1048
+ return;
1049
+ }
1050
+ let reconnectPromise = this.inflightConnectionPromises.get(to);
1051
+ if (!reconnectPromise) {
1052
+ const budgetConsumed = this.retryBudget.getBudgetConsumed(to);
1053
+ if (!this.retryBudget.hasBudget(to)) {
1054
+ const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
1055
+ log?.warn(errMsg, { clientId: this.clientId, connectedTo: to });
1056
+ this.protocolError(ProtocolError.RetriesExceeded, errMsg);
1057
+ return;
981
1058
  }
982
- );
983
- }
984
- async connectAttempt(to, attempt = 0) {
985
- const retry = await tracing_default.startActiveSpan(
986
- "connect",
987
- {
988
- attributes: {
989
- component: "river",
990
- "river.attempt": attempt,
991
- "span.kind": "client"
992
- },
993
- kind: import_api2.SpanKind.CLIENT
994
- },
995
- async (span) => {
1059
+ let sleep = Promise.resolve();
1060
+ const backoffMs = this.retryBudget.getBackoffMs(to);
1061
+ if (backoffMs > 0) {
1062
+ sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
1063
+ }
1064
+ log?.info(`attempting connection to ${to} (${backoffMs}ms backoff)`, {
1065
+ clientId: this.clientId,
1066
+ connectedTo: to
1067
+ });
1068
+ this.retryBudget.consumeBudget(to);
1069
+ reconnectPromise = tracing_default.startActiveSpan("connect", async (span) => {
996
1070
  try {
997
- const canProceedWithConnection = () => this.state === "open";
1071
+ span.addEvent("backoff", { backoffMs });
1072
+ await sleep;
998
1073
  if (!canProceedWithConnection()) {
999
- log?.info(
1000
- `transport state is no longer open, cancelling attempt to connect to ${to}`,
1001
- { clientId: this.clientId, connectedTo: to }
1002
- );
1003
- return false;
1074
+ throw new Error("transport state is no longer open");
1004
1075
  }
1005
- let reconnectPromise = this.inflightConnectionPromises.get(to);
1006
- if (!reconnectPromise) {
1007
- const budgetConsumed = this.retryBudget.getBudgetConsumed(to);
1008
- if (!this.retryBudget.hasBudget(to)) {
1009
- const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
1010
- log?.warn(errMsg, { clientId: this.clientId, connectedTo: to });
1011
- this.protocolError(ProtocolError.RetriesExceeded, errMsg);
1012
- return false;
1013
- }
1014
- let sleep = Promise.resolve();
1015
- const backoffMs = this.retryBudget.getBackoffMs(to);
1016
- if (backoffMs > 0) {
1017
- sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
1018
- }
1019
- log?.info(
1020
- `attempting connection to ${to} (${backoffMs}ms backoff)`,
1021
- {
1022
- clientId: this.clientId,
1023
- connectedTo: to
1024
- }
1025
- );
1026
- this.retryBudget.consumeBudget(to);
1027
- reconnectPromise = sleep.then(() => {
1028
- if (!canProceedWithConnection()) {
1029
- throw new Error("transport state is no longer open");
1030
- }
1031
- }).then(() => this.createNewOutgoingConnection(to)).then((conn) => {
1032
- if (!canProceedWithConnection()) {
1033
- log?.info(
1034
- `transport state is no longer open, closing pre-handshake connection to ${to}`,
1035
- {
1036
- clientId: this.clientId,
1037
- connectedTo: to,
1038
- connId: conn.debugId
1039
- }
1040
- );
1041
- conn.close();
1042
- throw new Error("transport state is no longer open");
1043
- }
1044
- return this.sendHandshake(to, conn).then((ok) => {
1045
- if (!ok) {
1046
- conn.close();
1047
- throw new Error("failed to send handshake");
1048
- }
1049
- return conn;
1050
- });
1051
- });
1052
- this.inflightConnectionPromises.set(to, reconnectPromise);
1053
- } else {
1076
+ span.addEvent("connecting");
1077
+ const conn = await this.createNewOutgoingConnection(to);
1078
+ if (!canProceedWithConnection()) {
1054
1079
  log?.info(
1055
- `attempting connection to ${to} (reusing previous attempt)`,
1080
+ `transport state is no longer open, closing pre-handshake connection to ${to}`,
1056
1081
  {
1057
1082
  clientId: this.clientId,
1058
- connectedTo: to
1083
+ connectedTo: to,
1084
+ connId: conn.id
1059
1085
  }
1060
1086
  );
1087
+ conn.close();
1088
+ throw new Error("transport state is no longer open");
1061
1089
  }
1062
- try {
1063
- await reconnectPromise;
1064
- } catch (error) {
1065
- this.inflightConnectionPromises.delete(to);
1066
- const errStr = coerceErrorString(error);
1067
- if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {
1068
- log?.warn(`connection to ${to} failed (${errStr})`, {
1069
- clientId: this.clientId,
1070
- connectedTo: to
1071
- });
1072
- } else {
1073
- log?.warn(`connection to ${to} failed (${errStr}), retrying`, {
1074
- clientId: this.clientId,
1075
- connectedTo: to
1076
- });
1077
- return true;
1078
- }
1079
- }
1080
- } catch (e) {
1081
- if (e instanceof Error) {
1082
- span.recordException(e);
1083
- } else {
1084
- span.recordException(coerceErrorString(e));
1090
+ span.addEvent("sending handshake");
1091
+ const ok = await this.sendHandshake(to, conn);
1092
+ if (!ok) {
1093
+ conn.close();
1094
+ throw new Error("failed to send handshake");
1085
1095
  }
1086
- span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
1096
+ return conn;
1097
+ } catch (err) {
1098
+ const errStr = coerceErrorString(err);
1099
+ span.recordException(errStr);
1100
+ span.setStatus({ code: import_api3.SpanStatusCode.ERROR });
1101
+ throw err;
1087
1102
  } finally {
1088
1103
  span.end();
1089
1104
  }
1090
- return false;
1105
+ });
1106
+ this.inflightConnectionPromises.set(to, reconnectPromise);
1107
+ } else {
1108
+ log?.info(`attempting connection to ${to} (reusing previous attempt)`, {
1109
+ clientId: this.clientId,
1110
+ connectedTo: to
1111
+ });
1112
+ }
1113
+ try {
1114
+ await reconnectPromise;
1115
+ } catch (error) {
1116
+ this.inflightConnectionPromises.delete(to);
1117
+ const errStr = coerceErrorString(error);
1118
+ if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {
1119
+ log?.warn(`connection to ${to} failed (${errStr})`, {
1120
+ clientId: this.clientId,
1121
+ connectedTo: to
1122
+ });
1123
+ } else {
1124
+ log?.warn(`connection to ${to} failed (${errStr}), retrying`, {
1125
+ clientId: this.clientId,
1126
+ connectedTo: to
1127
+ });
1128
+ return this.connect(to);
1091
1129
  }
1092
- );
1093
- if (retry) {
1094
- return this.connectAttempt(to, attempt + 1);
1095
1130
  }
1096
1131
  }
1097
1132
  deleteSession(session) {
@@ -1099,20 +1134,23 @@ var ClientTransport = class extends Transport {
1099
1134
  super.deleteSession(session);
1100
1135
  }
1101
1136
  async sendHandshake(to, conn) {
1102
- const tracing = { traceparent: "", tracestate: "" };
1103
- import_api2.propagation.inject(import_api2.context.active(), tracing);
1104
1137
  let metadata;
1105
1138
  if (this.options.handshake) {
1106
1139
  metadata = await this.options.handshake.get();
1107
1140
  if (!import_value.Value.Check(this.options.handshake.schema, metadata)) {
1108
1141
  log?.error(`handshake metadata did not match schema`, {
1109
1142
  clientId: this.clientId,
1110
- connectedTo: to
1143
+ connectedTo: to,
1144
+ tags: ["invariant-violation"]
1111
1145
  });
1112
1146
  this.protocolError(
1113
1147
  ProtocolError.HandshakeFailed,
1114
1148
  "handshake metadata did not match schema"
1115
1149
  );
1150
+ conn.telemetry?.span.setStatus({
1151
+ code: import_api3.SpanStatusCode.ERROR,
1152
+ message: "handshake meta mismatch"
1153
+ });
1116
1154
  return false;
1117
1155
  }
1118
1156
  }
@@ -1122,7 +1160,7 @@ var ClientTransport = class extends Transport {
1122
1160
  to,
1123
1161
  session.id,
1124
1162
  metadata,
1125
- tracing
1163
+ getPropagationContext(session.telemetry.ctx)
1126
1164
  );
1127
1165
  log?.debug(`sending handshake request to ${to}`, {
1128
1166
  clientId: this.clientId,
@@ -1138,13 +1176,31 @@ var ClientTransport = class extends Transport {
1138
1176
  };
1139
1177
 
1140
1178
  // transport/impls/ws/connection.ts
1141
- var import_agnostic_ws = __toESM(require("agnostic-ws"), 1);
1142
1179
  var WebSocketConnection = class extends Connection {
1180
+ errorCb = null;
1181
+ closeCb = null;
1143
1182
  ws;
1144
1183
  constructor(ws) {
1145
1184
  super();
1146
1185
  this.ws = ws;
1147
1186
  this.ws.binaryType = "arraybuffer";
1187
+ let didError = false;
1188
+ this.ws.onerror = () => {
1189
+ didError = true;
1190
+ };
1191
+ this.ws.onclose = ({ code, reason }) => {
1192
+ if (didError && this.errorCb) {
1193
+ this.errorCb(
1194
+ new Error(
1195
+ `websocket closed with code and reason: ${code} - ${reason}`
1196
+ )
1197
+ );
1198
+ return;
1199
+ }
1200
+ if (this.closeCb) {
1201
+ this.closeCb();
1202
+ }
1203
+ };
1148
1204
  }
1149
1205
  addDataListener(cb) {
1150
1206
  this.ws.onmessage = (msg) => cb(msg.data);
@@ -1153,13 +1209,13 @@ var WebSocketConnection = class extends Connection {
1153
1209
  this.ws.onmessage = null;
1154
1210
  }
1155
1211
  addCloseListener(cb) {
1156
- this.ws.onclose = cb;
1212
+ this.closeCb = cb;
1157
1213
  }
1158
1214
  addErrorListener(cb) {
1159
- this.ws.onerror = (err) => cb(err.error);
1215
+ this.errorCb = cb;
1160
1216
  }
1161
1217
  send(payload) {
1162
- if (this.ws.readyState === import_agnostic_ws.default.OPEN) {
1218
+ if (this.ws.readyState === this.ws.OPEN) {
1163
1219
  this.ws.send(payload);
1164
1220
  return true;
1165
1221
  } else {
@@ -1176,7 +1232,7 @@ var WebSocketClientTransport = class extends ClientTransport {
1176
1232
  /**
1177
1233
  * A function that returns a Promise that resolves to a websocket URL.
1178
1234
  */
1179
- urlGetter;
1235
+ wsGetter;
1180
1236
  /**
1181
1237
  * Creates a new WebSocketClientTransport instance.
1182
1238
  * @param wsGetter A function that returns a Promise that resolves to a WebSocket instance.
@@ -1184,53 +1240,39 @@ var WebSocketClientTransport = class extends ClientTransport {
1184
1240
  * @param serverId The ID of the server this transport is connecting to.
1185
1241
  * @param providedOptions An optional object containing configuration options for the transport.
1186
1242
  */
1187
- constructor(urlGetter, clientId, providedOptions) {
1243
+ constructor(wsGetter, clientId, providedOptions) {
1188
1244
  super(clientId, providedOptions);
1189
- this.urlGetter = urlGetter;
1245
+ this.wsGetter = wsGetter;
1190
1246
  }
1191
1247
  async createNewOutgoingConnection(to) {
1192
- const wsRes = await new Promise((resolve) => {
1193
- log?.info(`establishing a new websocket to ${to}`, {
1194
- clientId: this.clientId,
1195
- connectedTo: to
1196
- });
1197
- Promise.resolve(this.urlGetter(to)).then((url) => new import_agnostic_ws2.default(url)).then((ws) => {
1198
- if (ws.readyState === import_agnostic_ws2.default.OPEN) {
1199
- resolve({ ws });
1200
- return;
1201
- }
1202
- if (ws.readyState === import_agnostic_ws2.default.CLOSING || ws.readyState === import_agnostic_ws2.default.CLOSED) {
1203
- resolve({ err: "ws is closing or closed" });
1204
- return;
1205
- }
1206
- ws.onopen = () => {
1207
- resolve({ ws });
1208
- };
1209
- ws.onclose = (evt) => {
1210
- resolve({ err: evt.reason });
1211
- };
1212
- ws.onerror = (evt) => {
1213
- const err = evt.error;
1214
- resolve({
1215
- err: `${err.name}: ${err.message}`
1216
- });
1217
- };
1218
- }).catch((e) => {
1219
- const reason = e instanceof Error ? e.message : "unknown reason";
1220
- resolve({ err: `couldn't get a new websocket: ${reason}` });
1221
- });
1248
+ log?.info(`establishing a new websocket to ${to}`, {
1249
+ clientId: this.clientId,
1250
+ connectedTo: to
1222
1251
  });
1223
- if ("ws" in wsRes) {
1224
- const conn = new WebSocketConnection(wsRes.ws);
1225
- log?.info(`raw websocket to ${to} ok, starting handshake`, {
1226
- clientId: this.clientId,
1227
- connectedTo: to
1228
- });
1229
- this.handleConnection(conn, to);
1230
- return conn;
1231
- } else {
1232
- throw new Error(wsRes.err);
1233
- }
1252
+ const ws = await this.wsGetter(to);
1253
+ await new Promise((resolve, reject) => {
1254
+ if (ws.readyState === ws.OPEN) {
1255
+ resolve();
1256
+ return;
1257
+ }
1258
+ if (ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED) {
1259
+ reject(new Error("ws is closing or closed"));
1260
+ return;
1261
+ }
1262
+ ws.onopen = () => {
1263
+ resolve();
1264
+ };
1265
+ ws.onclose = (evt) => {
1266
+ reject(new Error(evt.reason));
1267
+ };
1268
+ });
1269
+ const conn = new WebSocketConnection(ws);
1270
+ log?.info(`raw websocket to ${to} ok, starting handshake`, {
1271
+ clientId: this.clientId,
1272
+ connectedTo: to
1273
+ });
1274
+ this.handleConnection(conn, to);
1275
+ return conn;
1234
1276
  }
1235
1277
  };
1236
1278
  // Annotate the CommonJS export names for ESM import in node: