@replit/river 0.23.18 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -16
- package/dist/{chunk-AVL32IMG.js → chunk-AASMR3CQ.js} +20 -16
- package/dist/chunk-AASMR3CQ.js.map +1 -0
- package/dist/chunk-JA57I7MG.js +653 -0
- package/dist/chunk-JA57I7MG.js.map +1 -0
- package/dist/chunk-KX5PQRVN.js +382 -0
- package/dist/chunk-KX5PQRVN.js.map +1 -0
- package/dist/{chunk-EV5HW4IC.js → chunk-KYYB4DUR.js} +65 -53
- package/dist/chunk-KYYB4DUR.js.map +1 -0
- package/dist/chunk-NLQPPDOT.js +399 -0
- package/dist/chunk-NLQPPDOT.js.map +1 -0
- package/dist/{chunk-R2HAS3GM.js → chunk-PJGGC3LV.js} +55 -41
- package/dist/chunk-PJGGC3LV.js.map +1 -0
- package/dist/{chunk-7MJYOL32.js → chunk-RXJLI2OP.js} +15 -23
- package/dist/chunk-RXJLI2OP.js.map +1 -0
- package/dist/{chunk-6LCL2ZZF.js → chunk-TAH2GVTJ.js} +1 -1
- package/dist/chunk-TAH2GVTJ.js.map +1 -0
- package/dist/chunk-ZAT3R4CU.js +277 -0
- package/dist/chunk-ZAT3R4CU.js.map +1 -0
- package/dist/{client-5776a6bb.d.ts → client-ba0d3315.d.ts} +12 -15
- package/dist/{connection-bd35d442.d.ts → connection-c3a96d09.d.ts} +1 -5
- package/dist/connection-d33e3246.d.ts +11 -0
- package/dist/{handshake-a947c234.d.ts → handshake-cdead82a.d.ts} +148 -183
- package/dist/logging/index.cjs.map +1 -1
- package/dist/logging/index.d.cts +1 -1
- package/dist/logging/index.d.ts +1 -1
- package/dist/logging/index.js +1 -1
- package/dist/{index-ea74cdbb.d.ts → message-e6c560fd.d.ts} +2 -2
- package/dist/router/index.cjs +104 -63
- package/dist/router/index.cjs.map +1 -1
- package/dist/router/index.d.cts +11 -10
- package/dist/router/index.d.ts +11 -10
- package/dist/router/index.js +2 -2
- package/dist/server-2ef5e6ec.d.ts +42 -0
- package/dist/{services-38b3f758.d.ts → services-e1417b33.d.ts} +3 -3
- package/dist/transport/impls/uds/client.cjs +1246 -1230
- package/dist/transport/impls/uds/client.cjs.map +1 -1
- package/dist/transport/impls/uds/client.d.cts +4 -4
- package/dist/transport/impls/uds/client.d.ts +4 -4
- package/dist/transport/impls/uds/client.js +7 -13
- package/dist/transport/impls/uds/client.js.map +1 -1
- package/dist/transport/impls/uds/server.cjs +1298 -1151
- package/dist/transport/impls/uds/server.cjs.map +1 -1
- package/dist/transport/impls/uds/server.d.cts +4 -4
- package/dist/transport/impls/uds/server.d.ts +4 -4
- package/dist/transport/impls/uds/server.js +6 -6
- package/dist/transport/impls/ws/client.cjs +976 -965
- package/dist/transport/impls/ws/client.cjs.map +1 -1
- package/dist/transport/impls/ws/client.d.cts +4 -4
- package/dist/transport/impls/ws/client.d.ts +4 -4
- package/dist/transport/impls/ws/client.js +6 -7
- package/dist/transport/impls/ws/client.js.map +1 -1
- package/dist/transport/impls/ws/server.cjs +1182 -1047
- package/dist/transport/impls/ws/server.cjs.map +1 -1
- package/dist/transport/impls/ws/server.d.cts +4 -4
- package/dist/transport/impls/ws/server.d.ts +4 -4
- package/dist/transport/impls/ws/server.js +6 -6
- package/dist/transport/index.cjs +1433 -1360
- package/dist/transport/index.cjs.map +1 -1
- package/dist/transport/index.d.cts +4 -4
- package/dist/transport/index.d.ts +4 -4
- package/dist/transport/index.js +9 -9
- package/dist/util/testHelpers.cjs +743 -310
- package/dist/util/testHelpers.cjs.map +1 -1
- package/dist/util/testHelpers.d.cts +9 -6
- package/dist/util/testHelpers.d.ts +9 -6
- package/dist/util/testHelpers.js +33 -10
- package/dist/util/testHelpers.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-6LCL2ZZF.js.map +0 -1
- package/dist/chunk-7MJYOL32.js.map +0 -1
- package/dist/chunk-AVL32IMG.js.map +0 -1
- package/dist/chunk-DPKOJQWF.js +0 -476
- package/dist/chunk-DPKOJQWF.js.map +0 -1
- package/dist/chunk-EV5HW4IC.js.map +0 -1
- package/dist/chunk-J6N6H2WU.js +0 -476
- package/dist/chunk-J6N6H2WU.js.map +0 -1
- package/dist/chunk-MW5JXLHY.js +0 -348
- package/dist/chunk-MW5JXLHY.js.map +0 -1
- package/dist/chunk-R2HAS3GM.js.map +0 -1
- package/dist/chunk-RJOWZIWB.js +0 -335
- package/dist/chunk-RJOWZIWB.js.map +0 -1
- package/dist/connection-df85db7e.d.ts +0 -17
- package/dist/server-53cd5b7e.d.ts +0 -24
|
@@ -25,11 +25,19 @@ __export(client_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(client_exports);
|
|
26
26
|
|
|
27
27
|
// transport/client.ts
|
|
28
|
-
var
|
|
28
|
+
var import_api3 = require("@opentelemetry/api");
|
|
29
29
|
|
|
30
30
|
// transport/message.ts
|
|
31
31
|
var import_typebox = require("@sinclair/typebox");
|
|
32
|
+
|
|
33
|
+
// transport/id.ts
|
|
32
34
|
var import_nanoid = require("nanoid");
|
|
35
|
+
var alphabet = (0, import_nanoid.customAlphabet)(
|
|
36
|
+
"1234567890abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ"
|
|
37
|
+
);
|
|
38
|
+
var generateId = () => alphabet(12);
|
|
39
|
+
|
|
40
|
+
// transport/message.ts
|
|
33
41
|
var TransportMessageSchema = (t) => import_typebox.Type.Object({
|
|
34
42
|
id: import_typebox.Type.String(),
|
|
35
43
|
from: import_typebox.Type.String(),
|
|
@@ -64,18 +72,29 @@ var ControlMessageHandshakeRequestSchema = import_typebox.Type.Object({
|
|
|
64
72
|
* used by the server to know whether this is a new or a reestablished connection, and whether it
|
|
65
73
|
* is compatible with what it already has.
|
|
66
74
|
*/
|
|
67
|
-
expectedSessionState: import_typebox.Type.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
})
|
|
76
|
-
),
|
|
75
|
+
expectedSessionState: import_typebox.Type.Object({
|
|
76
|
+
// what the client expects the server to send next
|
|
77
|
+
nextExpectedSeq: import_typebox.Type.Integer(),
|
|
78
|
+
// TODO: remove optional once we know all servers
|
|
79
|
+
// are nextSentSeq here
|
|
80
|
+
// what the server expects the client to send next
|
|
81
|
+
nextSentSeq: import_typebox.Type.Optional(import_typebox.Type.Integer())
|
|
82
|
+
}),
|
|
77
83
|
metadata: import_typebox.Type.Optional(import_typebox.Type.Unknown())
|
|
78
84
|
});
|
|
85
|
+
var HandshakeErrorRetriableResponseCodes = import_typebox.Type.Union([
|
|
86
|
+
import_typebox.Type.Literal("SESSION_STATE_MISMATCH")
|
|
87
|
+
]);
|
|
88
|
+
var HandshakeErrorFatalResponseCodes = import_typebox.Type.Union([
|
|
89
|
+
import_typebox.Type.Literal("MALFORMED_HANDSHAKE_META"),
|
|
90
|
+
import_typebox.Type.Literal("MALFORMED_HANDSHAKE"),
|
|
91
|
+
import_typebox.Type.Literal("PROTOCOL_VERSION_MISMATCH"),
|
|
92
|
+
import_typebox.Type.Literal("REJECTED_BY_CUSTOM_HANDLER")
|
|
93
|
+
]);
|
|
94
|
+
var HandshakeErrorResponseCodes = import_typebox.Type.Union([
|
|
95
|
+
HandshakeErrorRetriableResponseCodes,
|
|
96
|
+
HandshakeErrorFatalResponseCodes
|
|
97
|
+
]);
|
|
79
98
|
var ControlMessageHandshakeResponseSchema = import_typebox.Type.Object({
|
|
80
99
|
type: import_typebox.Type.Literal("HANDSHAKE_RESP"),
|
|
81
100
|
status: import_typebox.Type.Union([
|
|
@@ -85,7 +104,10 @@ var ControlMessageHandshakeResponseSchema = import_typebox.Type.Object({
|
|
|
85
104
|
}),
|
|
86
105
|
import_typebox.Type.Object({
|
|
87
106
|
ok: import_typebox.Type.Literal(false),
|
|
88
|
-
reason: import_typebox.Type.String()
|
|
107
|
+
reason: import_typebox.Type.String(),
|
|
108
|
+
// TODO: remove optional once we know all servers
|
|
109
|
+
// are sending code here
|
|
110
|
+
code: import_typebox.Type.Optional(HandshakeErrorResponseCodes)
|
|
89
111
|
})
|
|
90
112
|
])
|
|
91
113
|
});
|
|
@@ -107,12 +129,12 @@ function handshakeRequestMessage({
|
|
|
107
129
|
tracing
|
|
108
130
|
}) {
|
|
109
131
|
return {
|
|
110
|
-
id: (
|
|
132
|
+
id: generateId(),
|
|
111
133
|
from,
|
|
112
134
|
to,
|
|
113
135
|
seq: 0,
|
|
114
136
|
ack: 0,
|
|
115
|
-
streamId: (
|
|
137
|
+
streamId: generateId(),
|
|
116
138
|
controlFlags: 0,
|
|
117
139
|
tracing,
|
|
118
140
|
payload: {
|
|
@@ -124,7 +146,6 @@ function handshakeRequestMessage({
|
|
|
124
146
|
}
|
|
125
147
|
};
|
|
126
148
|
}
|
|
127
|
-
var SESSION_STATE_MISMATCH = "session state mismatch";
|
|
128
149
|
function isAck(controlFlag) {
|
|
129
150
|
return (controlFlag & 1 /* AckBit */) === 1 /* AckBit */;
|
|
130
151
|
}
|
|
@@ -186,7 +207,8 @@ var defaultTransportOptions = {
|
|
|
186
207
|
heartbeatIntervalMs: 1e3,
|
|
187
208
|
heartbeatsUntilDead: 2,
|
|
188
209
|
sessionDisconnectGraceMs: 5e3,
|
|
189
|
-
|
|
210
|
+
connectionTimeoutMs: 2e3,
|
|
211
|
+
handshakeTimeoutMs: 1e3,
|
|
190
212
|
codec: NaiveJsonCodec
|
|
191
213
|
};
|
|
192
214
|
var defaultConnectionRetryOptions = {
|
|
@@ -275,9 +297,6 @@ var LeakyBucketRateLimit = class {
|
|
|
275
297
|
}
|
|
276
298
|
};
|
|
277
299
|
|
|
278
|
-
// transport/transport.ts
|
|
279
|
-
var import_value = require("@sinclair/typebox/value");
|
|
280
|
-
|
|
281
300
|
// logging/log.ts
|
|
282
301
|
var LoggingLevels = {
|
|
283
302
|
debug: -1,
|
|
@@ -368,14 +387,222 @@ var EventDispatcher = class {
|
|
|
368
387
|
}
|
|
369
388
|
};
|
|
370
389
|
|
|
371
|
-
// transport/
|
|
372
|
-
var
|
|
390
|
+
// transport/sessionStateMachine/common.ts
|
|
391
|
+
var import_value = require("@sinclair/typebox/value");
|
|
392
|
+
var ERR_CONSUMED = `session state has been consumed and is no longer valid`;
|
|
393
|
+
var StateMachineState = class {
|
|
394
|
+
/*
|
|
395
|
+
* Whether this state has been consumed
|
|
396
|
+
* and we've moved on to another state
|
|
397
|
+
*/
|
|
398
|
+
_isConsumed;
|
|
399
|
+
close() {
|
|
400
|
+
this._handleClose();
|
|
401
|
+
}
|
|
402
|
+
constructor() {
|
|
403
|
+
this._isConsumed = false;
|
|
404
|
+
return new Proxy(this, {
|
|
405
|
+
get(target, prop) {
|
|
406
|
+
if (prop === "_isConsumed" || prop === "id" || prop === "state") {
|
|
407
|
+
return Reflect.get(target, prop);
|
|
408
|
+
}
|
|
409
|
+
if (prop === "_handleStateExit") {
|
|
410
|
+
return () => {
|
|
411
|
+
target._isConsumed = true;
|
|
412
|
+
target._handleStateExit();
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
if (prop === "_handleClose") {
|
|
416
|
+
return () => {
|
|
417
|
+
target._handleStateExit();
|
|
418
|
+
target._handleClose();
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
if (target._isConsumed) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
`${ERR_CONSUMED}: getting ${prop.toString()} on consumed state`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
return Reflect.get(target, prop);
|
|
427
|
+
},
|
|
428
|
+
set(target, prop, value) {
|
|
429
|
+
if (target._isConsumed) {
|
|
430
|
+
throw new Error(
|
|
431
|
+
`${ERR_CONSUMED}: setting ${prop.toString()} on consumed state`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
return Reflect.set(target, prop, value);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
var CommonSession = class extends StateMachineState {
|
|
440
|
+
from;
|
|
441
|
+
options;
|
|
442
|
+
log;
|
|
443
|
+
constructor(from, options, log) {
|
|
444
|
+
super();
|
|
445
|
+
this.from = from;
|
|
446
|
+
this.options = options;
|
|
447
|
+
this.log = log;
|
|
448
|
+
}
|
|
449
|
+
parseMsg(msg) {
|
|
450
|
+
const parsedMsg = this.options.codec.fromBuffer(msg);
|
|
451
|
+
if (parsedMsg === null) {
|
|
452
|
+
const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));
|
|
453
|
+
this.log?.error(
|
|
454
|
+
`received malformed msg: ${decodedBuffer}`,
|
|
455
|
+
this.loggingMetadata
|
|
456
|
+
);
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
|
|
460
|
+
this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
|
|
461
|
+
...this.loggingMetadata,
|
|
462
|
+
validationErrors: [
|
|
463
|
+
...import_value.Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
|
|
464
|
+
]
|
|
465
|
+
});
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
return parsedMsg;
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
var IdentifiedSession = class extends CommonSession {
|
|
472
|
+
id;
|
|
473
|
+
telemetry;
|
|
474
|
+
to;
|
|
475
|
+
/**
|
|
476
|
+
* Index of the message we will send next (excluding handshake)
|
|
477
|
+
*/
|
|
478
|
+
seq;
|
|
479
|
+
/**
|
|
480
|
+
* Number of unique messages we've received this session (excluding handshake)
|
|
481
|
+
*/
|
|
482
|
+
ack;
|
|
483
|
+
sendBuffer;
|
|
484
|
+
constructor(id, from, to, seq, ack, sendBuffer, telemetry, options, log) {
|
|
485
|
+
super(from, options, log);
|
|
486
|
+
this.id = id;
|
|
487
|
+
this.to = to;
|
|
488
|
+
this.seq = seq;
|
|
489
|
+
this.ack = ack;
|
|
490
|
+
this.sendBuffer = sendBuffer;
|
|
491
|
+
this.telemetry = telemetry;
|
|
492
|
+
this.log = log;
|
|
493
|
+
}
|
|
494
|
+
get loggingMetadata() {
|
|
495
|
+
const spanContext = this.telemetry.span.spanContext();
|
|
496
|
+
return {
|
|
497
|
+
clientId: this.from,
|
|
498
|
+
connectedTo: this.to,
|
|
499
|
+
sessionId: this.id,
|
|
500
|
+
telemetry: {
|
|
501
|
+
traceId: spanContext.traceId,
|
|
502
|
+
spanId: spanContext.spanId
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
constructMsg(partialMsg) {
|
|
507
|
+
const msg = {
|
|
508
|
+
...partialMsg,
|
|
509
|
+
id: generateId(),
|
|
510
|
+
to: this.to,
|
|
511
|
+
from: this.from,
|
|
512
|
+
seq: this.seq,
|
|
513
|
+
ack: this.ack
|
|
514
|
+
};
|
|
515
|
+
this.seq++;
|
|
516
|
+
return msg;
|
|
517
|
+
}
|
|
518
|
+
nextSeq() {
|
|
519
|
+
return this.sendBuffer.length > 0 ? this.sendBuffer[0].seq : this.seq;
|
|
520
|
+
}
|
|
521
|
+
send(msg) {
|
|
522
|
+
const constructedMsg = this.constructMsg(msg);
|
|
523
|
+
this.sendBuffer.push(constructedMsg);
|
|
524
|
+
return constructedMsg.id;
|
|
525
|
+
}
|
|
526
|
+
_handleStateExit() {
|
|
527
|
+
}
|
|
528
|
+
_handleClose() {
|
|
529
|
+
this.sendBuffer.length = 0;
|
|
530
|
+
this.telemetry.span.end();
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// transport/sessionStateMachine/SessionConnecting.ts
|
|
535
|
+
var SessionConnecting = class extends IdentifiedSession {
|
|
536
|
+
state = "Connecting" /* Connecting */;
|
|
537
|
+
connPromise;
|
|
538
|
+
listeners;
|
|
539
|
+
connectionTimeout;
|
|
540
|
+
constructor(connPromise, listeners, ...args) {
|
|
541
|
+
super(...args);
|
|
542
|
+
this.connPromise = connPromise;
|
|
543
|
+
this.listeners = listeners;
|
|
544
|
+
this.connectionTimeout = setTimeout(() => {
|
|
545
|
+
listeners.onConnectionTimeout();
|
|
546
|
+
}, this.options.connectionTimeoutMs);
|
|
547
|
+
connPromise.then(
|
|
548
|
+
(conn) => {
|
|
549
|
+
if (this._isConsumed)
|
|
550
|
+
return;
|
|
551
|
+
listeners.onConnectionEstablished(conn);
|
|
552
|
+
},
|
|
553
|
+
(err) => {
|
|
554
|
+
if (this._isConsumed)
|
|
555
|
+
return;
|
|
556
|
+
listeners.onConnectionFailed(err);
|
|
557
|
+
}
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
// close a pending connection if it resolves, ignore errors if the promise
|
|
561
|
+
// ends up rejected anyways
|
|
562
|
+
bestEffortClose() {
|
|
563
|
+
void this.connPromise.then((conn) => conn.close()).catch(() => {
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
_handleStateExit() {
|
|
567
|
+
super._handleStateExit();
|
|
568
|
+
clearTimeout(this.connectionTimeout);
|
|
569
|
+
this.connectionTimeout = void 0;
|
|
570
|
+
}
|
|
571
|
+
_handleClose() {
|
|
572
|
+
this.bestEffortClose();
|
|
573
|
+
super._handleClose();
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// transport/sessionStateMachine/SessionNoConnection.ts
|
|
578
|
+
var SessionNoConnection = class extends IdentifiedSession {
|
|
579
|
+
state = "NoConnection" /* NoConnection */;
|
|
580
|
+
listeners;
|
|
581
|
+
gracePeriodTimeout;
|
|
582
|
+
constructor(listeners, ...args) {
|
|
583
|
+
super(...args);
|
|
584
|
+
this.listeners = listeners;
|
|
585
|
+
this.gracePeriodTimeout = setTimeout(() => {
|
|
586
|
+
this.listeners.onSessionGracePeriodElapsed();
|
|
587
|
+
}, this.options.sessionDisconnectGraceMs);
|
|
588
|
+
}
|
|
589
|
+
_handleClose() {
|
|
590
|
+
super._handleClose();
|
|
591
|
+
}
|
|
592
|
+
_handleStateExit() {
|
|
593
|
+
super._handleStateExit();
|
|
594
|
+
if (this.gracePeriodTimeout) {
|
|
595
|
+
clearTimeout(this.gracePeriodTimeout);
|
|
596
|
+
this.gracePeriodTimeout = void 0;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
};
|
|
373
600
|
|
|
374
601
|
// tracing/index.ts
|
|
375
602
|
var import_api = require("@opentelemetry/api");
|
|
376
603
|
|
|
377
604
|
// package.json
|
|
378
|
-
var version = "0.
|
|
605
|
+
var version = "0.24.0";
|
|
379
606
|
|
|
380
607
|
// tracing/index.ts
|
|
381
608
|
function getPropagationContext(ctx) {
|
|
@@ -386,16 +613,16 @@ function getPropagationContext(ctx) {
|
|
|
386
613
|
import_api.propagation.inject(ctx, tracing);
|
|
387
614
|
return tracing;
|
|
388
615
|
}
|
|
389
|
-
function createSessionTelemetryInfo(
|
|
616
|
+
function createSessionTelemetryInfo(sessionId, to, from, propagationCtx) {
|
|
390
617
|
const parentCtx = propagationCtx ? import_api.propagation.extract(import_api.context.active(), propagationCtx) : import_api.context.active();
|
|
391
618
|
const span = tracer.startSpan(
|
|
392
|
-
`session ${
|
|
619
|
+
`session ${sessionId}`,
|
|
393
620
|
{
|
|
394
621
|
attributes: {
|
|
395
622
|
component: "river",
|
|
396
|
-
"river.session.id":
|
|
397
|
-
"river.session.to":
|
|
398
|
-
"river.session.from":
|
|
623
|
+
"river.session.id": sessionId,
|
|
624
|
+
"river.session.to": to,
|
|
625
|
+
"river.session.from": from
|
|
399
626
|
}
|
|
400
627
|
},
|
|
401
628
|
parentCtx
|
|
@@ -403,173 +630,155 @@ function createSessionTelemetryInfo(session, propagationCtx) {
|
|
|
403
630
|
const ctx = import_api.trace.setSpan(parentCtx, span);
|
|
404
631
|
return { span, ctx };
|
|
405
632
|
}
|
|
406
|
-
function createConnectionTelemetryInfo(connection, info) {
|
|
407
|
-
const span = tracer.startSpan(
|
|
408
|
-
`connection ${connection.id}`,
|
|
409
|
-
{
|
|
410
|
-
attributes: {
|
|
411
|
-
component: "river",
|
|
412
|
-
"river.connection.id": connection.id
|
|
413
|
-
},
|
|
414
|
-
links: [{ context: info.span.spanContext() }]
|
|
415
|
-
},
|
|
416
|
-
info.ctx
|
|
417
|
-
);
|
|
418
|
-
const ctx = import_api.trace.setSpan(info.ctx, span);
|
|
419
|
-
return { span, ctx };
|
|
420
|
-
}
|
|
421
633
|
var tracer = import_api.trace.getTracer("river", version);
|
|
422
634
|
var tracing_default = tracer;
|
|
423
635
|
|
|
424
|
-
// transport/
|
|
425
|
-
var
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
this.
|
|
636
|
+
// transport/sessionStateMachine/SessionWaitingForHandshake.ts
|
|
637
|
+
var SessionWaitingForHandshake = class extends CommonSession {
|
|
638
|
+
state = "WaitingForHandshake" /* WaitingForHandshake */;
|
|
639
|
+
conn;
|
|
640
|
+
listeners;
|
|
641
|
+
handshakeTimeout;
|
|
642
|
+
constructor(conn, listeners, ...args) {
|
|
643
|
+
super(...args);
|
|
644
|
+
this.conn = conn;
|
|
645
|
+
this.listeners = listeners;
|
|
646
|
+
this.handshakeTimeout = setTimeout(() => {
|
|
647
|
+
listeners.onHandshakeTimeout();
|
|
648
|
+
}, this.options.handshakeTimeoutMs);
|
|
649
|
+
this.conn.addDataListener(this.onHandshakeData);
|
|
650
|
+
this.conn.addErrorListener(listeners.onConnectionErrored);
|
|
651
|
+
this.conn.addCloseListener(listeners.onConnectionClosed);
|
|
433
652
|
}
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
traceId: spanContext.traceId,
|
|
440
|
-
spanId: spanContext.spanId
|
|
441
|
-
};
|
|
653
|
+
onHandshakeData = (msg) => {
|
|
654
|
+
const parsedMsg = this.parseMsg(msg);
|
|
655
|
+
if (parsedMsg === null) {
|
|
656
|
+
this.listeners.onInvalidHandshake("could not parse message");
|
|
657
|
+
return;
|
|
442
658
|
}
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
var Session = class {
|
|
447
|
-
codec;
|
|
448
|
-
options;
|
|
449
|
-
telemetry;
|
|
450
|
-
/**
|
|
451
|
-
* The buffer of messages that have been sent but not yet acknowledged.
|
|
452
|
-
*/
|
|
453
|
-
sendBuffer = [];
|
|
454
|
-
/**
|
|
455
|
-
* The active connection associated with this session
|
|
456
|
-
*/
|
|
457
|
-
connection;
|
|
458
|
-
/**
|
|
459
|
-
* A connection that is currently undergoing handshaking. Used to distinguish between the active
|
|
460
|
-
* connection, but still be able to close it if needed.
|
|
461
|
-
*/
|
|
462
|
-
handshakingConnection;
|
|
463
|
-
from;
|
|
464
|
-
to;
|
|
465
|
-
/**
|
|
466
|
-
* The unique ID of this session.
|
|
467
|
-
*/
|
|
468
|
-
id;
|
|
469
|
-
/**
|
|
470
|
-
* What the other side advertised as their session ID
|
|
471
|
-
* for this session.
|
|
472
|
-
*/
|
|
473
|
-
advertisedSessionId;
|
|
474
|
-
/**
|
|
475
|
-
* Number of messages we've sent along this session (excluding handshake and acks)
|
|
476
|
-
*/
|
|
477
|
-
seq = 0;
|
|
478
|
-
/**
|
|
479
|
-
* Number of unique messages we've received this session (excluding handshake and acks)
|
|
480
|
-
*/
|
|
481
|
-
ack = 0;
|
|
482
|
-
/**
|
|
483
|
-
* The grace period between when the inner connection is disconnected
|
|
484
|
-
* and when we should consider the entire session disconnected.
|
|
485
|
-
*/
|
|
486
|
-
disconnectionGrace;
|
|
487
|
-
/**
|
|
488
|
-
* Number of heartbeats we've sent without a response.
|
|
489
|
-
*/
|
|
490
|
-
heartbeatMisses;
|
|
491
|
-
/**
|
|
492
|
-
* The interval for sending heartbeats.
|
|
493
|
-
*/
|
|
494
|
-
heartbeat;
|
|
495
|
-
log;
|
|
496
|
-
constructor(conn, from, to, options, propagationCtx) {
|
|
497
|
-
this.id = `session-${nanoid2(12)}`;
|
|
498
|
-
this.options = options;
|
|
499
|
-
this.from = from;
|
|
500
|
-
this.to = to;
|
|
501
|
-
this.connection = conn;
|
|
502
|
-
this.codec = options.codec;
|
|
503
|
-
this.heartbeatMisses = 0;
|
|
504
|
-
this.heartbeat = setInterval(
|
|
505
|
-
() => this.sendHeartbeat(),
|
|
506
|
-
options.heartbeatIntervalMs
|
|
507
|
-
);
|
|
508
|
-
this.telemetry = createSessionTelemetryInfo(this, propagationCtx);
|
|
509
|
-
}
|
|
510
|
-
bindLogger(log) {
|
|
511
|
-
this.log = log;
|
|
512
|
-
}
|
|
659
|
+
this.listeners.onHandshake(parsedMsg);
|
|
660
|
+
};
|
|
513
661
|
get loggingMetadata() {
|
|
514
|
-
const spanContext = this.telemetry.span.spanContext();
|
|
515
662
|
return {
|
|
516
663
|
clientId: this.from,
|
|
517
|
-
|
|
518
|
-
sessionId: this.id,
|
|
519
|
-
connId: this.connection?.id,
|
|
520
|
-
telemetry: {
|
|
521
|
-
traceId: spanContext.traceId,
|
|
522
|
-
spanId: spanContext.spanId
|
|
523
|
-
}
|
|
664
|
+
connId: this.conn.id
|
|
524
665
|
};
|
|
525
666
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
667
|
+
sendHandshake(msg) {
|
|
668
|
+
return this.conn.send(this.options.codec.toBuffer(msg));
|
|
669
|
+
}
|
|
670
|
+
_handleStateExit() {
|
|
671
|
+
this.conn.removeDataListener(this.onHandshakeData);
|
|
672
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
673
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
674
|
+
clearTimeout(this.handshakeTimeout);
|
|
675
|
+
this.handshakeTimeout = void 0;
|
|
676
|
+
}
|
|
677
|
+
_handleClose() {
|
|
678
|
+
this.conn.close();
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
// transport/sessionStateMachine/SessionHandshaking.ts
|
|
683
|
+
var SessionHandshaking = class extends IdentifiedSession {
|
|
684
|
+
state = "Handshaking" /* Handshaking */;
|
|
685
|
+
conn;
|
|
686
|
+
listeners;
|
|
687
|
+
handshakeTimeout;
|
|
688
|
+
constructor(conn, listeners, ...args) {
|
|
689
|
+
super(...args);
|
|
690
|
+
this.conn = conn;
|
|
691
|
+
this.listeners = listeners;
|
|
692
|
+
this.handshakeTimeout = setTimeout(() => {
|
|
693
|
+
listeners.onHandshakeTimeout();
|
|
694
|
+
}, this.options.handshakeTimeoutMs);
|
|
695
|
+
this.conn.addDataListener(this.onHandshakeData);
|
|
696
|
+
this.conn.addErrorListener(listeners.onConnectionErrored);
|
|
697
|
+
this.conn.addCloseListener(listeners.onConnectionClosed);
|
|
698
|
+
}
|
|
699
|
+
onHandshakeData = (msg) => {
|
|
700
|
+
const parsedMsg = this.parseMsg(msg);
|
|
701
|
+
if (parsedMsg === null) {
|
|
702
|
+
this.listeners.onInvalidHandshake("could not parse message");
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
this.listeners.onHandshake(parsedMsg);
|
|
706
|
+
};
|
|
707
|
+
sendHandshake(msg) {
|
|
708
|
+
return this.conn.send(this.options.codec.toBuffer(msg));
|
|
709
|
+
}
|
|
710
|
+
_handleStateExit() {
|
|
711
|
+
super._handleStateExit();
|
|
712
|
+
this.conn.removeDataListener(this.onHandshakeData);
|
|
713
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
714
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
715
|
+
clearTimeout(this.handshakeTimeout);
|
|
716
|
+
}
|
|
717
|
+
_handleClose() {
|
|
718
|
+
super._handleClose();
|
|
719
|
+
this.conn.close();
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
// transport/sessionStateMachine/SessionConnected.ts
|
|
724
|
+
var import_api2 = require("@opentelemetry/api");
|
|
725
|
+
var SessionConnected = class extends IdentifiedSession {
|
|
726
|
+
state = "Connected" /* Connected */;
|
|
727
|
+
conn;
|
|
728
|
+
listeners;
|
|
729
|
+
heartbeatHandle;
|
|
730
|
+
heartbeatMisses = 0;
|
|
731
|
+
get isActivelyHeartbeating() {
|
|
732
|
+
return this.heartbeatHandle !== void 0;
|
|
733
|
+
}
|
|
734
|
+
updateBookkeeping(ack, seq) {
|
|
735
|
+
this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
|
|
736
|
+
this.ack = seq + 1;
|
|
737
|
+
this.heartbeatMisses = 0;
|
|
738
|
+
}
|
|
534
739
|
send(msg) {
|
|
535
|
-
const
|
|
536
|
-
this.
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
transportMessage: fullMsg
|
|
549
|
-
}
|
|
550
|
-
);
|
|
551
|
-
} else {
|
|
740
|
+
const constructedMsg = this.constructMsg(msg);
|
|
741
|
+
this.sendBuffer.push(constructedMsg);
|
|
742
|
+
this.conn.send(this.options.codec.toBuffer(constructedMsg));
|
|
743
|
+
return constructedMsg.id;
|
|
744
|
+
}
|
|
745
|
+
constructor(conn, listeners, ...args) {
|
|
746
|
+
super(...args);
|
|
747
|
+
this.conn = conn;
|
|
748
|
+
this.listeners = listeners;
|
|
749
|
+
this.conn.addDataListener(this.onMessageData);
|
|
750
|
+
this.conn.addCloseListener(listeners.onConnectionClosed);
|
|
751
|
+
this.conn.addErrorListener(listeners.onConnectionErrored);
|
|
752
|
+
if (this.sendBuffer.length > 0) {
|
|
552
753
|
this.log?.debug(
|
|
553
|
-
`
|
|
554
|
-
|
|
754
|
+
`sending ${this.sendBuffer.length} buffered messages`,
|
|
755
|
+
this.loggingMetadata
|
|
555
756
|
);
|
|
556
757
|
}
|
|
557
|
-
|
|
758
|
+
for (const msg of this.sendBuffer) {
|
|
759
|
+
conn.send(this.options.codec.toBuffer(msg));
|
|
760
|
+
}
|
|
558
761
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
if (this.
|
|
762
|
+
startActiveHeartbeat() {
|
|
763
|
+
this.heartbeatHandle = setInterval(() => {
|
|
764
|
+
const misses = this.heartbeatMisses;
|
|
765
|
+
const missDuration = misses * this.options.heartbeatIntervalMs;
|
|
766
|
+
if (misses >= this.options.heartbeatsUntilDead) {
|
|
564
767
|
this.log?.info(
|
|
565
768
|
`closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
|
|
566
769
|
this.loggingMetadata
|
|
567
770
|
);
|
|
568
771
|
this.telemetry.span.addEvent("closing connection due to inactivity");
|
|
569
|
-
this.
|
|
772
|
+
this.conn.close();
|
|
773
|
+
clearInterval(this.heartbeatHandle);
|
|
774
|
+
this.heartbeatHandle = void 0;
|
|
775
|
+
return;
|
|
570
776
|
}
|
|
571
|
-
|
|
572
|
-
|
|
777
|
+
this.sendHeartbeat();
|
|
778
|
+
this.heartbeatMisses++;
|
|
779
|
+
}, this.options.heartbeatIntervalMs);
|
|
780
|
+
}
|
|
781
|
+
sendHeartbeat() {
|
|
573
782
|
this.send({
|
|
574
783
|
streamId: "heartbeat",
|
|
575
784
|
controlFlags: 1 /* AckBit */,
|
|
@@ -577,186 +786,255 @@ var Session = class {
|
|
|
577
786
|
type: "ACK"
|
|
578
787
|
}
|
|
579
788
|
});
|
|
580
|
-
this.heartbeatMisses++;
|
|
581
|
-
}
|
|
582
|
-
resetBufferedMessages() {
|
|
583
|
-
this.sendBuffer = [];
|
|
584
|
-
this.seq = 0;
|
|
585
|
-
this.ack = 0;
|
|
586
789
|
}
|
|
587
|
-
|
|
588
|
-
this.
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
message: errMsg
|
|
604
|
-
});
|
|
605
|
-
this.log?.error(errMsg, {
|
|
790
|
+
onMessageData = (msg) => {
|
|
791
|
+
const parsedMsg = this.parseMsg(msg);
|
|
792
|
+
if (parsedMsg === null)
|
|
793
|
+
return;
|
|
794
|
+
if (parsedMsg.seq !== this.ack) {
|
|
795
|
+
if (parsedMsg.seq < this.ack) {
|
|
796
|
+
this.log?.debug(
|
|
797
|
+
`received duplicate msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack}), discarding`,
|
|
798
|
+
{
|
|
799
|
+
...this.loggingMetadata,
|
|
800
|
+
transportMessage: parsedMsg
|
|
801
|
+
}
|
|
802
|
+
);
|
|
803
|
+
} else {
|
|
804
|
+
const reason = `received out-of-order msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack})`;
|
|
805
|
+
this.log?.error(reason, {
|
|
606
806
|
...this.loggingMetadata,
|
|
607
|
-
transportMessage:
|
|
608
|
-
connId: conn.id,
|
|
807
|
+
transportMessage: parsedMsg,
|
|
609
808
|
tags: ["invariant-violation"]
|
|
610
809
|
});
|
|
611
|
-
|
|
612
|
-
|
|
810
|
+
this.telemetry.span.setStatus({
|
|
811
|
+
code: import_api2.SpanStatusCode.ERROR,
|
|
812
|
+
message: reason
|
|
813
|
+
});
|
|
814
|
+
this.listeners.onInvalidMessage(reason);
|
|
613
815
|
}
|
|
816
|
+
return;
|
|
614
817
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
818
|
+
this.log?.debug(`received msg`, {
|
|
819
|
+
...this.loggingMetadata,
|
|
820
|
+
transportMessage: parsedMsg
|
|
821
|
+
});
|
|
822
|
+
this.updateBookkeeping(parsedMsg.ack, parsedMsg.seq);
|
|
823
|
+
if (!isAck(parsedMsg.controlFlags)) {
|
|
824
|
+
this.listeners.onMessage(parsedMsg);
|
|
622
825
|
return;
|
|
623
826
|
}
|
|
624
|
-
this.
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if (this.
|
|
629
|
-
|
|
630
|
-
this.log?.info(
|
|
631
|
-
`closing old inner connection from session to ${this.to}`,
|
|
632
|
-
this.loggingMetadata
|
|
633
|
-
);
|
|
634
|
-
this.connection.close();
|
|
635
|
-
this.connection = void 0;
|
|
636
|
-
}
|
|
637
|
-
replaceWithNewConnection(newConn, isTransparentReconnect) {
|
|
638
|
-
this.closeStaleConnection(newConn);
|
|
639
|
-
this.cancelGrace();
|
|
640
|
-
if (isTransparentReconnect) {
|
|
641
|
-
this.sendBufferedMessages(newConn);
|
|
827
|
+
this.log?.debug(`discarding msg (ack bit set)`, {
|
|
828
|
+
...this.loggingMetadata,
|
|
829
|
+
transportMessage: parsedMsg
|
|
830
|
+
});
|
|
831
|
+
if (!this.isActivelyHeartbeating) {
|
|
832
|
+
this.sendHeartbeat();
|
|
642
833
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
this.
|
|
834
|
+
};
|
|
835
|
+
_handleStateExit() {
|
|
836
|
+
super._handleStateExit();
|
|
837
|
+
this.conn.removeDataListener(this.onMessageData);
|
|
838
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
839
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
840
|
+
clearInterval(this.heartbeatHandle);
|
|
841
|
+
this.heartbeatHandle = void 0;
|
|
842
|
+
}
|
|
843
|
+
_handleClose() {
|
|
844
|
+
super._handleClose();
|
|
845
|
+
this.conn.close();
|
|
648
846
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
// transport/sessionStateMachine/transitions.ts
|
|
850
|
+
function inheritSharedSession(session) {
|
|
851
|
+
return [
|
|
852
|
+
session.id,
|
|
853
|
+
session.from,
|
|
854
|
+
session.to,
|
|
855
|
+
session.seq,
|
|
856
|
+
session.ack,
|
|
857
|
+
session.sendBuffer,
|
|
858
|
+
session.telemetry,
|
|
859
|
+
session.options,
|
|
860
|
+
session.log
|
|
861
|
+
];
|
|
862
|
+
}
|
|
863
|
+
var SessionStateGraph = {
|
|
864
|
+
entrypoints: {
|
|
865
|
+
NoConnection(to, from, listeners, options, log) {
|
|
866
|
+
const id = `session-${generateId()}`;
|
|
867
|
+
const telemetry = createSessionTelemetryInfo(id, to, from);
|
|
868
|
+
const sendBuffer = [];
|
|
869
|
+
const session = new SessionNoConnection(
|
|
870
|
+
listeners,
|
|
871
|
+
id,
|
|
872
|
+
from,
|
|
873
|
+
to,
|
|
874
|
+
0,
|
|
875
|
+
0,
|
|
876
|
+
sendBuffer,
|
|
877
|
+
telemetry,
|
|
878
|
+
options,
|
|
879
|
+
log
|
|
659
880
|
);
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
881
|
+
session.log?.info(`session ${session.id} created in NoConnection state`, {
|
|
882
|
+
...session.loggingMetadata,
|
|
883
|
+
tags: ["state-transition"]
|
|
884
|
+
});
|
|
885
|
+
return session;
|
|
886
|
+
},
|
|
887
|
+
WaitingForHandshake(from, conn, listeners, options, log) {
|
|
888
|
+
const session = new SessionWaitingForHandshake(
|
|
889
|
+
conn,
|
|
890
|
+
listeners,
|
|
891
|
+
from,
|
|
892
|
+
options,
|
|
893
|
+
log
|
|
894
|
+
);
|
|
895
|
+
session.log?.info(`session created in WaitingForHandshake state`, {
|
|
896
|
+
...session.loggingMetadata,
|
|
897
|
+
tags: ["state-transition"]
|
|
898
|
+
});
|
|
899
|
+
return session;
|
|
677
900
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
901
|
+
},
|
|
902
|
+
// All of the transitions 'move'/'consume' the old session and return a new one.
|
|
903
|
+
// After a session is transitioned, any usage of the old session will throw.
|
|
904
|
+
transition: {
|
|
905
|
+
// happy path transitions
|
|
906
|
+
NoConnectionToConnecting(oldSession, connPromise, listeners) {
|
|
907
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
908
|
+
oldSession._handleStateExit();
|
|
909
|
+
const session = new SessionConnecting(
|
|
910
|
+
connPromise,
|
|
911
|
+
listeners,
|
|
912
|
+
...carriedState
|
|
913
|
+
);
|
|
914
|
+
session.log?.info(
|
|
915
|
+
`session ${session.id} transition from NoConnection to Connecting`,
|
|
916
|
+
{
|
|
917
|
+
...session.loggingMetadata,
|
|
918
|
+
tags: ["state-transition"]
|
|
919
|
+
}
|
|
920
|
+
);
|
|
921
|
+
return session;
|
|
922
|
+
},
|
|
923
|
+
ConnectingToHandshaking(oldSession, conn, listeners) {
|
|
924
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
925
|
+
oldSession._handleStateExit();
|
|
926
|
+
const session = new SessionHandshaking(conn, listeners, ...carriedState);
|
|
927
|
+
session.log?.info(
|
|
928
|
+
`session ${session.id} transition from Connecting to Handshaking`,
|
|
929
|
+
{
|
|
930
|
+
...session.loggingMetadata,
|
|
931
|
+
tags: ["state-transition"]
|
|
932
|
+
}
|
|
933
|
+
);
|
|
934
|
+
return session;
|
|
935
|
+
},
|
|
936
|
+
HandshakingToConnected(oldSession, listeners) {
|
|
937
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
938
|
+
const conn = oldSession.conn;
|
|
939
|
+
oldSession._handleStateExit();
|
|
940
|
+
const session = new SessionConnected(conn, listeners, ...carriedState);
|
|
941
|
+
session.log?.info(
|
|
942
|
+
`session ${session.id} transition from Handshaking to Connected`,
|
|
943
|
+
{
|
|
944
|
+
...session.loggingMetadata,
|
|
945
|
+
tags: ["state-transition"]
|
|
946
|
+
}
|
|
947
|
+
);
|
|
948
|
+
return session;
|
|
949
|
+
},
|
|
950
|
+
WaitingForHandshakeToConnected(pendingSession, oldSession, sessionId, to, propagationCtx, listeners) {
|
|
951
|
+
const conn = pendingSession.conn;
|
|
952
|
+
const { from, options } = pendingSession;
|
|
953
|
+
const carriedState = oldSession ? (
|
|
954
|
+
// old session exists, inherit state
|
|
955
|
+
inheritSharedSession(oldSession)
|
|
956
|
+
) : (
|
|
957
|
+
// old session does not exist, create new state
|
|
958
|
+
[
|
|
959
|
+
sessionId,
|
|
960
|
+
from,
|
|
961
|
+
to,
|
|
962
|
+
0,
|
|
963
|
+
0,
|
|
964
|
+
[],
|
|
965
|
+
createSessionTelemetryInfo(sessionId, to, from, propagationCtx),
|
|
966
|
+
options,
|
|
967
|
+
pendingSession.log
|
|
968
|
+
]
|
|
969
|
+
);
|
|
970
|
+
pendingSession._handleStateExit();
|
|
971
|
+
oldSession?._handleStateExit();
|
|
972
|
+
const session = new SessionConnected(conn, listeners, ...carriedState);
|
|
973
|
+
session.log?.info(
|
|
974
|
+
`session ${session.id} transition from WaitingForHandshake to Connected`,
|
|
975
|
+
{
|
|
976
|
+
...session.loggingMetadata,
|
|
977
|
+
tags: ["state-transition"]
|
|
978
|
+
}
|
|
979
|
+
);
|
|
980
|
+
return session;
|
|
981
|
+
},
|
|
982
|
+
// disconnect paths
|
|
983
|
+
ConnectingToNoConnection(oldSession, listeners) {
|
|
984
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
985
|
+
oldSession.bestEffortClose();
|
|
986
|
+
oldSession._handleStateExit();
|
|
987
|
+
const session = new SessionNoConnection(listeners, ...carriedState);
|
|
988
|
+
session.log?.info(
|
|
989
|
+
`session ${session.id} transition from Connecting to NoConnection`,
|
|
990
|
+
{
|
|
991
|
+
...session.loggingMetadata,
|
|
992
|
+
tags: ["state-transition"]
|
|
993
|
+
}
|
|
994
|
+
);
|
|
995
|
+
return session;
|
|
996
|
+
},
|
|
997
|
+
HandshakingToNoConnection(oldSession, listeners) {
|
|
998
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
999
|
+
oldSession.conn.close();
|
|
1000
|
+
oldSession._handleStateExit();
|
|
1001
|
+
const session = new SessionNoConnection(listeners, ...carriedState);
|
|
1002
|
+
session.log?.info(
|
|
1003
|
+
`session ${session.id} transition from Handshaking to NoConnection`,
|
|
1004
|
+
{
|
|
1005
|
+
...session.loggingMetadata,
|
|
1006
|
+
tags: ["state-transition"]
|
|
1007
|
+
}
|
|
1008
|
+
);
|
|
1009
|
+
return session;
|
|
1010
|
+
},
|
|
1011
|
+
ConnectedToNoConnection(oldSession, listeners) {
|
|
1012
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1013
|
+
oldSession.conn.close();
|
|
1014
|
+
oldSession._handleStateExit();
|
|
1015
|
+
const session = new SessionNoConnection(listeners, ...carriedState);
|
|
1016
|
+
session.log?.info(
|
|
1017
|
+
`session ${session.id} transition from Connected to NoConnection`,
|
|
1018
|
+
{
|
|
1019
|
+
...session.loggingMetadata,
|
|
1020
|
+
tags: ["state-transition"]
|
|
1021
|
+
}
|
|
1022
|
+
);
|
|
1023
|
+
return session;
|
|
707
1024
|
}
|
|
708
|
-
return nextExpectedSeq === this.seq;
|
|
709
|
-
}
|
|
710
|
-
// This is only used in tests to make the session misbehave.
|
|
711
|
-
/* @internal */
|
|
712
|
-
advanceAckForTesting(by) {
|
|
713
|
-
this.ack += by;
|
|
714
|
-
}
|
|
715
|
-
constructMsg(partialMsg) {
|
|
716
|
-
const msg = {
|
|
717
|
-
...partialMsg,
|
|
718
|
-
id: unsafeId(),
|
|
719
|
-
to: this.to,
|
|
720
|
-
from: this.from,
|
|
721
|
-
seq: this.seq,
|
|
722
|
-
ack: this.ack
|
|
723
|
-
};
|
|
724
|
-
this.seq++;
|
|
725
|
-
this.sendBuffer.push(msg);
|
|
726
|
-
return msg;
|
|
727
|
-
}
|
|
728
|
-
inspectSendBuffer() {
|
|
729
|
-
return this.sendBuffer;
|
|
730
1025
|
}
|
|
731
1026
|
};
|
|
732
1027
|
|
|
733
1028
|
// transport/transport.ts
|
|
734
|
-
var import_api3 = require("@opentelemetry/api");
|
|
735
1029
|
var Transport = class {
|
|
736
1030
|
/**
|
|
737
1031
|
* The status of the transport.
|
|
738
1032
|
*/
|
|
739
1033
|
status;
|
|
740
|
-
/**
|
|
741
|
-
* The {@link Codec} used to encode and decode messages.
|
|
742
|
-
*/
|
|
743
|
-
codec;
|
|
744
1034
|
/**
|
|
745
1035
|
* The client ID of this transport.
|
|
746
1036
|
*/
|
|
747
1037
|
clientId;
|
|
748
|
-
/**
|
|
749
|
-
* The map of {@link Session}s managed by this transport.
|
|
750
|
-
*/
|
|
751
|
-
sessions;
|
|
752
|
-
/**
|
|
753
|
-
* The map of {@link Connection}s managed by this transport.
|
|
754
|
-
*/
|
|
755
|
-
get connections() {
|
|
756
|
-
return new Map(
|
|
757
|
-
[...this.sessions].map(([client, session]) => [client, session.connection]).filter((entry) => entry[1] !== void 0)
|
|
758
|
-
);
|
|
759
|
-
}
|
|
760
1038
|
/**
|
|
761
1039
|
* The event dispatcher for handling events of type EventTypes.
|
|
762
1040
|
*/
|
|
@@ -766,19 +1044,18 @@ var Transport = class {
|
|
|
766
1044
|
*/
|
|
767
1045
|
options;
|
|
768
1046
|
log;
|
|
1047
|
+
sessions;
|
|
769
1048
|
/**
|
|
770
1049
|
* Creates a new Transport instance.
|
|
771
|
-
* This should also set up {@link onConnect}, and {@link onDisconnect} listeners.
|
|
772
1050
|
* @param codec The codec used to encode and decode messages.
|
|
773
1051
|
* @param clientId The client ID of this transport.
|
|
774
1052
|
*/
|
|
775
1053
|
constructor(clientId, providedOptions) {
|
|
776
1054
|
this.options = { ...defaultTransportOptions, ...providedOptions };
|
|
777
1055
|
this.eventDispatcher = new EventDispatcher();
|
|
778
|
-
this.sessions = /* @__PURE__ */ new Map();
|
|
779
|
-
this.codec = this.options.codec;
|
|
780
1056
|
this.clientId = clientId;
|
|
781
1057
|
this.status = "open";
|
|
1058
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
782
1059
|
}
|
|
783
1060
|
bindLogger(fn, level) {
|
|
784
1061
|
if (typeof fn === "function") {
|
|
@@ -787,299 +1064,15 @@ var Transport = class {
|
|
|
787
1064
|
}
|
|
788
1065
|
this.log = createLogProxy(fn);
|
|
789
1066
|
}
|
|
790
|
-
/**
|
|
791
|
-
* Called when a new connection is established
|
|
792
|
-
* and we know the identity of the connected client.
|
|
793
|
-
* @param conn The connection object.
|
|
794
|
-
*/
|
|
795
|
-
onConnect(conn, session, isTransparentReconnect) {
|
|
796
|
-
this.eventDispatcher.dispatchEvent("connectionStatus", {
|
|
797
|
-
status: "connect",
|
|
798
|
-
conn
|
|
799
|
-
});
|
|
800
|
-
conn.telemetry = createConnectionTelemetryInfo(conn, session.telemetry);
|
|
801
|
-
session.replaceWithNewConnection(conn, isTransparentReconnect);
|
|
802
|
-
this.log?.info(`connected to ${session.to}`, {
|
|
803
|
-
...conn.loggingMetadata,
|
|
804
|
-
...session.loggingMetadata
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
createSession(to, conn, propagationCtx) {
|
|
808
|
-
const session = new Session(
|
|
809
|
-
conn,
|
|
810
|
-
this.clientId,
|
|
811
|
-
to,
|
|
812
|
-
this.options,
|
|
813
|
-
propagationCtx
|
|
814
|
-
);
|
|
815
|
-
if (this.log) {
|
|
816
|
-
session.bindLogger(this.log);
|
|
817
|
-
}
|
|
818
|
-
const currentSession = this.sessions.get(session.to);
|
|
819
|
-
if (currentSession) {
|
|
820
|
-
this.log?.warn(
|
|
821
|
-
`session ${session.id} from ${session.to} surreptitiously replacing ${currentSession.id}`,
|
|
822
|
-
{
|
|
823
|
-
...currentSession.loggingMetadata,
|
|
824
|
-
tags: ["invariant-violation"]
|
|
825
|
-
}
|
|
826
|
-
);
|
|
827
|
-
this.deleteSession({
|
|
828
|
-
session: currentSession,
|
|
829
|
-
closeHandshakingConnection: false
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
this.sessions.set(session.to, session);
|
|
833
|
-
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
834
|
-
status: "connect",
|
|
835
|
-
session
|
|
836
|
-
});
|
|
837
|
-
return session;
|
|
838
|
-
}
|
|
839
|
-
createNewSession({
|
|
840
|
-
to,
|
|
841
|
-
conn,
|
|
842
|
-
sessionId,
|
|
843
|
-
propagationCtx
|
|
844
|
-
}) {
|
|
845
|
-
let session = this.sessions.get(to);
|
|
846
|
-
if (session !== void 0) {
|
|
847
|
-
this.log?.info(
|
|
848
|
-
`session for ${to} already exists, replacing it with a new session as requested`,
|
|
849
|
-
session.loggingMetadata
|
|
850
|
-
);
|
|
851
|
-
this.deleteSession({
|
|
852
|
-
session,
|
|
853
|
-
closeHandshakingConnection: false
|
|
854
|
-
});
|
|
855
|
-
session = void 0;
|
|
856
|
-
}
|
|
857
|
-
session = this.createSession(to, conn, propagationCtx);
|
|
858
|
-
session.advertisedSessionId = sessionId;
|
|
859
|
-
this.log?.info(`created new session for ${to}`, session.loggingMetadata);
|
|
860
|
-
return session;
|
|
861
|
-
}
|
|
862
|
-
getExistingSession({
|
|
863
|
-
to,
|
|
864
|
-
sessionId,
|
|
865
|
-
nextExpectedSeq
|
|
866
|
-
}) {
|
|
867
|
-
const session = this.sessions.get(to);
|
|
868
|
-
if (
|
|
869
|
-
// reject this request if there was no previous session to replace
|
|
870
|
-
session === void 0 || // or if both parties do not agree about the next expected sequence number
|
|
871
|
-
!session.nextExpectedSeqInRange(nextExpectedSeq) || // or if both parties do not agree on the advertised session id
|
|
872
|
-
session.advertisedSessionId !== sessionId
|
|
873
|
-
) {
|
|
874
|
-
return false;
|
|
875
|
-
}
|
|
876
|
-
this.log?.info(
|
|
877
|
-
`reused existing session for ${to}`,
|
|
878
|
-
session.loggingMetadata
|
|
879
|
-
);
|
|
880
|
-
return session;
|
|
881
|
-
}
|
|
882
|
-
getOrCreateSession({
|
|
883
|
-
to,
|
|
884
|
-
conn,
|
|
885
|
-
handshakingConn,
|
|
886
|
-
sessionId,
|
|
887
|
-
propagationCtx
|
|
888
|
-
}) {
|
|
889
|
-
let session = this.sessions.get(to);
|
|
890
|
-
const isReconnect = session !== void 0;
|
|
891
|
-
let isTransparentReconnect = isReconnect;
|
|
892
|
-
if (session?.advertisedSessionId !== void 0 && sessionId !== void 0 && session.advertisedSessionId !== sessionId) {
|
|
893
|
-
this.log?.info(
|
|
894
|
-
`session for ${to} already exists but has a different session id (expected: ${session.advertisedSessionId}, got: ${sessionId}), creating a new one`,
|
|
895
|
-
session.loggingMetadata
|
|
896
|
-
);
|
|
897
|
-
this.deleteSession({
|
|
898
|
-
session,
|
|
899
|
-
closeHandshakingConnection: handshakingConn !== void 0,
|
|
900
|
-
handshakingConn
|
|
901
|
-
});
|
|
902
|
-
isTransparentReconnect = false;
|
|
903
|
-
session = void 0;
|
|
904
|
-
}
|
|
905
|
-
if (!session) {
|
|
906
|
-
session = this.createSession(to, conn, propagationCtx);
|
|
907
|
-
this.log?.info(
|
|
908
|
-
`no session for ${to}, created a new one`,
|
|
909
|
-
session.loggingMetadata
|
|
910
|
-
);
|
|
911
|
-
}
|
|
912
|
-
if (sessionId !== void 0) {
|
|
913
|
-
session.advertisedSessionId = sessionId;
|
|
914
|
-
}
|
|
915
|
-
if (handshakingConn !== void 0) {
|
|
916
|
-
session.replaceWithNewHandshakingConnection(handshakingConn);
|
|
917
|
-
}
|
|
918
|
-
return { session, isReconnect, isTransparentReconnect };
|
|
919
|
-
}
|
|
920
|
-
deleteSession({
|
|
921
|
-
session,
|
|
922
|
-
closeHandshakingConnection,
|
|
923
|
-
handshakingConn
|
|
924
|
-
}) {
|
|
925
|
-
if (closeHandshakingConnection) {
|
|
926
|
-
session.closeHandshakingConnection(handshakingConn);
|
|
927
|
-
}
|
|
928
|
-
session.close();
|
|
929
|
-
session.telemetry.span.end();
|
|
930
|
-
const currentSession = this.sessions.get(session.to);
|
|
931
|
-
if (currentSession && currentSession.id !== session.id) {
|
|
932
|
-
this.log?.warn(
|
|
933
|
-
`session ${session.id} disconnect from ${session.to}, mismatch with ${currentSession.id}`,
|
|
934
|
-
{
|
|
935
|
-
...session.loggingMetadata,
|
|
936
|
-
tags: ["invariant-violation"]
|
|
937
|
-
}
|
|
938
|
-
);
|
|
939
|
-
return;
|
|
940
|
-
}
|
|
941
|
-
this.sessions.delete(session.to);
|
|
942
|
-
this.log?.info(
|
|
943
|
-
`session ${session.id} disconnect from ${session.to}`,
|
|
944
|
-
session.loggingMetadata
|
|
945
|
-
);
|
|
946
|
-
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
947
|
-
status: "disconnect",
|
|
948
|
-
session
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
/**
|
|
952
|
-
* The downstream implementation needs to call this when a connection is closed.
|
|
953
|
-
* @param conn The connection object.
|
|
954
|
-
* @param connectedTo The peer we are connected to.
|
|
955
|
-
*/
|
|
956
|
-
onDisconnect(conn, session) {
|
|
957
|
-
if (session.connection !== void 0 && session.connection.id !== conn.id) {
|
|
958
|
-
session.telemetry.span.addEvent("onDisconnect race");
|
|
959
|
-
this.log?.warn("onDisconnect race", {
|
|
960
|
-
clientId: this.clientId,
|
|
961
|
-
...session.loggingMetadata,
|
|
962
|
-
...conn.loggingMetadata,
|
|
963
|
-
tags: ["invariant-violation"]
|
|
964
|
-
});
|
|
965
|
-
return;
|
|
966
|
-
}
|
|
967
|
-
conn.telemetry?.span.end();
|
|
968
|
-
this.eventDispatcher.dispatchEvent("connectionStatus", {
|
|
969
|
-
status: "disconnect",
|
|
970
|
-
conn
|
|
971
|
-
});
|
|
972
|
-
session.connection = void 0;
|
|
973
|
-
session.beginGrace(() => {
|
|
974
|
-
if (session.connection !== void 0) {
|
|
975
|
-
session.telemetry.span.addEvent("session grace period race");
|
|
976
|
-
this.log?.warn("session grace period race", {
|
|
977
|
-
clientId: this.clientId,
|
|
978
|
-
...session.loggingMetadata,
|
|
979
|
-
...conn.loggingMetadata,
|
|
980
|
-
tags: ["invariant-violation"]
|
|
981
|
-
});
|
|
982
|
-
return;
|
|
983
|
-
}
|
|
984
|
-
session.telemetry.span.addEvent("session grace period expired");
|
|
985
|
-
this.deleteSession({
|
|
986
|
-
session,
|
|
987
|
-
closeHandshakingConnection: true,
|
|
988
|
-
handshakingConn: conn
|
|
989
|
-
});
|
|
990
|
-
});
|
|
991
|
-
}
|
|
992
|
-
/**
|
|
993
|
-
* Parses a message from a Uint8Array into a {@link OpaqueTransportMessage}.
|
|
994
|
-
* @param msg The message to parse.
|
|
995
|
-
* @returns The parsed message, or null if the message is malformed or invalid.
|
|
996
|
-
*/
|
|
997
|
-
parseMsg(msg, conn) {
|
|
998
|
-
const parsedMsg = this.codec.fromBuffer(msg);
|
|
999
|
-
if (parsedMsg === null) {
|
|
1000
|
-
const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));
|
|
1001
|
-
this.log?.error(
|
|
1002
|
-
`received malformed msg, killing conn: ${decodedBuffer}`,
|
|
1003
|
-
{
|
|
1004
|
-
clientId: this.clientId,
|
|
1005
|
-
...conn.loggingMetadata
|
|
1006
|
-
}
|
|
1007
|
-
);
|
|
1008
|
-
return null;
|
|
1009
|
-
}
|
|
1010
|
-
if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
|
|
1011
|
-
this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
|
|
1012
|
-
clientId: this.clientId,
|
|
1013
|
-
...conn.loggingMetadata,
|
|
1014
|
-
validationErrors: [
|
|
1015
|
-
...import_value.Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
|
|
1016
|
-
]
|
|
1017
|
-
});
|
|
1018
|
-
return null;
|
|
1019
|
-
}
|
|
1020
|
-
return parsedMsg;
|
|
1021
|
-
}
|
|
1022
1067
|
/**
|
|
1023
1068
|
* Called when a message is received by this transport.
|
|
1024
1069
|
* You generally shouldn't need to override this in downstream transport implementations.
|
|
1025
1070
|
* @param msg The received message.
|
|
1026
1071
|
*/
|
|
1027
|
-
handleMsg(msg
|
|
1072
|
+
handleMsg(msg) {
|
|
1028
1073
|
if (this.getStatus() !== "open")
|
|
1029
1074
|
return;
|
|
1030
|
-
|
|
1031
|
-
if (!session) {
|
|
1032
|
-
this.log?.error(`received message for unknown session from ${msg.from}`, {
|
|
1033
|
-
clientId: this.clientId,
|
|
1034
|
-
transportMessage: msg,
|
|
1035
|
-
...conn.loggingMetadata,
|
|
1036
|
-
tags: ["invariant-violation"]
|
|
1037
|
-
});
|
|
1038
|
-
return;
|
|
1039
|
-
}
|
|
1040
|
-
session.cancelGrace();
|
|
1041
|
-
this.log?.debug(`received msg`, {
|
|
1042
|
-
clientId: this.clientId,
|
|
1043
|
-
transportMessage: msg,
|
|
1044
|
-
...conn.loggingMetadata
|
|
1045
|
-
});
|
|
1046
|
-
if (msg.seq !== session.nextExpectedSeq) {
|
|
1047
|
-
if (msg.seq < session.nextExpectedSeq) {
|
|
1048
|
-
this.log?.debug(
|
|
1049
|
-
`received duplicate msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq}), discarding`,
|
|
1050
|
-
{
|
|
1051
|
-
clientId: this.clientId,
|
|
1052
|
-
transportMessage: msg,
|
|
1053
|
-
...conn.loggingMetadata
|
|
1054
|
-
}
|
|
1055
|
-
);
|
|
1056
|
-
} else {
|
|
1057
|
-
const errMsg = `received out-of-order msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq})`;
|
|
1058
|
-
this.log?.error(`${errMsg}, marking connection as dead`, {
|
|
1059
|
-
clientId: this.clientId,
|
|
1060
|
-
transportMessage: msg,
|
|
1061
|
-
...conn.loggingMetadata,
|
|
1062
|
-
tags: ["invariant-violation"]
|
|
1063
|
-
});
|
|
1064
|
-
this.protocolError(ProtocolError.MessageOrderingViolated, errMsg);
|
|
1065
|
-
session.telemetry.span.setStatus({
|
|
1066
|
-
code: import_api3.SpanStatusCode.ERROR,
|
|
1067
|
-
message: "message order violated"
|
|
1068
|
-
});
|
|
1069
|
-
this.deleteSession({ session, closeHandshakingConnection: true });
|
|
1070
|
-
}
|
|
1071
|
-
return;
|
|
1072
|
-
}
|
|
1073
|
-
session.updateBookkeeping(msg.ack, msg.seq);
|
|
1074
|
-
if (!isAck(msg.controlFlags)) {
|
|
1075
|
-
this.eventDispatcher.dispatchEvent("message", msg);
|
|
1076
|
-
} else {
|
|
1077
|
-
this.log?.debug(`discarding msg (ack bit set)`, {
|
|
1078
|
-
clientId: this.clientId,
|
|
1079
|
-
transportMessage: msg,
|
|
1080
|
-
...conn.loggingMetadata
|
|
1081
|
-
});
|
|
1082
|
-
}
|
|
1075
|
+
this.eventDispatcher.dispatchEvent("message", msg);
|
|
1083
1076
|
}
|
|
1084
1077
|
/**
|
|
1085
1078
|
* Adds a listener to this transport.
|
|
@@ -1097,34 +1090,6 @@ var Transport = class {
|
|
|
1097
1090
|
removeEventListener(type, handler) {
|
|
1098
1091
|
this.eventDispatcher.removeEventListener(type, handler);
|
|
1099
1092
|
}
|
|
1100
|
-
/**
|
|
1101
|
-
* Sends a message over this transport, delegating to the appropriate connection to actually
|
|
1102
|
-
* send the message.
|
|
1103
|
-
* @param msg The message to send.
|
|
1104
|
-
* @returns The ID of the sent message or undefined if it wasn't sent
|
|
1105
|
-
*/
|
|
1106
|
-
send(to, msg) {
|
|
1107
|
-
if (this.getStatus() === "closed") {
|
|
1108
|
-
const err = "transport is closed, cant send";
|
|
1109
|
-
this.log?.error(err, {
|
|
1110
|
-
clientId: this.clientId,
|
|
1111
|
-
transportMessage: msg,
|
|
1112
|
-
tags: ["invariant-violation"]
|
|
1113
|
-
});
|
|
1114
|
-
throw new Error(err);
|
|
1115
|
-
}
|
|
1116
|
-
return this.getOrCreateSession({ to }).session.send(msg);
|
|
1117
|
-
}
|
|
1118
|
-
// control helpers
|
|
1119
|
-
sendCloseStream(to, streamId) {
|
|
1120
|
-
return this.send(to, {
|
|
1121
|
-
streamId,
|
|
1122
|
-
controlFlags: 4 /* StreamClosedBit */,
|
|
1123
|
-
payload: {
|
|
1124
|
-
type: "CLOSE"
|
|
1125
|
-
}
|
|
1126
|
-
});
|
|
1127
|
-
}
|
|
1128
1093
|
protocolError(type, message) {
|
|
1129
1094
|
this.eventDispatcher.dispatchEvent("protocolError", { type, message });
|
|
1130
1095
|
}
|
|
@@ -1136,7 +1101,7 @@ var Transport = class {
|
|
|
1136
1101
|
close() {
|
|
1137
1102
|
this.status = "closed";
|
|
1138
1103
|
for (const session of this.sessions.values()) {
|
|
1139
|
-
this.deleteSession(
|
|
1104
|
+
this.deleteSession(session);
|
|
1140
1105
|
}
|
|
1141
1106
|
this.eventDispatcher.dispatchEvent("transportStatus", {
|
|
1142
1107
|
status: this.status
|
|
@@ -1147,6 +1112,68 @@ var Transport = class {
|
|
|
1147
1112
|
getStatus() {
|
|
1148
1113
|
return this.status;
|
|
1149
1114
|
}
|
|
1115
|
+
updateSession(session) {
|
|
1116
|
+
const activeSession = this.sessions.get(session.to);
|
|
1117
|
+
if (activeSession && activeSession.id !== session.id) {
|
|
1118
|
+
const msg = `attempt to transition active session for ${session.to} but active session (${activeSession.id}) is different from handle (${session.id})`;
|
|
1119
|
+
throw new Error(msg);
|
|
1120
|
+
}
|
|
1121
|
+
this.sessions.set(session.to, session);
|
|
1122
|
+
if (!activeSession) {
|
|
1123
|
+
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
1124
|
+
status: "connect",
|
|
1125
|
+
session
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
this.eventDispatcher.dispatchEvent("sessionTransition", {
|
|
1129
|
+
state: session.state,
|
|
1130
|
+
session
|
|
1131
|
+
});
|
|
1132
|
+
return session;
|
|
1133
|
+
}
|
|
1134
|
+
// state transitions
|
|
1135
|
+
deleteSession(session) {
|
|
1136
|
+
session.log?.info(`closing session ${session.id}`, session.loggingMetadata);
|
|
1137
|
+
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
1138
|
+
status: "disconnect",
|
|
1139
|
+
session
|
|
1140
|
+
});
|
|
1141
|
+
session.close();
|
|
1142
|
+
this.sessions.delete(session.to);
|
|
1143
|
+
}
|
|
1144
|
+
// common listeners
|
|
1145
|
+
onSessionGracePeriodElapsed(session) {
|
|
1146
|
+
this.log?.warn(
|
|
1147
|
+
`session to ${session.to} grace period elapsed, closing`,
|
|
1148
|
+
session.loggingMetadata
|
|
1149
|
+
);
|
|
1150
|
+
this.deleteSession(session);
|
|
1151
|
+
}
|
|
1152
|
+
onConnectingFailed(session) {
|
|
1153
|
+
const noConnectionSession = SessionStateGraph.transition.ConnectingToNoConnection(session, {
|
|
1154
|
+
onSessionGracePeriodElapsed: () => {
|
|
1155
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
return this.updateSession(noConnectionSession);
|
|
1159
|
+
}
|
|
1160
|
+
onConnClosed(session) {
|
|
1161
|
+
let noConnectionSession;
|
|
1162
|
+
if (session.state === "Handshaking" /* Handshaking */) {
|
|
1163
|
+
noConnectionSession = SessionStateGraph.transition.HandshakingToNoConnection(session, {
|
|
1164
|
+
onSessionGracePeriodElapsed: () => {
|
|
1165
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
} else {
|
|
1169
|
+
noConnectionSession = SessionStateGraph.transition.ConnectedToNoConnection(session, {
|
|
1170
|
+
onSessionGracePeriodElapsed: () => {
|
|
1171
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
return this.updateSession(noConnectionSession);
|
|
1176
|
+
}
|
|
1150
1177
|
};
|
|
1151
1178
|
|
|
1152
1179
|
// util/stringify.ts
|
|
@@ -1164,10 +1191,6 @@ var ClientTransport = class extends Transport {
|
|
|
1164
1191
|
* The options for this transport.
|
|
1165
1192
|
*/
|
|
1166
1193
|
options;
|
|
1167
|
-
/**
|
|
1168
|
-
* The map of reconnect promises for each client ID.
|
|
1169
|
-
*/
|
|
1170
|
-
inflightConnectionPromises;
|
|
1171
1194
|
retryBudget;
|
|
1172
1195
|
/**
|
|
1173
1196
|
* A flag indicating whether the transport should automatically reconnect
|
|
@@ -1186,352 +1209,278 @@ var ClientTransport = class extends Transport {
|
|
|
1186
1209
|
...defaultClientTransportOptions,
|
|
1187
1210
|
...providedOptions
|
|
1188
1211
|
};
|
|
1189
|
-
this.inflightConnectionPromises = /* @__PURE__ */ new Map();
|
|
1190
1212
|
this.retryBudget = new LeakyBucketRateLimit(this.options);
|
|
1191
1213
|
}
|
|
1192
1214
|
extendHandshake(options) {
|
|
1193
1215
|
this.handshakeExtensions = options;
|
|
1194
1216
|
}
|
|
1195
|
-
|
|
1196
|
-
if (this.getStatus()
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
}, this.options.handshakeTimeoutMs);
|
|
1208
|
-
const handshakeHandler = (data) => {
|
|
1209
|
-
const maybeSession = this.receiveHandshakeResponseMessage(data, conn);
|
|
1210
|
-
clearTimeout(handshakeTimeout);
|
|
1211
|
-
if (!maybeSession) {
|
|
1212
|
-
conn.close();
|
|
1213
|
-
return;
|
|
1214
|
-
} else {
|
|
1215
|
-
session = maybeSession;
|
|
1216
|
-
}
|
|
1217
|
-
conn.removeDataListener(handshakeHandler);
|
|
1218
|
-
conn.addDataListener((data2) => {
|
|
1219
|
-
const parsed = this.parseMsg(data2, conn);
|
|
1220
|
-
if (!parsed) {
|
|
1221
|
-
conn.telemetry?.span.setStatus({
|
|
1222
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1223
|
-
message: "message parse failure"
|
|
1224
|
-
});
|
|
1225
|
-
conn.close();
|
|
1226
|
-
return;
|
|
1227
|
-
}
|
|
1228
|
-
this.handleMsg(parsed, conn);
|
|
1217
|
+
tryReconnecting(to) {
|
|
1218
|
+
if (this.reconnectOnConnectionDrop && this.getStatus() === "open") {
|
|
1219
|
+
this.connect(to);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
send(to, msg) {
|
|
1223
|
+
if (this.getStatus() === "closed") {
|
|
1224
|
+
const err = "transport is closed, cant send";
|
|
1225
|
+
this.log?.error(err, {
|
|
1226
|
+
clientId: this.clientId,
|
|
1227
|
+
transportMessage: msg,
|
|
1228
|
+
tags: ["invariant-violation"]
|
|
1229
1229
|
});
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1230
|
+
throw new Error(err);
|
|
1231
|
+
}
|
|
1232
|
+
let session = this.sessions.get(to);
|
|
1233
|
+
if (!session) {
|
|
1234
|
+
session = this.createUnconnectedSession(to);
|
|
1235
|
+
}
|
|
1236
|
+
return session.send(msg);
|
|
1237
|
+
}
|
|
1238
|
+
createUnconnectedSession(to) {
|
|
1239
|
+
const session = SessionStateGraph.entrypoints.NoConnection(
|
|
1240
|
+
to,
|
|
1241
|
+
this.clientId,
|
|
1242
|
+
{
|
|
1243
|
+
onSessionGracePeriodElapsed: () => {
|
|
1244
|
+
this.onSessionGracePeriodElapsed(session);
|
|
1244
1245
|
}
|
|
1245
|
-
|
|
1246
|
-
this.
|
|
1247
|
-
|
|
1248
|
-
|
|
1246
|
+
},
|
|
1247
|
+
this.options,
|
|
1248
|
+
this.log
|
|
1249
|
+
);
|
|
1250
|
+
this.updateSession(session);
|
|
1251
|
+
return session;
|
|
1252
|
+
}
|
|
1253
|
+
// listeners
|
|
1254
|
+
onConnectingFailed(session) {
|
|
1255
|
+
const noConnectionSession = super.onConnectingFailed(session);
|
|
1256
|
+
this.tryReconnecting(noConnectionSession.to);
|
|
1257
|
+
return noConnectionSession;
|
|
1258
|
+
}
|
|
1259
|
+
onConnClosed(session) {
|
|
1260
|
+
const noConnectionSession = super.onConnClosed(session);
|
|
1261
|
+
this.tryReconnecting(noConnectionSession.to);
|
|
1262
|
+
return noConnectionSession;
|
|
1263
|
+
}
|
|
1264
|
+
onConnectionEstablished(session, conn) {
|
|
1265
|
+
const handshakingSession = SessionStateGraph.transition.ConnectingToHandshaking(session, conn, {
|
|
1266
|
+
onConnectionErrored: (err) => {
|
|
1267
|
+
const errStr = coerceErrorString(err);
|
|
1268
|
+
this.log?.error(
|
|
1269
|
+
`connection to ${handshakingSession.to} errored during handshake: ${errStr}`,
|
|
1270
|
+
handshakingSession.loggingMetadata
|
|
1271
|
+
);
|
|
1272
|
+
},
|
|
1273
|
+
onConnectionClosed: () => {
|
|
1274
|
+
this.log?.warn(
|
|
1275
|
+
`connection to ${handshakingSession.to} closed during handshake`,
|
|
1276
|
+
handshakingSession.loggingMetadata
|
|
1277
|
+
);
|
|
1278
|
+
this.onConnClosed(handshakingSession);
|
|
1279
|
+
},
|
|
1280
|
+
onHandshake: (msg) => {
|
|
1281
|
+
this.onHandshakeResponse(handshakingSession, msg);
|
|
1282
|
+
},
|
|
1283
|
+
onInvalidHandshake: (reason) => {
|
|
1284
|
+
this.log?.error(
|
|
1285
|
+
`invalid handshake: ${reason}`,
|
|
1286
|
+
handshakingSession.loggingMetadata
|
|
1287
|
+
);
|
|
1288
|
+
this.deleteSession(session);
|
|
1289
|
+
this.protocolError(ProtocolError.HandshakeFailed, reason);
|
|
1290
|
+
},
|
|
1291
|
+
onHandshakeTimeout: () => {
|
|
1292
|
+
this.log?.error(
|
|
1293
|
+
`connection to ${handshakingSession.to} timed out during handshake`,
|
|
1294
|
+
handshakingSession.loggingMetadata
|
|
1295
|
+
);
|
|
1296
|
+
this.onConnClosed(handshakingSession);
|
|
1249
1297
|
}
|
|
1250
1298
|
});
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
...conn.loggingMetadata,
|
|
1260
|
-
...session?.loggingMetadata,
|
|
1261
|
-
clientId: this.clientId,
|
|
1262
|
-
connectedTo: to
|
|
1263
|
-
}
|
|
1264
|
-
);
|
|
1299
|
+
this.updateSession(handshakingSession);
|
|
1300
|
+
void this.sendHandshake(handshakingSession);
|
|
1301
|
+
return handshakingSession;
|
|
1302
|
+
}
|
|
1303
|
+
rejectHandshakeResponse(session, reason, metadata) {
|
|
1304
|
+
session.conn.telemetry?.span.setStatus({
|
|
1305
|
+
code: import_api3.SpanStatusCode.ERROR,
|
|
1306
|
+
message: reason
|
|
1265
1307
|
});
|
|
1308
|
+
this.log?.warn(reason, metadata);
|
|
1309
|
+
this.deleteSession(session);
|
|
1266
1310
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
});
|
|
1274
|
-
this.protocolError(
|
|
1275
|
-
ProtocolError.HandshakeFailed,
|
|
1276
|
-
"received non-transport message"
|
|
1277
|
-
);
|
|
1278
|
-
return false;
|
|
1279
|
-
}
|
|
1280
|
-
if (!import_value2.Value.Check(ControlMessageHandshakeResponseSchema, parsed.payload)) {
|
|
1281
|
-
conn.telemetry?.span.setStatus({
|
|
1282
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1283
|
-
message: "invalid handshake response"
|
|
1284
|
-
});
|
|
1285
|
-
this.log?.warn(`received invalid handshake resp`, {
|
|
1286
|
-
...conn.loggingMetadata,
|
|
1287
|
-
clientId: this.clientId,
|
|
1288
|
-
connectedTo: parsed.from,
|
|
1289
|
-
transportMessage: parsed,
|
|
1311
|
+
onHandshakeResponse(session, msg) {
|
|
1312
|
+
if (!import_value2.Value.Check(ControlMessageHandshakeResponseSchema, msg.payload)) {
|
|
1313
|
+
const reason = `received invalid handshake response`;
|
|
1314
|
+
this.rejectHandshakeResponse(session, reason, {
|
|
1315
|
+
...session.loggingMetadata,
|
|
1316
|
+
transportMessage: msg,
|
|
1290
1317
|
validationErrors: [
|
|
1291
|
-
...import_value2.Value.Errors(
|
|
1292
|
-
ControlMessageHandshakeResponseSchema,
|
|
1293
|
-
parsed.payload
|
|
1294
|
-
)
|
|
1318
|
+
...import_value2.Value.Errors(ControlMessageHandshakeResponseSchema, msg.payload)
|
|
1295
1319
|
]
|
|
1296
1320
|
});
|
|
1297
|
-
|
|
1298
|
-
ProtocolError.HandshakeFailed,
|
|
1299
|
-
"invalid handshake resp"
|
|
1300
|
-
);
|
|
1301
|
-
return false;
|
|
1321
|
+
return;
|
|
1302
1322
|
}
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
});
|
|
1323
|
+
if (!msg.payload.status.ok) {
|
|
1324
|
+
const retriable = msg.payload.status.code ? import_value2.Value.Check(
|
|
1325
|
+
HandshakeErrorRetriableResponseCodes,
|
|
1326
|
+
msg.payload.status.code
|
|
1327
|
+
) : false;
|
|
1328
|
+
const reason = `handshake failed: ${msg.payload.status.reason}`;
|
|
1329
|
+
this.rejectHandshakeResponse(session, reason, {
|
|
1330
|
+
...session.loggingMetadata,
|
|
1331
|
+
transportMessage: msg
|
|
1332
|
+
});
|
|
1333
|
+
if (retriable) {
|
|
1334
|
+
this.tryReconnecting(session.to);
|
|
1316
1335
|
} else {
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
message: "handshake rejected"
|
|
1320
|
-
});
|
|
1336
|
+
this.deleteSession(session);
|
|
1337
|
+
this.protocolError(ProtocolError.HandshakeFailed, reason);
|
|
1321
1338
|
}
|
|
1322
|
-
|
|
1323
|
-
`received handshake rejection: ${parsed.payload.status.reason}`,
|
|
1324
|
-
{
|
|
1325
|
-
...conn.loggingMetadata,
|
|
1326
|
-
clientId: this.clientId,
|
|
1327
|
-
connectedTo: parsed.from,
|
|
1328
|
-
transportMessage: parsed
|
|
1329
|
-
}
|
|
1330
|
-
);
|
|
1331
|
-
this.protocolError(
|
|
1332
|
-
ProtocolError.HandshakeFailed,
|
|
1333
|
-
parsed.payload.status.reason
|
|
1334
|
-
);
|
|
1335
|
-
return false;
|
|
1339
|
+
return;
|
|
1336
1340
|
}
|
|
1337
|
-
if (
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
conn.telemetry?.span.setStatus({
|
|
1343
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1344
|
-
message: "session id mismatch"
|
|
1345
|
-
});
|
|
1346
|
-
this.log?.warn(`handshake from ${parsed.from} session id mismatch`, {
|
|
1347
|
-
...conn.loggingMetadata,
|
|
1348
|
-
clientId: this.clientId,
|
|
1349
|
-
connectedTo: parsed.from,
|
|
1350
|
-
transportMessage: parsed
|
|
1341
|
+
if (msg.payload.status.sessionId !== session.id) {
|
|
1342
|
+
const reason = `session id mismatch: expected ${session.id}, got ${msg.payload.status.sessionId}`;
|
|
1343
|
+
this.rejectHandshakeResponse(session, reason, {
|
|
1344
|
+
...session.loggingMetadata,
|
|
1345
|
+
transportMessage: msg
|
|
1351
1346
|
});
|
|
1352
|
-
|
|
1353
|
-
return false;
|
|
1347
|
+
return;
|
|
1354
1348
|
}
|
|
1355
|
-
this.log?.
|
|
1356
|
-
...
|
|
1357
|
-
|
|
1358
|
-
connectedTo: parsed.from,
|
|
1359
|
-
transportMessage: parsed
|
|
1349
|
+
this.log?.info(`handshake from ${msg.from} ok`, {
|
|
1350
|
+
...session.loggingMetadata,
|
|
1351
|
+
transportMessage: msg
|
|
1360
1352
|
});
|
|
1361
|
-
const
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1353
|
+
const connectedSession = SessionStateGraph.transition.HandshakingToConnected(session, {
|
|
1354
|
+
onConnectionErrored: (err) => {
|
|
1355
|
+
const errStr = coerceErrorString(err);
|
|
1356
|
+
this.log?.warn(
|
|
1357
|
+
`connection to ${connectedSession.to} errored: ${errStr}`,
|
|
1358
|
+
connectedSession.loggingMetadata
|
|
1359
|
+
);
|
|
1360
|
+
},
|
|
1361
|
+
onConnectionClosed: () => {
|
|
1362
|
+
this.log?.info(
|
|
1363
|
+
`connection to ${connectedSession.to} closed`,
|
|
1364
|
+
connectedSession.loggingMetadata
|
|
1365
|
+
);
|
|
1366
|
+
this.onConnClosed(connectedSession);
|
|
1367
|
+
},
|
|
1368
|
+
onMessage: (msg2) => this.handleMsg(msg2),
|
|
1369
|
+
onInvalidMessage: (reason) => {
|
|
1370
|
+
this.deleteSession(connectedSession);
|
|
1371
|
+
this.protocolError(ProtocolError.MessageOrderingViolated, reason);
|
|
1372
|
+
}
|
|
1365
1373
|
});
|
|
1366
|
-
this.
|
|
1367
|
-
this.retryBudget.startRestoringBudget(
|
|
1368
|
-
return session;
|
|
1374
|
+
this.updateSession(connectedSession);
|
|
1375
|
+
this.retryBudget.startRestoringBudget(connectedSession.to);
|
|
1369
1376
|
}
|
|
1370
1377
|
/**
|
|
1371
1378
|
* Manually attempts to connect to a client.
|
|
1372
1379
|
* @param to The client ID of the node to connect to.
|
|
1373
1380
|
*/
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1381
|
+
connect(to) {
|
|
1382
|
+
let session = this.sessions.get(to);
|
|
1383
|
+
session ??= this.createUnconnectedSession(to);
|
|
1384
|
+
if (session.state !== "NoConnection" /* NoConnection */) {
|
|
1385
|
+
this.log?.debug(
|
|
1386
|
+
`session to ${to} has state ${session.state}, skipping connect attempt`,
|
|
1387
|
+
session.loggingMetadata
|
|
1388
|
+
);
|
|
1380
1389
|
return;
|
|
1381
1390
|
}
|
|
1382
|
-
|
|
1383
|
-
if (!canProceedWithConnection()) {
|
|
1391
|
+
if (this.getStatus() !== "open") {
|
|
1384
1392
|
this.log?.info(
|
|
1385
1393
|
`transport state is no longer open, cancelling attempt to connect to ${to}`,
|
|
1386
|
-
|
|
1394
|
+
session.loggingMetadata
|
|
1387
1395
|
);
|
|
1388
1396
|
return;
|
|
1389
1397
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
this.protocolError(ProtocolError.RetriesExceeded, errMsg);
|
|
1397
|
-
return;
|
|
1398
|
-
}
|
|
1399
|
-
let sleep = Promise.resolve();
|
|
1400
|
-
const backoffMs = this.retryBudget.getBackoffMs(to);
|
|
1401
|
-
if (backoffMs > 0) {
|
|
1402
|
-
sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
1403
|
-
}
|
|
1404
|
-
this.log?.info(
|
|
1405
|
-
`attempting connection to ${to} (${backoffMs}ms backoff)`,
|
|
1406
|
-
{
|
|
1407
|
-
clientId: this.clientId,
|
|
1408
|
-
connectedTo: to
|
|
1409
|
-
}
|
|
1410
|
-
);
|
|
1411
|
-
this.retryBudget.consumeBudget(to);
|
|
1412
|
-
reconnectPromise = tracing_default.startActiveSpan("connect", async (span) => {
|
|
1413
|
-
try {
|
|
1414
|
-
span.addEvent("backoff", { backoffMs });
|
|
1415
|
-
await sleep;
|
|
1416
|
-
if (!canProceedWithConnection()) {
|
|
1417
|
-
throw new Error("transport state is no longer open");
|
|
1418
|
-
}
|
|
1419
|
-
span.addEvent("connecting");
|
|
1420
|
-
const conn = await this.createNewOutgoingConnection(to);
|
|
1421
|
-
if (!canProceedWithConnection()) {
|
|
1422
|
-
this.log?.info(
|
|
1423
|
-
`transport state is no longer open, closing pre-handshake connection to ${to}`,
|
|
1424
|
-
{
|
|
1425
|
-
...conn.loggingMetadata,
|
|
1426
|
-
clientId: this.clientId,
|
|
1427
|
-
connectedTo: to
|
|
1428
|
-
}
|
|
1429
|
-
);
|
|
1430
|
-
conn.close();
|
|
1431
|
-
throw new Error("transport state is no longer open");
|
|
1432
|
-
}
|
|
1433
|
-
span.addEvent("sending handshake");
|
|
1434
|
-
const ok = await this.sendHandshake(to, conn);
|
|
1435
|
-
if (!ok) {
|
|
1436
|
-
conn.close();
|
|
1437
|
-
throw new Error("failed to send handshake");
|
|
1438
|
-
}
|
|
1439
|
-
return conn;
|
|
1440
|
-
} catch (err) {
|
|
1441
|
-
const errStr = coerceErrorString(err);
|
|
1442
|
-
span.recordException(errStr);
|
|
1443
|
-
span.setStatus({ code: import_api4.SpanStatusCode.ERROR });
|
|
1444
|
-
throw err;
|
|
1445
|
-
} finally {
|
|
1446
|
-
span.end();
|
|
1447
|
-
}
|
|
1448
|
-
});
|
|
1449
|
-
this.inflightConnectionPromises.set(to, reconnectPromise);
|
|
1450
|
-
} else {
|
|
1451
|
-
this.log?.info(
|
|
1452
|
-
`attempting connection to ${to} (reusing previous attempt)`,
|
|
1453
|
-
{
|
|
1454
|
-
clientId: this.clientId,
|
|
1455
|
-
connectedTo: to
|
|
1456
|
-
}
|
|
1457
|
-
);
|
|
1398
|
+
if (!this.retryBudget.hasBudget(to)) {
|
|
1399
|
+
const budgetConsumed = this.retryBudget.getBudgetConsumed(to);
|
|
1400
|
+
const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
|
|
1401
|
+
this.log?.error(errMsg, session.loggingMetadata);
|
|
1402
|
+
this.protocolError(ProtocolError.RetriesExceeded, errMsg);
|
|
1403
|
+
return;
|
|
1458
1404
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
const errStr = coerceErrorString(error);
|
|
1464
|
-
if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {
|
|
1465
|
-
this.log?.warn(`connection to ${to} failed (${errStr})`, {
|
|
1466
|
-
clientId: this.clientId,
|
|
1467
|
-
connectedTo: to
|
|
1468
|
-
});
|
|
1469
|
-
} else {
|
|
1470
|
-
this.log?.warn(`connection to ${to} failed (${errStr}), retrying`, {
|
|
1471
|
-
clientId: this.clientId,
|
|
1472
|
-
connectedTo: to
|
|
1473
|
-
});
|
|
1474
|
-
await this.connect(to);
|
|
1475
|
-
}
|
|
1405
|
+
let sleep = Promise.resolve();
|
|
1406
|
+
const backoffMs = this.retryBudget.getBackoffMs(to);
|
|
1407
|
+
if (backoffMs > 0) {
|
|
1408
|
+
sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
1476
1409
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1410
|
+
this.log?.info(
|
|
1411
|
+
`attempting connection to ${to} (${backoffMs}ms backoff)`,
|
|
1412
|
+
session.loggingMetadata
|
|
1413
|
+
);
|
|
1414
|
+
this.retryBudget.consumeBudget(to);
|
|
1415
|
+
const reconnectPromise = tracing_default.startActiveSpan("connect", async (span) => {
|
|
1416
|
+
try {
|
|
1417
|
+
span.addEvent("backoff", { backoffMs });
|
|
1418
|
+
await sleep;
|
|
1419
|
+
if (this.getStatus() !== "open") {
|
|
1420
|
+
throw new Error("transport state is no longer open");
|
|
1421
|
+
}
|
|
1422
|
+
span.addEvent("connecting");
|
|
1423
|
+
return await this.createNewOutgoingConnection(to);
|
|
1424
|
+
} catch (err) {
|
|
1425
|
+
const errStr = coerceErrorString(err);
|
|
1426
|
+
span.recordException(errStr);
|
|
1427
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR });
|
|
1428
|
+
throw err;
|
|
1429
|
+
} finally {
|
|
1430
|
+
span.end();
|
|
1431
|
+
}
|
|
1488
1432
|
});
|
|
1433
|
+
const connectingSession = SessionStateGraph.transition.NoConnectionToConnecting(
|
|
1434
|
+
session,
|
|
1435
|
+
reconnectPromise,
|
|
1436
|
+
{
|
|
1437
|
+
onConnectionEstablished: (conn) => {
|
|
1438
|
+
this.log?.debug(
|
|
1439
|
+
`connection to ${connectingSession.to} established`,
|
|
1440
|
+
connectingSession.loggingMetadata
|
|
1441
|
+
);
|
|
1442
|
+
this.onConnectionEstablished(connectingSession, conn);
|
|
1443
|
+
},
|
|
1444
|
+
onConnectionFailed: (error) => {
|
|
1445
|
+
const errStr = coerceErrorString(error);
|
|
1446
|
+
this.log?.error(
|
|
1447
|
+
`error connecting to ${connectingSession.to}: ${errStr}`,
|
|
1448
|
+
connectingSession.loggingMetadata
|
|
1449
|
+
);
|
|
1450
|
+
this.onConnectingFailed(connectingSession);
|
|
1451
|
+
},
|
|
1452
|
+
onConnectionTimeout: () => {
|
|
1453
|
+
this.log?.error(
|
|
1454
|
+
`connection to ${connectingSession.to} timed out`,
|
|
1455
|
+
connectingSession.loggingMetadata
|
|
1456
|
+
);
|
|
1457
|
+
this.onConnectingFailed(connectingSession);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
);
|
|
1461
|
+
this.updateSession(connectingSession);
|
|
1489
1462
|
}
|
|
1490
|
-
async sendHandshake(
|
|
1463
|
+
async sendHandshake(session) {
|
|
1491
1464
|
let metadata = void 0;
|
|
1492
1465
|
if (this.handshakeExtensions) {
|
|
1493
1466
|
metadata = await this.handshakeExtensions.construct();
|
|
1494
|
-
if (!import_value2.Value.Check(this.handshakeExtensions.schema, metadata)) {
|
|
1495
|
-
this.log?.error(`constructed handshake metadata did not match schema`, {
|
|
1496
|
-
...conn.loggingMetadata,
|
|
1497
|
-
clientId: this.clientId,
|
|
1498
|
-
connectedTo: to,
|
|
1499
|
-
validationErrors: [
|
|
1500
|
-
...import_value2.Value.Errors(this.handshakeExtensions.schema, metadata)
|
|
1501
|
-
],
|
|
1502
|
-
tags: ["invariant-violation"]
|
|
1503
|
-
});
|
|
1504
|
-
this.protocolError(
|
|
1505
|
-
ProtocolError.HandshakeFailed,
|
|
1506
|
-
"handshake metadata did not match schema"
|
|
1507
|
-
);
|
|
1508
|
-
conn.telemetry?.span.setStatus({
|
|
1509
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1510
|
-
message: "handshake meta mismatch"
|
|
1511
|
-
});
|
|
1512
|
-
return false;
|
|
1513
|
-
}
|
|
1514
1467
|
}
|
|
1515
|
-
const { session } = this.getOrCreateSession({ to, handshakingConn: conn });
|
|
1516
1468
|
const requestMsg = handshakeRequestMessage({
|
|
1517
1469
|
from: this.clientId,
|
|
1518
|
-
to,
|
|
1470
|
+
to: session.to,
|
|
1519
1471
|
sessionId: session.id,
|
|
1520
1472
|
expectedSessionState: {
|
|
1521
|
-
|
|
1522
|
-
|
|
1473
|
+
nextExpectedSeq: session.ack,
|
|
1474
|
+
nextSentSeq: session.nextSeq()
|
|
1523
1475
|
},
|
|
1524
1476
|
metadata,
|
|
1525
1477
|
tracing: getPropagationContext(session.telemetry.ctx)
|
|
1526
1478
|
});
|
|
1527
|
-
this.log?.debug(`sending handshake request to ${to}`, {
|
|
1528
|
-
...
|
|
1529
|
-
clientId: this.clientId,
|
|
1530
|
-
connectedTo: to,
|
|
1479
|
+
this.log?.debug(`sending handshake request to ${session.to}`, {
|
|
1480
|
+
...session.loggingMetadata,
|
|
1531
1481
|
transportMessage: requestMsg
|
|
1532
1482
|
});
|
|
1533
|
-
|
|
1534
|
-
return true;
|
|
1483
|
+
session.sendHandshake(requestMsg);
|
|
1535
1484
|
}
|
|
1536
1485
|
close() {
|
|
1537
1486
|
this.retryBudget.close();
|
|
@@ -1539,10 +1488,79 @@ var ClientTransport = class extends Transport {
|
|
|
1539
1488
|
}
|
|
1540
1489
|
};
|
|
1541
1490
|
|
|
1491
|
+
// transport/connection.ts
|
|
1492
|
+
var Connection = class {
|
|
1493
|
+
id;
|
|
1494
|
+
telemetry;
|
|
1495
|
+
constructor() {
|
|
1496
|
+
this.id = `conn-${generateId()}`;
|
|
1497
|
+
}
|
|
1498
|
+
get loggingMetadata() {
|
|
1499
|
+
const metadata = { connId: this.id };
|
|
1500
|
+
const spanContext = this.telemetry?.span.spanContext();
|
|
1501
|
+
if (this.telemetry?.span.isRecording() && spanContext) {
|
|
1502
|
+
metadata.telemetry = {
|
|
1503
|
+
traceId: spanContext.traceId,
|
|
1504
|
+
spanId: spanContext.spanId
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
return metadata;
|
|
1508
|
+
}
|
|
1509
|
+
// can't use event emitter because we need this to work in both node + browser
|
|
1510
|
+
_dataListeners = /* @__PURE__ */ new Set();
|
|
1511
|
+
_closeListeners = /* @__PURE__ */ new Set();
|
|
1512
|
+
_errorListeners = /* @__PURE__ */ new Set();
|
|
1513
|
+
get dataListeners() {
|
|
1514
|
+
return [...this._dataListeners];
|
|
1515
|
+
}
|
|
1516
|
+
get closeListeners() {
|
|
1517
|
+
return [...this._closeListeners];
|
|
1518
|
+
}
|
|
1519
|
+
get errorListeners() {
|
|
1520
|
+
return [...this._errorListeners];
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Handle adding a callback for when a message is received.
|
|
1524
|
+
* @param msg The message that was received.
|
|
1525
|
+
*/
|
|
1526
|
+
addDataListener(cb) {
|
|
1527
|
+
this._dataListeners.add(cb);
|
|
1528
|
+
}
|
|
1529
|
+
removeDataListener(cb) {
|
|
1530
|
+
this._dataListeners.delete(cb);
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Handle adding a callback for when the connection is closed.
|
|
1534
|
+
* This should also be called if an error happens and after notifying all the error listeners.
|
|
1535
|
+
* @param cb The callback to call when the connection is closed.
|
|
1536
|
+
*/
|
|
1537
|
+
addCloseListener(cb) {
|
|
1538
|
+
this._closeListeners.add(cb);
|
|
1539
|
+
}
|
|
1540
|
+
removeCloseListener(cb) {
|
|
1541
|
+
this._closeListeners.delete(cb);
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Handle adding a callback for when an error is received.
|
|
1545
|
+
* This should only be used for this.logging errors, all cleanup
|
|
1546
|
+
* should be delegated to addCloseListener.
|
|
1547
|
+
*
|
|
1548
|
+
* The implementer should take care such that the implemented
|
|
1549
|
+
* connection will call both the close and error callbacks
|
|
1550
|
+
* on an error.
|
|
1551
|
+
*
|
|
1552
|
+
* @param cb The callback to call when an error is received.
|
|
1553
|
+
*/
|
|
1554
|
+
addErrorListener(cb) {
|
|
1555
|
+
this._errorListeners.add(cb);
|
|
1556
|
+
}
|
|
1557
|
+
removeErrorListener(cb) {
|
|
1558
|
+
this._errorListeners.delete(cb);
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1561
|
+
|
|
1542
1562
|
// transport/impls/ws/connection.ts
|
|
1543
1563
|
var WebSocketConnection = class extends Connection {
|
|
1544
|
-
errorCb = null;
|
|
1545
|
-
closeCb = null;
|
|
1546
1564
|
ws;
|
|
1547
1565
|
constructor(ws) {
|
|
1548
1566
|
super();
|
|
@@ -1553,29 +1571,23 @@ var WebSocketConnection = class extends Connection {
|
|
|
1553
1571
|
didError = true;
|
|
1554
1572
|
};
|
|
1555
1573
|
this.ws.onclose = ({ code, reason }) => {
|
|
1556
|
-
if (didError
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
`websocket closed with code and reason: ${code} - ${reason}`
|
|
1560
|
-
)
|
|
1574
|
+
if (didError) {
|
|
1575
|
+
const err = new Error(
|
|
1576
|
+
`websocket closed with code and reason: ${code} - ${reason}`
|
|
1561
1577
|
);
|
|
1578
|
+
for (const cb of this.errorListeners) {
|
|
1579
|
+
cb(err);
|
|
1580
|
+
}
|
|
1562
1581
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1582
|
+
for (const cb of this.closeListeners) {
|
|
1583
|
+
cb();
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
this.ws.onmessage = (msg) => {
|
|
1587
|
+
for (const cb of this.dataListeners) {
|
|
1588
|
+
cb(msg.data);
|
|
1565
1589
|
}
|
|
1566
1590
|
};
|
|
1567
|
-
}
|
|
1568
|
-
addDataListener(cb) {
|
|
1569
|
-
this.ws.onmessage = (msg) => cb(msg.data);
|
|
1570
|
-
}
|
|
1571
|
-
removeDataListener() {
|
|
1572
|
-
this.ws.onmessage = null;
|
|
1573
|
-
}
|
|
1574
|
-
addCloseListener(cb) {
|
|
1575
|
-
this.closeCb = cb;
|
|
1576
|
-
}
|
|
1577
|
-
addErrorListener(cb) {
|
|
1578
|
-
this.errorCb = cb;
|
|
1579
1591
|
}
|
|
1580
1592
|
send(payload) {
|
|
1581
1593
|
if (this.ws.readyState !== this.ws.OPEN) {
|
|
@@ -1636,7 +1648,6 @@ var WebSocketClientTransport = class extends ClientTransport {
|
|
|
1636
1648
|
clientId: this.clientId,
|
|
1637
1649
|
connectedTo: to
|
|
1638
1650
|
});
|
|
1639
|
-
this.handleConnection(conn, to);
|
|
1640
1651
|
return conn;
|
|
1641
1652
|
}
|
|
1642
1653
|
};
|