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