@replit/river 0.200.0-rc.2 → 0.200.0-rc.20
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 +30 -29
- package/dist/chunk-3HI3IJTL.js +285 -0
- package/dist/chunk-3HI3IJTL.js.map +1 -0
- package/dist/chunk-5L5RNZXH.js +391 -0
- package/dist/chunk-5L5RNZXH.js.map +1 -0
- package/dist/{chunk-QMM35C3H.js → chunk-BAGOAJ3K.js} +1 -1
- package/dist/chunk-BAGOAJ3K.js.map +1 -0
- package/dist/{chunk-S5RL45KH.js → chunk-BYCR4VEM.js} +78 -54
- package/dist/chunk-BYCR4VEM.js.map +1 -0
- package/dist/chunk-DM5QR4HQ.js +60 -0
- package/dist/chunk-DM5QR4HQ.js.map +1 -0
- package/dist/chunk-OLWVR5AB.js +860 -0
- package/dist/chunk-OLWVR5AB.js.map +1 -0
- package/dist/chunk-WKBWCRGN.js +437 -0
- package/dist/chunk-WKBWCRGN.js.map +1 -0
- package/dist/chunk-YBCQVIPR.js +351 -0
- package/dist/chunk-YBCQVIPR.js.map +1 -0
- package/dist/client-75090f07.d.ts +49 -0
- package/dist/connection-c9f96b64.d.ts +32 -0
- package/dist/context-9c907028.d.ts +622 -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-59fe53e1.d.ts} +34 -31
- package/dist/router/index.cjs +771 -1159
- package/dist/router/index.cjs.map +1 -1
- package/dist/router/index.d.cts +14 -48
- package/dist/router/index.d.ts +14 -48
- package/dist/router/index.js +1238 -15
- package/dist/router/index.js.map +1 -1
- package/dist/server-109a29e2.d.ts +69 -0
- package/dist/services-aa49a9fb.d.ts +811 -0
- package/dist/transport/impls/ws/client.cjs +1293 -1034
- package/dist/transport/impls/ws/client.cjs.map +1 -1
- package/dist/transport/impls/ws/client.d.cts +7 -5
- package/dist/transport/impls/ws/client.d.ts +7 -5
- package/dist/transport/impls/ws/client.js +11 -11
- package/dist/transport/impls/ws/client.js.map +1 -1
- package/dist/transport/impls/ws/server.cjs +1437 -1072
- package/dist/transport/impls/ws/server.cjs.map +1 -1
- package/dist/transport/impls/ws/server.d.cts +7 -5
- package/dist/transport/impls/ws/server.d.ts +7 -5
- package/dist/transport/impls/ws/server.js +20 -8
- package/dist/transport/impls/ws/server.js.map +1 -1
- package/dist/transport/index.cjs +1720 -1400
- package/dist/transport/index.cjs.map +1 -1
- package/dist/transport/index.d.cts +5 -26
- package/dist/transport/index.d.ts +5 -26
- package/dist/transport/index.js +11 -11
- package/dist/util/testHelpers.cjs +1164 -591
- package/dist/util/testHelpers.cjs.map +1 -1
- package/dist/util/testHelpers.d.cts +41 -38
- package/dist/util/testHelpers.d.ts +41 -38
- package/dist/util/testHelpers.js +124 -89
- package/dist/util/testHelpers.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-47TFNAY2.js +0 -476
- package/dist/chunk-47TFNAY2.js.map +0 -1
- package/dist/chunk-4VNY34QG.js +0 -106
- package/dist/chunk-4VNY34QG.js.map +0 -1
- package/dist/chunk-7CKIN3JT.js +0 -2004
- 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
- package/dist/connection-f900e390.d.ts +0 -35
- package/dist/services-970f97bb.d.ts +0 -1372
- package/dist/transport/impls/uds/client.cjs +0 -1687
- package/dist/transport/impls/uds/client.cjs.map +0 -1
- package/dist/transport/impls/uds/client.d.cts +0 -17
- package/dist/transport/impls/uds/client.d.ts +0 -17
- package/dist/transport/impls/uds/client.js +0 -44
- package/dist/transport/impls/uds/client.js.map +0 -1
- package/dist/transport/impls/uds/server.cjs +0 -1522
- package/dist/transport/impls/uds/server.cjs.map +0 -1
- package/dist/transport/impls/uds/server.d.cts +0 -19
- package/dist/transport/impls/uds/server.d.ts +0 -19
- package/dist/transport/impls/uds/server.js +0 -33
- package/dist/transport/impls/uds/server.js.map +0 -1
|
@@ -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,35 @@ 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
|
-
reconnect: import_typebox.Type.Boolean(),
|
|
74
|
-
nextExpectedSeq: import_typebox.Type.Integer()
|
|
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
|
+
nextSentSeq: import_typebox.Type.Integer()
|
|
79
|
+
}),
|
|
77
80
|
metadata: import_typebox.Type.Optional(import_typebox.Type.Unknown())
|
|
78
81
|
});
|
|
82
|
+
var HandshakeErrorRetriableResponseCodes = import_typebox.Type.Union([
|
|
83
|
+
import_typebox.Type.Literal("SESSION_STATE_MISMATCH")
|
|
84
|
+
]);
|
|
85
|
+
var HandshakeErrorCustomHandlerFatalResponseCodes = import_typebox.Type.Union([
|
|
86
|
+
// The custom validation handler rejected the handler because the client is unsupported.
|
|
87
|
+
import_typebox.Type.Literal("REJECTED_UNSUPPORTED_CLIENT"),
|
|
88
|
+
// The custom validation handler rejected the handshake.
|
|
89
|
+
import_typebox.Type.Literal("REJECTED_BY_CUSTOM_HANDLER")
|
|
90
|
+
]);
|
|
91
|
+
var HandshakeErrorFatalResponseCodes = import_typebox.Type.Union([
|
|
92
|
+
HandshakeErrorCustomHandlerFatalResponseCodes,
|
|
93
|
+
// The ciient sent a handshake that doesn't comply with the extended handshake metadata.
|
|
94
|
+
import_typebox.Type.Literal("MALFORMED_HANDSHAKE_META"),
|
|
95
|
+
// The ciient sent a handshake that doesn't comply with ControlMessageHandshakeRequestSchema.
|
|
96
|
+
import_typebox.Type.Literal("MALFORMED_HANDSHAKE"),
|
|
97
|
+
// The client's protocol version does not match the server's.
|
|
98
|
+
import_typebox.Type.Literal("PROTOCOL_VERSION_MISMATCH")
|
|
99
|
+
]);
|
|
100
|
+
var HandshakeErrorResponseCodes = import_typebox.Type.Union([
|
|
101
|
+
HandshakeErrorRetriableResponseCodes,
|
|
102
|
+
HandshakeErrorFatalResponseCodes
|
|
103
|
+
]);
|
|
79
104
|
var ControlMessageHandshakeResponseSchema = import_typebox.Type.Object({
|
|
80
105
|
type: import_typebox.Type.Literal("HANDSHAKE_RESP"),
|
|
81
106
|
status: import_typebox.Type.Union([
|
|
@@ -85,7 +110,8 @@ var ControlMessageHandshakeResponseSchema = import_typebox.Type.Object({
|
|
|
85
110
|
}),
|
|
86
111
|
import_typebox.Type.Object({
|
|
87
112
|
ok: import_typebox.Type.Literal(false),
|
|
88
|
-
reason: import_typebox.Type.String()
|
|
113
|
+
reason: import_typebox.Type.String(),
|
|
114
|
+
code: HandshakeErrorResponseCodes
|
|
89
115
|
})
|
|
90
116
|
])
|
|
91
117
|
});
|
|
@@ -107,24 +133,23 @@ function handshakeRequestMessage({
|
|
|
107
133
|
tracing
|
|
108
134
|
}) {
|
|
109
135
|
return {
|
|
110
|
-
id: (
|
|
136
|
+
id: generateId(),
|
|
111
137
|
from,
|
|
112
138
|
to,
|
|
113
139
|
seq: 0,
|
|
114
140
|
ack: 0,
|
|
115
|
-
streamId: (
|
|
141
|
+
streamId: generateId(),
|
|
116
142
|
controlFlags: 0,
|
|
117
143
|
tracing,
|
|
118
144
|
payload: {
|
|
119
145
|
type: "HANDSHAKE_REQ",
|
|
120
|
-
protocolVersion:
|
|
146
|
+
protocolVersion: currentProtocolVersion,
|
|
121
147
|
sessionId,
|
|
122
148
|
expectedSessionState,
|
|
123
149
|
metadata
|
|
124
150
|
}
|
|
125
151
|
};
|
|
126
152
|
}
|
|
127
|
-
var SESSION_STATE_MISMATCH = "session state mismatch";
|
|
128
153
|
function isAck(controlFlag) {
|
|
129
154
|
return (controlFlag & 1 /* AckBit */) === 1 /* AckBit */;
|
|
130
155
|
}
|
|
@@ -186,10 +211,13 @@ var defaultTransportOptions = {
|
|
|
186
211
|
heartbeatIntervalMs: 1e3,
|
|
187
212
|
heartbeatsUntilDead: 2,
|
|
188
213
|
sessionDisconnectGraceMs: 5e3,
|
|
214
|
+
connectionTimeoutMs: 2e3,
|
|
215
|
+
handshakeTimeoutMs: 1e3,
|
|
216
|
+
enableTransparentSessionReconnects: true,
|
|
189
217
|
codec: NaiveJsonCodec
|
|
190
218
|
};
|
|
191
219
|
var defaultConnectionRetryOptions = {
|
|
192
|
-
baseIntervalMs:
|
|
220
|
+
baseIntervalMs: 150,
|
|
193
221
|
maxJitterMs: 200,
|
|
194
222
|
maxBackoffMs: 32e3,
|
|
195
223
|
attemptBudgetCapacity: 5,
|
|
@@ -206,17 +234,17 @@ var defaultServerTransportOptions = {
|
|
|
206
234
|
// transport/rateLimit.ts
|
|
207
235
|
var LeakyBucketRateLimit = class {
|
|
208
236
|
budgetConsumed;
|
|
209
|
-
|
|
237
|
+
intervalHandle;
|
|
210
238
|
options;
|
|
211
239
|
constructor(options) {
|
|
212
240
|
this.options = options;
|
|
213
|
-
this.budgetConsumed =
|
|
214
|
-
this.intervalHandles = /* @__PURE__ */ new Map();
|
|
241
|
+
this.budgetConsumed = 0;
|
|
215
242
|
}
|
|
216
|
-
getBackoffMs(
|
|
217
|
-
if (
|
|
243
|
+
getBackoffMs() {
|
|
244
|
+
if (this.getBudgetConsumed() === 0) {
|
|
218
245
|
return 0;
|
|
219
|
-
|
|
246
|
+
}
|
|
247
|
+
const exponent = Math.max(0, this.getBudgetConsumed() - 1);
|
|
220
248
|
const jitter = Math.floor(Math.random() * this.options.maxJitterMs);
|
|
221
249
|
const backoffMs = Math.min(
|
|
222
250
|
this.options.baseIntervalMs * 2 ** exponent,
|
|
@@ -227,56 +255,49 @@ var LeakyBucketRateLimit = class {
|
|
|
227
255
|
get totalBudgetRestoreTime() {
|
|
228
256
|
return this.options.budgetRestoreIntervalMs * this.options.attemptBudgetCapacity;
|
|
229
257
|
}
|
|
230
|
-
consumeBudget(
|
|
231
|
-
this.stopLeak(
|
|
232
|
-
this.budgetConsumed
|
|
258
|
+
consumeBudget() {
|
|
259
|
+
this.stopLeak();
|
|
260
|
+
this.budgetConsumed = this.getBudgetConsumed() + 1;
|
|
233
261
|
}
|
|
234
|
-
getBudgetConsumed(
|
|
235
|
-
return this.budgetConsumed
|
|
262
|
+
getBudgetConsumed() {
|
|
263
|
+
return this.budgetConsumed;
|
|
236
264
|
}
|
|
237
|
-
hasBudget(
|
|
238
|
-
return this.getBudgetConsumed(
|
|
265
|
+
hasBudget() {
|
|
266
|
+
return this.getBudgetConsumed() < this.options.attemptBudgetCapacity;
|
|
239
267
|
}
|
|
240
|
-
startRestoringBudget(
|
|
241
|
-
if (this.
|
|
268
|
+
startRestoringBudget() {
|
|
269
|
+
if (this.intervalHandle) {
|
|
242
270
|
return;
|
|
243
271
|
}
|
|
244
272
|
const restoreBudgetForUser = () => {
|
|
245
|
-
const currentBudget = this.budgetConsumed
|
|
273
|
+
const currentBudget = this.budgetConsumed;
|
|
246
274
|
if (!currentBudget) {
|
|
247
|
-
this.stopLeak(
|
|
275
|
+
this.stopLeak();
|
|
248
276
|
return;
|
|
249
277
|
}
|
|
250
278
|
const newBudget = currentBudget - 1;
|
|
251
279
|
if (newBudget === 0) {
|
|
252
|
-
this.budgetConsumed.delete(user);
|
|
253
280
|
return;
|
|
254
281
|
}
|
|
255
|
-
this.budgetConsumed
|
|
282
|
+
this.budgetConsumed = newBudget;
|
|
256
283
|
};
|
|
257
|
-
|
|
284
|
+
this.intervalHandle = setInterval(
|
|
258
285
|
restoreBudgetForUser,
|
|
259
286
|
this.options.budgetRestoreIntervalMs
|
|
260
287
|
);
|
|
261
|
-
this.intervalHandles.set(user, intervalHandle);
|
|
262
288
|
}
|
|
263
|
-
stopLeak(
|
|
264
|
-
if (!this.
|
|
289
|
+
stopLeak() {
|
|
290
|
+
if (!this.intervalHandle) {
|
|
265
291
|
return;
|
|
266
292
|
}
|
|
267
|
-
clearInterval(this.
|
|
268
|
-
this.
|
|
293
|
+
clearInterval(this.intervalHandle);
|
|
294
|
+
this.intervalHandle = void 0;
|
|
269
295
|
}
|
|
270
296
|
close() {
|
|
271
|
-
|
|
272
|
-
this.stopLeak(user);
|
|
273
|
-
}
|
|
297
|
+
this.stopLeak();
|
|
274
298
|
}
|
|
275
299
|
};
|
|
276
300
|
|
|
277
|
-
// transport/transport.ts
|
|
278
|
-
var import_value = require("@sinclair/typebox/value");
|
|
279
|
-
|
|
280
301
|
// logging/log.ts
|
|
281
302
|
var LoggingLevels = {
|
|
282
303
|
debug: -1,
|
|
@@ -334,7 +355,8 @@ var createLogProxy = (log) => ({
|
|
|
334
355
|
var ProtocolError = {
|
|
335
356
|
RetriesExceeded: "conn_retry_exceeded",
|
|
336
357
|
HandshakeFailed: "handshake_failed",
|
|
337
|
-
MessageOrderingViolated: "message_ordering_violated"
|
|
358
|
+
MessageOrderingViolated: "message_ordering_violated",
|
|
359
|
+
InvalidMessage: "invalid_message"
|
|
338
360
|
};
|
|
339
361
|
var EventDispatcher = class {
|
|
340
362
|
eventListeners = {};
|
|
@@ -367,14 +389,252 @@ var EventDispatcher = class {
|
|
|
367
389
|
}
|
|
368
390
|
};
|
|
369
391
|
|
|
370
|
-
// transport/
|
|
371
|
-
var
|
|
392
|
+
// transport/sessionStateMachine/common.ts
|
|
393
|
+
var import_value = require("@sinclair/typebox/value");
|
|
394
|
+
var ERR_CONSUMED = `session state has been consumed and is no longer valid`;
|
|
395
|
+
var StateMachineState = class {
|
|
396
|
+
/*
|
|
397
|
+
* Whether this state has been consumed
|
|
398
|
+
* and we've moved on to another state
|
|
399
|
+
*/
|
|
400
|
+
_isConsumed;
|
|
401
|
+
close() {
|
|
402
|
+
this._handleClose();
|
|
403
|
+
}
|
|
404
|
+
constructor() {
|
|
405
|
+
this._isConsumed = false;
|
|
406
|
+
return new Proxy(this, {
|
|
407
|
+
get(target, prop) {
|
|
408
|
+
if (prop === "_isConsumed" || prop === "id" || prop === "state") {
|
|
409
|
+
return Reflect.get(target, prop);
|
|
410
|
+
}
|
|
411
|
+
if (prop === "_handleStateExit") {
|
|
412
|
+
return () => {
|
|
413
|
+
target._isConsumed = true;
|
|
414
|
+
target._handleStateExit();
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
if (prop === "_handleClose") {
|
|
418
|
+
return () => {
|
|
419
|
+
target._isConsumed = true;
|
|
420
|
+
target._handleStateExit();
|
|
421
|
+
target._handleClose();
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
if (target._isConsumed) {
|
|
425
|
+
throw new Error(
|
|
426
|
+
`${ERR_CONSUMED}: getting ${prop.toString()} on consumed state`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
return Reflect.get(target, prop);
|
|
430
|
+
},
|
|
431
|
+
set(target, prop, value) {
|
|
432
|
+
if (target._isConsumed) {
|
|
433
|
+
throw new Error(
|
|
434
|
+
`${ERR_CONSUMED}: setting ${prop.toString()} on consumed state`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
return Reflect.set(target, prop, value);
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
var CommonSession = class extends StateMachineState {
|
|
443
|
+
from;
|
|
444
|
+
options;
|
|
445
|
+
log;
|
|
446
|
+
constructor({ from, options, log }) {
|
|
447
|
+
super();
|
|
448
|
+
this.from = from;
|
|
449
|
+
this.options = options;
|
|
450
|
+
this.log = log;
|
|
451
|
+
}
|
|
452
|
+
parseMsg(msg) {
|
|
453
|
+
const parsedMsg = this.options.codec.fromBuffer(msg);
|
|
454
|
+
if (parsedMsg === null) {
|
|
455
|
+
const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));
|
|
456
|
+
this.log?.error(
|
|
457
|
+
`received malformed msg: ${decodedBuffer}`,
|
|
458
|
+
this.loggingMetadata
|
|
459
|
+
);
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
|
|
463
|
+
this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
|
|
464
|
+
...this.loggingMetadata,
|
|
465
|
+
validationErrors: [
|
|
466
|
+
...import_value.Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
|
|
467
|
+
]
|
|
468
|
+
});
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
return parsedMsg;
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
var IdentifiedSession = class extends CommonSession {
|
|
475
|
+
id;
|
|
476
|
+
telemetry;
|
|
477
|
+
to;
|
|
478
|
+
protocolVersion;
|
|
479
|
+
/**
|
|
480
|
+
* Index of the message we will send next (excluding handshake)
|
|
481
|
+
*/
|
|
482
|
+
seq;
|
|
483
|
+
/**
|
|
484
|
+
* Number of unique messages we've received this session (excluding handshake)
|
|
485
|
+
*/
|
|
486
|
+
ack;
|
|
487
|
+
sendBuffer;
|
|
488
|
+
constructor(props) {
|
|
489
|
+
const { id, to, seq, ack, sendBuffer, telemetry, log, protocolVersion } = props;
|
|
490
|
+
super(props);
|
|
491
|
+
this.id = id;
|
|
492
|
+
this.to = to;
|
|
493
|
+
this.seq = seq;
|
|
494
|
+
this.ack = ack;
|
|
495
|
+
this.sendBuffer = sendBuffer;
|
|
496
|
+
this.telemetry = telemetry;
|
|
497
|
+
this.log = log;
|
|
498
|
+
this.protocolVersion = protocolVersion;
|
|
499
|
+
}
|
|
500
|
+
get loggingMetadata() {
|
|
501
|
+
const spanContext = this.telemetry.span.spanContext();
|
|
502
|
+
const metadata = {
|
|
503
|
+
clientId: this.from,
|
|
504
|
+
connectedTo: this.to,
|
|
505
|
+
sessionId: this.id
|
|
506
|
+
};
|
|
507
|
+
if (this.telemetry.span.isRecording()) {
|
|
508
|
+
metadata.telemetry = {
|
|
509
|
+
traceId: spanContext.traceId,
|
|
510
|
+
spanId: spanContext.spanId
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
return metadata;
|
|
514
|
+
}
|
|
515
|
+
constructMsg(partialMsg) {
|
|
516
|
+
const msg = {
|
|
517
|
+
...partialMsg,
|
|
518
|
+
id: generateId(),
|
|
519
|
+
to: this.to,
|
|
520
|
+
from: this.from,
|
|
521
|
+
seq: this.seq,
|
|
522
|
+
ack: this.ack
|
|
523
|
+
};
|
|
524
|
+
this.seq++;
|
|
525
|
+
return msg;
|
|
526
|
+
}
|
|
527
|
+
nextSeq() {
|
|
528
|
+
return this.sendBuffer.length > 0 ? this.sendBuffer[0].seq : this.seq;
|
|
529
|
+
}
|
|
530
|
+
send(msg) {
|
|
531
|
+
const constructedMsg = this.constructMsg(msg);
|
|
532
|
+
this.sendBuffer.push(constructedMsg);
|
|
533
|
+
return constructedMsg.id;
|
|
534
|
+
}
|
|
535
|
+
_handleStateExit() {
|
|
536
|
+
}
|
|
537
|
+
_handleClose() {
|
|
538
|
+
this.sendBuffer.length = 0;
|
|
539
|
+
this.telemetry.span.end();
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
var IdentifiedSessionWithGracePeriod = class extends IdentifiedSession {
|
|
543
|
+
graceExpiryTime;
|
|
544
|
+
gracePeriodTimeout;
|
|
545
|
+
listeners;
|
|
546
|
+
constructor(props) {
|
|
547
|
+
super(props);
|
|
548
|
+
this.listeners = props.listeners;
|
|
549
|
+
this.graceExpiryTime = props.graceExpiryTime;
|
|
550
|
+
this.gracePeriodTimeout = setTimeout(() => {
|
|
551
|
+
this.listeners.onSessionGracePeriodElapsed();
|
|
552
|
+
}, this.graceExpiryTime - Date.now());
|
|
553
|
+
}
|
|
554
|
+
_handleStateExit() {
|
|
555
|
+
super._handleStateExit();
|
|
556
|
+
if (this.gracePeriodTimeout) {
|
|
557
|
+
clearTimeout(this.gracePeriodTimeout);
|
|
558
|
+
this.gracePeriodTimeout = void 0;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
_handleClose() {
|
|
562
|
+
super._handleClose();
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
// transport/sessionStateMachine/SessionConnecting.ts
|
|
567
|
+
var SessionConnecting = class extends IdentifiedSessionWithGracePeriod {
|
|
568
|
+
state = "Connecting" /* Connecting */;
|
|
569
|
+
connPromise;
|
|
570
|
+
listeners;
|
|
571
|
+
connectionTimeout;
|
|
572
|
+
constructor(props) {
|
|
573
|
+
super(props);
|
|
574
|
+
this.connPromise = props.connPromise;
|
|
575
|
+
this.listeners = props.listeners;
|
|
576
|
+
this.connPromise.then(
|
|
577
|
+
(conn) => {
|
|
578
|
+
if (this._isConsumed)
|
|
579
|
+
return;
|
|
580
|
+
this.listeners.onConnectionEstablished(conn);
|
|
581
|
+
},
|
|
582
|
+
(err) => {
|
|
583
|
+
if (this._isConsumed)
|
|
584
|
+
return;
|
|
585
|
+
this.listeners.onConnectionFailed(err);
|
|
586
|
+
}
|
|
587
|
+
);
|
|
588
|
+
this.connectionTimeout = setTimeout(() => {
|
|
589
|
+
this.listeners.onConnectionTimeout();
|
|
590
|
+
}, this.options.connectionTimeoutMs);
|
|
591
|
+
}
|
|
592
|
+
// close a pending connection if it resolves, ignore errors if the promise
|
|
593
|
+
// ends up rejected anyways
|
|
594
|
+
bestEffortClose() {
|
|
595
|
+
const logger = this.log;
|
|
596
|
+
const metadata = this.loggingMetadata;
|
|
597
|
+
this.connPromise.then((conn) => {
|
|
598
|
+
conn.close();
|
|
599
|
+
logger?.info(
|
|
600
|
+
"connection eventually resolved but session has transitioned, closed connection",
|
|
601
|
+
{
|
|
602
|
+
...metadata,
|
|
603
|
+
...conn.loggingMetadata
|
|
604
|
+
}
|
|
605
|
+
);
|
|
606
|
+
}).catch(() => {
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
_handleStateExit() {
|
|
610
|
+
super._handleStateExit();
|
|
611
|
+
if (this.connectionTimeout) {
|
|
612
|
+
clearTimeout(this.connectionTimeout);
|
|
613
|
+
this.connectionTimeout = void 0;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
_handleClose() {
|
|
617
|
+
this.bestEffortClose();
|
|
618
|
+
super._handleClose();
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
// transport/sessionStateMachine/SessionNoConnection.ts
|
|
623
|
+
var SessionNoConnection = class extends IdentifiedSessionWithGracePeriod {
|
|
624
|
+
state = "NoConnection" /* NoConnection */;
|
|
625
|
+
_handleClose() {
|
|
626
|
+
super._handleClose();
|
|
627
|
+
}
|
|
628
|
+
_handleStateExit() {
|
|
629
|
+
super._handleStateExit();
|
|
630
|
+
}
|
|
631
|
+
};
|
|
372
632
|
|
|
373
633
|
// tracing/index.ts
|
|
374
634
|
var import_api = require("@opentelemetry/api");
|
|
375
635
|
|
|
376
636
|
// package.json
|
|
377
|
-
var version = "0.200.0-rc.
|
|
637
|
+
var version = "0.200.0-rc.20";
|
|
378
638
|
|
|
379
639
|
// tracing/index.ts
|
|
380
640
|
function getPropagationContext(ctx) {
|
|
@@ -385,16 +645,16 @@ function getPropagationContext(ctx) {
|
|
|
385
645
|
import_api.propagation.inject(ctx, tracing);
|
|
386
646
|
return tracing;
|
|
387
647
|
}
|
|
388
|
-
function createSessionTelemetryInfo(
|
|
648
|
+
function createSessionTelemetryInfo(sessionId, to, from, propagationCtx) {
|
|
389
649
|
const parentCtx = propagationCtx ? import_api.propagation.extract(import_api.context.active(), propagationCtx) : import_api.context.active();
|
|
390
650
|
const span = tracer.startSpan(
|
|
391
|
-
`session ${
|
|
651
|
+
`session ${sessionId}`,
|
|
392
652
|
{
|
|
393
653
|
attributes: {
|
|
394
654
|
component: "river",
|
|
395
|
-
"river.session.id":
|
|
396
|
-
"river.session.to":
|
|
397
|
-
"river.session.from":
|
|
655
|
+
"river.session.id": sessionId,
|
|
656
|
+
"river.session.to": to,
|
|
657
|
+
"river.session.from": from
|
|
398
658
|
}
|
|
399
659
|
},
|
|
400
660
|
parentCtx
|
|
@@ -402,173 +662,180 @@ function createSessionTelemetryInfo(session, propagationCtx) {
|
|
|
402
662
|
const ctx = import_api.trace.setSpan(parentCtx, span);
|
|
403
663
|
return { span, ctx };
|
|
404
664
|
}
|
|
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
665
|
var tracer = import_api.trace.getTracer("river", version);
|
|
421
666
|
var tracing_default = tracer;
|
|
422
667
|
|
|
423
|
-
// transport/
|
|
424
|
-
var
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
this.
|
|
668
|
+
// transport/sessionStateMachine/SessionWaitingForHandshake.ts
|
|
669
|
+
var SessionWaitingForHandshake = class extends CommonSession {
|
|
670
|
+
state = "WaitingForHandshake" /* WaitingForHandshake */;
|
|
671
|
+
conn;
|
|
672
|
+
listeners;
|
|
673
|
+
handshakeTimeout;
|
|
674
|
+
constructor(props) {
|
|
675
|
+
super(props);
|
|
676
|
+
this.conn = props.conn;
|
|
677
|
+
this.listeners = props.listeners;
|
|
678
|
+
this.handshakeTimeout = setTimeout(() => {
|
|
679
|
+
this.listeners.onHandshakeTimeout();
|
|
680
|
+
}, this.options.handshakeTimeoutMs);
|
|
681
|
+
this.conn.addDataListener(this.onHandshakeData);
|
|
682
|
+
this.conn.addErrorListener(this.listeners.onConnectionErrored);
|
|
683
|
+
this.conn.addCloseListener(this.listeners.onConnectionClosed);
|
|
432
684
|
}
|
|
433
685
|
get loggingMetadata() {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
686
|
+
return {
|
|
687
|
+
clientId: this.from,
|
|
688
|
+
connId: this.conn.id,
|
|
689
|
+
...this.conn.loggingMetadata
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
onHandshakeData = (msg) => {
|
|
693
|
+
const parsedMsg = this.parseMsg(msg);
|
|
694
|
+
if (parsedMsg === null) {
|
|
695
|
+
this.listeners.onInvalidHandshake(
|
|
696
|
+
"could not parse message",
|
|
697
|
+
"MALFORMED_HANDSHAKE"
|
|
698
|
+
);
|
|
699
|
+
return;
|
|
441
700
|
}
|
|
442
|
-
|
|
701
|
+
this.listeners.onHandshake(parsedMsg);
|
|
702
|
+
};
|
|
703
|
+
sendHandshake(msg) {
|
|
704
|
+
return this.conn.send(this.options.codec.toBuffer(msg));
|
|
443
705
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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);
|
|
706
|
+
_handleStateExit() {
|
|
707
|
+
this.conn.removeDataListener(this.onHandshakeData);
|
|
708
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
709
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
710
|
+
clearTimeout(this.handshakeTimeout);
|
|
711
|
+
this.handshakeTimeout = void 0;
|
|
508
712
|
}
|
|
509
|
-
|
|
510
|
-
this.
|
|
713
|
+
_handleClose() {
|
|
714
|
+
this.conn.close();
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
// transport/sessionStateMachine/SessionHandshaking.ts
|
|
719
|
+
var SessionHandshaking = class extends IdentifiedSessionWithGracePeriod {
|
|
720
|
+
state = "Handshaking" /* Handshaking */;
|
|
721
|
+
conn;
|
|
722
|
+
listeners;
|
|
723
|
+
handshakeTimeout;
|
|
724
|
+
constructor(props) {
|
|
725
|
+
super(props);
|
|
726
|
+
this.conn = props.conn;
|
|
727
|
+
this.listeners = props.listeners;
|
|
728
|
+
this.handshakeTimeout = setTimeout(() => {
|
|
729
|
+
this.listeners.onHandshakeTimeout();
|
|
730
|
+
}, this.options.handshakeTimeoutMs);
|
|
731
|
+
this.conn.addDataListener(this.onHandshakeData);
|
|
732
|
+
this.conn.addErrorListener(this.listeners.onConnectionErrored);
|
|
733
|
+
this.conn.addCloseListener(this.listeners.onConnectionClosed);
|
|
511
734
|
}
|
|
512
735
|
get loggingMetadata() {
|
|
513
|
-
const spanContext = this.telemetry.span.spanContext();
|
|
514
736
|
return {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
sessionId: this.id,
|
|
518
|
-
connId: this.connection?.id,
|
|
519
|
-
telemetry: {
|
|
520
|
-
traceId: spanContext.traceId,
|
|
521
|
-
spanId: spanContext.spanId
|
|
522
|
-
}
|
|
737
|
+
...super.loggingMetadata,
|
|
738
|
+
...this.conn.loggingMetadata
|
|
523
739
|
};
|
|
524
740
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
741
|
+
onHandshakeData = (msg) => {
|
|
742
|
+
const parsedMsg = this.parseMsg(msg);
|
|
743
|
+
if (parsedMsg === null) {
|
|
744
|
+
this.listeners.onInvalidHandshake(
|
|
745
|
+
"could not parse message",
|
|
746
|
+
"MALFORMED_HANDSHAKE"
|
|
747
|
+
);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
this.listeners.onHandshake(parsedMsg);
|
|
751
|
+
};
|
|
752
|
+
sendHandshake(msg) {
|
|
753
|
+
return this.conn.send(this.options.codec.toBuffer(msg));
|
|
754
|
+
}
|
|
755
|
+
_handleStateExit() {
|
|
756
|
+
super._handleStateExit();
|
|
757
|
+
this.conn.removeDataListener(this.onHandshakeData);
|
|
758
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
759
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
760
|
+
if (this.handshakeTimeout) {
|
|
761
|
+
clearTimeout(this.handshakeTimeout);
|
|
762
|
+
this.handshakeTimeout = void 0;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
_handleClose() {
|
|
766
|
+
super._handleClose();
|
|
767
|
+
this.conn.close();
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
// transport/sessionStateMachine/SessionConnected.ts
|
|
772
|
+
var import_api2 = require("@opentelemetry/api");
|
|
773
|
+
var SessionConnected = class extends IdentifiedSession {
|
|
774
|
+
state = "Connected" /* Connected */;
|
|
775
|
+
conn;
|
|
776
|
+
listeners;
|
|
777
|
+
heartbeatHandle;
|
|
778
|
+
heartbeatMisses = 0;
|
|
779
|
+
isActivelyHeartbeating;
|
|
780
|
+
updateBookkeeping(ack, seq) {
|
|
781
|
+
this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
|
|
782
|
+
this.ack = seq + 1;
|
|
783
|
+
this.heartbeatMisses = 0;
|
|
784
|
+
}
|
|
533
785
|
send(msg) {
|
|
534
|
-
const
|
|
535
|
-
this.
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
786
|
+
const constructedMsg = this.constructMsg(msg);
|
|
787
|
+
this.sendBuffer.push(constructedMsg);
|
|
788
|
+
this.conn.send(this.options.codec.toBuffer(constructedMsg));
|
|
789
|
+
return constructedMsg.id;
|
|
790
|
+
}
|
|
791
|
+
constructor(props) {
|
|
792
|
+
super(props);
|
|
793
|
+
this.conn = props.conn;
|
|
794
|
+
this.listeners = props.listeners;
|
|
795
|
+
this.conn.addDataListener(this.onMessageData);
|
|
796
|
+
this.conn.addCloseListener(this.listeners.onConnectionClosed);
|
|
797
|
+
this.conn.addErrorListener(this.listeners.onConnectionErrored);
|
|
798
|
+
if (this.sendBuffer.length > 0) {
|
|
543
799
|
this.log?.info(
|
|
544
|
-
`
|
|
545
|
-
|
|
546
|
-
...this.loggingMetadata,
|
|
547
|
-
transportMessage: fullMsg
|
|
548
|
-
}
|
|
549
|
-
);
|
|
550
|
-
} else {
|
|
551
|
-
this.log?.debug(
|
|
552
|
-
`buffering msg to ${fullMsg.to}, connection not ready yet`,
|
|
553
|
-
{ ...this.loggingMetadata, transportMessage: fullMsg }
|
|
800
|
+
`sending ${this.sendBuffer.length} buffered messages, starting at seq ${this.nextSeq()}`,
|
|
801
|
+
this.loggingMetadata
|
|
554
802
|
);
|
|
803
|
+
for (const msg of this.sendBuffer) {
|
|
804
|
+
this.conn.send(this.options.codec.toBuffer(msg));
|
|
805
|
+
}
|
|
555
806
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
if (misses > this.options.heartbeatsUntilDead) {
|
|
562
|
-
if (this.connection) {
|
|
807
|
+
this.isActivelyHeartbeating = false;
|
|
808
|
+
this.heartbeatHandle = setInterval(() => {
|
|
809
|
+
const misses = this.heartbeatMisses;
|
|
810
|
+
const missDuration = misses * this.options.heartbeatIntervalMs;
|
|
811
|
+
if (misses >= this.options.heartbeatsUntilDead) {
|
|
563
812
|
this.log?.info(
|
|
564
813
|
`closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
|
|
565
814
|
this.loggingMetadata
|
|
566
815
|
);
|
|
567
816
|
this.telemetry.span.addEvent("closing connection due to inactivity");
|
|
568
|
-
this.
|
|
817
|
+
this.conn.close();
|
|
818
|
+
clearInterval(this.heartbeatHandle);
|
|
819
|
+
this.heartbeatHandle = void 0;
|
|
820
|
+
return;
|
|
569
821
|
}
|
|
570
|
-
|
|
571
|
-
|
|
822
|
+
if (this.isActivelyHeartbeating) {
|
|
823
|
+
this.sendHeartbeat();
|
|
824
|
+
}
|
|
825
|
+
this.heartbeatMisses++;
|
|
826
|
+
}, this.options.heartbeatIntervalMs);
|
|
827
|
+
}
|
|
828
|
+
get loggingMetadata() {
|
|
829
|
+
return {
|
|
830
|
+
...super.loggingMetadata,
|
|
831
|
+
...this.conn.loggingMetadata
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
startActiveHeartbeat() {
|
|
835
|
+
this.isActivelyHeartbeating = true;
|
|
836
|
+
}
|
|
837
|
+
sendHeartbeat() {
|
|
838
|
+
this.log?.debug("sending heartbeat", this.loggingMetadata);
|
|
572
839
|
this.send({
|
|
573
840
|
streamId: "heartbeat",
|
|
574
841
|
controlFlags: 1 /* AckBit */,
|
|
@@ -576,186 +843,403 @@ var Session = class {
|
|
|
576
843
|
type: "ACK"
|
|
577
844
|
}
|
|
578
845
|
});
|
|
579
|
-
this.heartbeatMisses++;
|
|
580
846
|
}
|
|
581
|
-
|
|
582
|
-
this.
|
|
583
|
-
this.
|
|
584
|
-
this.
|
|
847
|
+
closeConnection() {
|
|
848
|
+
this.conn.removeDataListener(this.onMessageData);
|
|
849
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
850
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
851
|
+
this.conn.close();
|
|
585
852
|
}
|
|
586
|
-
|
|
587
|
-
this.
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
this.log?.error(errMsg, {
|
|
853
|
+
onMessageData = (msg) => {
|
|
854
|
+
const parsedMsg = this.parseMsg(msg);
|
|
855
|
+
if (parsedMsg === null) {
|
|
856
|
+
this.listeners.onInvalidMessage("could not parse message");
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
if (parsedMsg.seq !== this.ack) {
|
|
860
|
+
if (parsedMsg.seq < this.ack) {
|
|
861
|
+
this.log?.debug(
|
|
862
|
+
`received duplicate msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack}), discarding`,
|
|
863
|
+
{
|
|
864
|
+
...this.loggingMetadata,
|
|
865
|
+
transportMessage: parsedMsg
|
|
866
|
+
}
|
|
867
|
+
);
|
|
868
|
+
} else {
|
|
869
|
+
const reason = `received out-of-order msg, closing connection (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack})`;
|
|
870
|
+
this.log?.warn(reason, {
|
|
605
871
|
...this.loggingMetadata,
|
|
606
|
-
transportMessage:
|
|
607
|
-
connId: conn.id,
|
|
872
|
+
transportMessage: parsedMsg,
|
|
608
873
|
tags: ["invariant-violation"]
|
|
609
874
|
});
|
|
610
|
-
|
|
611
|
-
|
|
875
|
+
this.telemetry.span.setStatus({
|
|
876
|
+
code: import_api2.SpanStatusCode.ERROR,
|
|
877
|
+
message: reason
|
|
878
|
+
});
|
|
879
|
+
this.closeConnection();
|
|
612
880
|
}
|
|
881
|
+
return;
|
|
613
882
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
883
|
+
this.log?.debug(`received msg`, {
|
|
884
|
+
...this.loggingMetadata,
|
|
885
|
+
transportMessage: parsedMsg
|
|
886
|
+
});
|
|
887
|
+
this.updateBookkeeping(parsedMsg.ack, parsedMsg.seq);
|
|
888
|
+
if (!isAck(parsedMsg.controlFlags)) {
|
|
889
|
+
this.listeners.onMessage(parsedMsg);
|
|
621
890
|
return;
|
|
622
891
|
}
|
|
623
|
-
this.
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
if (this.
|
|
628
|
-
|
|
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);
|
|
892
|
+
this.log?.debug(`discarding msg (ack bit set)`, {
|
|
893
|
+
...this.loggingMetadata,
|
|
894
|
+
transportMessage: parsedMsg
|
|
895
|
+
});
|
|
896
|
+
if (!this.isActivelyHeartbeating) {
|
|
897
|
+
this.sendHeartbeat();
|
|
641
898
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
this.
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
this.loggingMetadata
|
|
652
|
-
);
|
|
653
|
-
this.cancelGrace();
|
|
654
|
-
this.disconnectionGrace = setTimeout(() => {
|
|
655
|
-
this.log?.info(
|
|
656
|
-
`grace period for ${this.to} elapsed`,
|
|
657
|
-
this.loggingMetadata
|
|
658
|
-
);
|
|
659
|
-
cb();
|
|
660
|
-
}, this.options.sessionDisconnectGraceMs);
|
|
661
|
-
}
|
|
662
|
-
// called on reconnect of the underlying session
|
|
663
|
-
cancelGrace() {
|
|
664
|
-
this.heartbeatMisses = 0;
|
|
665
|
-
clearTimeout(this.disconnectionGrace);
|
|
666
|
-
this.disconnectionGrace = void 0;
|
|
667
|
-
}
|
|
668
|
-
/**
|
|
669
|
-
* Used to close the handshaking connection, if set.
|
|
670
|
-
*/
|
|
671
|
-
closeHandshakingConnection(expectedHandshakingConn) {
|
|
672
|
-
if (this.handshakingConnection === void 0)
|
|
673
|
-
return;
|
|
674
|
-
if (expectedHandshakingConn !== void 0 && this.handshakingConnection === expectedHandshakingConn) {
|
|
675
|
-
return;
|
|
899
|
+
};
|
|
900
|
+
_handleStateExit() {
|
|
901
|
+
super._handleStateExit();
|
|
902
|
+
this.conn.removeDataListener(this.onMessageData);
|
|
903
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
904
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
905
|
+
if (this.heartbeatHandle) {
|
|
906
|
+
clearInterval(this.heartbeatHandle);
|
|
907
|
+
this.heartbeatHandle = void 0;
|
|
676
908
|
}
|
|
677
|
-
this.handshakingConnection.close();
|
|
678
|
-
this.handshakingConnection = void 0;
|
|
679
|
-
}
|
|
680
|
-
// closed when we want to discard the whole session
|
|
681
|
-
// (i.e. shutdown or session disconnect)
|
|
682
|
-
close() {
|
|
683
|
-
this.closeStaleConnection();
|
|
684
|
-
this.cancelGrace();
|
|
685
|
-
this.resetBufferedMessages();
|
|
686
|
-
clearInterval(this.heartbeat);
|
|
687
|
-
}
|
|
688
|
-
get connected() {
|
|
689
|
-
return this.connection !== void 0;
|
|
690
|
-
}
|
|
691
|
-
get nextExpectedAck() {
|
|
692
|
-
return this.seq;
|
|
693
909
|
}
|
|
694
|
-
|
|
695
|
-
|
|
910
|
+
_handleClose() {
|
|
911
|
+
super._handleClose();
|
|
912
|
+
this.conn.close();
|
|
696
913
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
// transport/sessionStateMachine/SessionBackingOff.ts
|
|
917
|
+
var SessionBackingOff = class extends IdentifiedSessionWithGracePeriod {
|
|
918
|
+
state = "BackingOff" /* BackingOff */;
|
|
919
|
+
listeners;
|
|
920
|
+
backoffTimeout;
|
|
921
|
+
constructor(props) {
|
|
922
|
+
super(props);
|
|
923
|
+
this.listeners = props.listeners;
|
|
924
|
+
this.backoffTimeout = setTimeout(() => {
|
|
925
|
+
this.listeners.onBackoffFinished();
|
|
926
|
+
}, props.backoffMs);
|
|
927
|
+
}
|
|
928
|
+
_handleClose() {
|
|
929
|
+
super._handleClose();
|
|
930
|
+
}
|
|
931
|
+
_handleStateExit() {
|
|
932
|
+
super._handleStateExit();
|
|
933
|
+
if (this.backoffTimeout) {
|
|
934
|
+
clearTimeout(this.backoffTimeout);
|
|
935
|
+
this.backoffTimeout = void 0;
|
|
706
936
|
}
|
|
707
|
-
return nextExpectedSeq === this.seq;
|
|
708
937
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
// transport/sessionStateMachine/transitions.ts
|
|
941
|
+
function inheritSharedSession(session) {
|
|
942
|
+
return {
|
|
943
|
+
id: session.id,
|
|
944
|
+
from: session.from,
|
|
945
|
+
to: session.to,
|
|
946
|
+
seq: session.seq,
|
|
947
|
+
ack: session.ack,
|
|
948
|
+
sendBuffer: session.sendBuffer,
|
|
949
|
+
telemetry: session.telemetry,
|
|
950
|
+
options: session.options,
|
|
951
|
+
log: session.log,
|
|
952
|
+
protocolVersion: session.protocolVersion
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
function inheritSharedSessionWithGrace(session) {
|
|
956
|
+
return {
|
|
957
|
+
...inheritSharedSession(session),
|
|
958
|
+
graceExpiryTime: session.graceExpiryTime
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
var SessionStateGraph = {
|
|
962
|
+
entrypoints: {
|
|
963
|
+
NoConnection: (to, from, listeners, options, protocolVersion, log) => {
|
|
964
|
+
const id = `session-${generateId()}`;
|
|
965
|
+
const telemetry = createSessionTelemetryInfo(id, to, from);
|
|
966
|
+
const sendBuffer = [];
|
|
967
|
+
const session = new SessionNoConnection({
|
|
968
|
+
listeners,
|
|
969
|
+
id,
|
|
970
|
+
from,
|
|
971
|
+
to,
|
|
972
|
+
seq: 0,
|
|
973
|
+
ack: 0,
|
|
974
|
+
graceExpiryTime: Date.now() + options.sessionDisconnectGraceMs,
|
|
975
|
+
sendBuffer,
|
|
976
|
+
telemetry,
|
|
977
|
+
options,
|
|
978
|
+
protocolVersion,
|
|
979
|
+
log
|
|
980
|
+
});
|
|
981
|
+
session.log?.info(`session ${session.id} created in NoConnection state`, {
|
|
982
|
+
...session.loggingMetadata,
|
|
983
|
+
tags: ["state-transition"]
|
|
984
|
+
});
|
|
985
|
+
return session;
|
|
986
|
+
},
|
|
987
|
+
WaitingForHandshake: (from, conn, listeners, options, log) => {
|
|
988
|
+
const session = new SessionWaitingForHandshake({
|
|
989
|
+
conn,
|
|
990
|
+
listeners,
|
|
991
|
+
from,
|
|
992
|
+
options,
|
|
993
|
+
log
|
|
994
|
+
});
|
|
995
|
+
session.log?.info(`session created in WaitingForHandshake state`, {
|
|
996
|
+
...session.loggingMetadata,
|
|
997
|
+
tags: ["state-transition"]
|
|
998
|
+
});
|
|
999
|
+
return session;
|
|
1000
|
+
}
|
|
1001
|
+
},
|
|
1002
|
+
// All of the transitions 'move'/'consume' the old session and return a new one.
|
|
1003
|
+
// After a session is transitioned, any usage of the old session will throw.
|
|
1004
|
+
transition: {
|
|
1005
|
+
// happy path transitions
|
|
1006
|
+
NoConnectionToBackingOff: (oldSession, backoffMs, listeners) => {
|
|
1007
|
+
const carriedState = inheritSharedSessionWithGrace(oldSession);
|
|
1008
|
+
oldSession._handleStateExit();
|
|
1009
|
+
const session = new SessionBackingOff({
|
|
1010
|
+
backoffMs,
|
|
1011
|
+
listeners,
|
|
1012
|
+
...carriedState
|
|
1013
|
+
});
|
|
1014
|
+
session.log?.info(
|
|
1015
|
+
`session ${session.id} transition from NoConnection to BackingOff`,
|
|
1016
|
+
{
|
|
1017
|
+
...session.loggingMetadata,
|
|
1018
|
+
tags: ["state-transition"]
|
|
1019
|
+
}
|
|
1020
|
+
);
|
|
1021
|
+
return session;
|
|
1022
|
+
},
|
|
1023
|
+
BackingOffToConnecting: (oldSession, connPromise, listeners) => {
|
|
1024
|
+
const carriedState = inheritSharedSessionWithGrace(oldSession);
|
|
1025
|
+
oldSession._handleStateExit();
|
|
1026
|
+
const session = new SessionConnecting({
|
|
1027
|
+
connPromise,
|
|
1028
|
+
listeners,
|
|
1029
|
+
...carriedState
|
|
1030
|
+
});
|
|
1031
|
+
session.log?.info(
|
|
1032
|
+
`session ${session.id} transition from BackingOff to Connecting`,
|
|
1033
|
+
{
|
|
1034
|
+
...session.loggingMetadata,
|
|
1035
|
+
tags: ["state-transition"]
|
|
1036
|
+
}
|
|
1037
|
+
);
|
|
1038
|
+
return session;
|
|
1039
|
+
},
|
|
1040
|
+
ConnectingToHandshaking: (oldSession, conn, listeners) => {
|
|
1041
|
+
const carriedState = inheritSharedSessionWithGrace(oldSession);
|
|
1042
|
+
oldSession._handleStateExit();
|
|
1043
|
+
const session = new SessionHandshaking({
|
|
1044
|
+
conn,
|
|
1045
|
+
listeners,
|
|
1046
|
+
...carriedState
|
|
1047
|
+
});
|
|
1048
|
+
session.log?.info(
|
|
1049
|
+
`session ${session.id} transition from Connecting to Handshaking`,
|
|
1050
|
+
{
|
|
1051
|
+
...session.loggingMetadata,
|
|
1052
|
+
tags: ["state-transition"]
|
|
1053
|
+
}
|
|
1054
|
+
);
|
|
1055
|
+
return session;
|
|
1056
|
+
},
|
|
1057
|
+
HandshakingToConnected: (oldSession, listeners) => {
|
|
1058
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1059
|
+
const conn = oldSession.conn;
|
|
1060
|
+
oldSession._handleStateExit();
|
|
1061
|
+
const session = new SessionConnected({
|
|
1062
|
+
conn,
|
|
1063
|
+
listeners,
|
|
1064
|
+
...carriedState
|
|
1065
|
+
});
|
|
1066
|
+
session.log?.info(
|
|
1067
|
+
`session ${session.id} transition from Handshaking to Connected`,
|
|
1068
|
+
{
|
|
1069
|
+
...session.loggingMetadata,
|
|
1070
|
+
tags: ["state-transition"]
|
|
1071
|
+
}
|
|
1072
|
+
);
|
|
1073
|
+
return session;
|
|
1074
|
+
},
|
|
1075
|
+
WaitingForHandshakeToConnected: (pendingSession, oldSession, sessionId, to, propagationCtx, listeners, protocolVersion) => {
|
|
1076
|
+
const conn = pendingSession.conn;
|
|
1077
|
+
const { from, options } = pendingSession;
|
|
1078
|
+
const carriedState = oldSession ? (
|
|
1079
|
+
// old session exists, inherit state
|
|
1080
|
+
inheritSharedSession(oldSession)
|
|
1081
|
+
) : (
|
|
1082
|
+
// old session does not exist, create new state
|
|
1083
|
+
{
|
|
1084
|
+
id: sessionId,
|
|
1085
|
+
from,
|
|
1086
|
+
to,
|
|
1087
|
+
seq: 0,
|
|
1088
|
+
ack: 0,
|
|
1089
|
+
sendBuffer: [],
|
|
1090
|
+
telemetry: createSessionTelemetryInfo(
|
|
1091
|
+
sessionId,
|
|
1092
|
+
to,
|
|
1093
|
+
from,
|
|
1094
|
+
propagationCtx
|
|
1095
|
+
),
|
|
1096
|
+
options,
|
|
1097
|
+
log: pendingSession.log,
|
|
1098
|
+
protocolVersion
|
|
1099
|
+
}
|
|
1100
|
+
);
|
|
1101
|
+
pendingSession._handleStateExit();
|
|
1102
|
+
oldSession?._handleStateExit();
|
|
1103
|
+
const session = new SessionConnected({
|
|
1104
|
+
conn,
|
|
1105
|
+
listeners,
|
|
1106
|
+
...carriedState
|
|
1107
|
+
});
|
|
1108
|
+
session.log?.info(
|
|
1109
|
+
`session ${session.id} transition from WaitingForHandshake to Connected`,
|
|
1110
|
+
{
|
|
1111
|
+
...session.loggingMetadata,
|
|
1112
|
+
tags: ["state-transition"]
|
|
1113
|
+
}
|
|
1114
|
+
);
|
|
1115
|
+
return session;
|
|
1116
|
+
},
|
|
1117
|
+
// disconnect paths
|
|
1118
|
+
BackingOffToNoConnection: (oldSession, listeners) => {
|
|
1119
|
+
const carriedState = inheritSharedSessionWithGrace(oldSession);
|
|
1120
|
+
oldSession._handleStateExit();
|
|
1121
|
+
const session = new SessionNoConnection({
|
|
1122
|
+
listeners,
|
|
1123
|
+
...carriedState
|
|
1124
|
+
});
|
|
1125
|
+
session.log?.info(
|
|
1126
|
+
`session ${session.id} transition from BackingOff to NoConnection`,
|
|
1127
|
+
{
|
|
1128
|
+
...session.loggingMetadata,
|
|
1129
|
+
tags: ["state-transition"]
|
|
1130
|
+
}
|
|
1131
|
+
);
|
|
1132
|
+
return session;
|
|
1133
|
+
},
|
|
1134
|
+
ConnectingToNoConnection: (oldSession, listeners) => {
|
|
1135
|
+
const carriedState = inheritSharedSessionWithGrace(oldSession);
|
|
1136
|
+
oldSession.bestEffortClose();
|
|
1137
|
+
oldSession._handleStateExit();
|
|
1138
|
+
const session = new SessionNoConnection({
|
|
1139
|
+
listeners,
|
|
1140
|
+
...carriedState
|
|
1141
|
+
});
|
|
1142
|
+
session.log?.info(
|
|
1143
|
+
`session ${session.id} transition from Connecting to NoConnection`,
|
|
1144
|
+
{
|
|
1145
|
+
...session.loggingMetadata,
|
|
1146
|
+
tags: ["state-transition"]
|
|
1147
|
+
}
|
|
1148
|
+
);
|
|
1149
|
+
return session;
|
|
1150
|
+
},
|
|
1151
|
+
HandshakingToNoConnection: (oldSession, listeners) => {
|
|
1152
|
+
const carriedState = inheritSharedSessionWithGrace(oldSession);
|
|
1153
|
+
oldSession.conn.close();
|
|
1154
|
+
oldSession._handleStateExit();
|
|
1155
|
+
const session = new SessionNoConnection({
|
|
1156
|
+
listeners,
|
|
1157
|
+
...carriedState
|
|
1158
|
+
});
|
|
1159
|
+
session.log?.info(
|
|
1160
|
+
`session ${session.id} transition from Handshaking to NoConnection`,
|
|
1161
|
+
{
|
|
1162
|
+
...session.loggingMetadata,
|
|
1163
|
+
tags: ["state-transition"]
|
|
1164
|
+
}
|
|
1165
|
+
);
|
|
1166
|
+
return session;
|
|
1167
|
+
},
|
|
1168
|
+
ConnectedToNoConnection: (oldSession, listeners) => {
|
|
1169
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1170
|
+
const graceExpiryTime = Date.now() + oldSession.options.sessionDisconnectGraceMs;
|
|
1171
|
+
oldSession.conn.close();
|
|
1172
|
+
oldSession._handleStateExit();
|
|
1173
|
+
const session = new SessionNoConnection({
|
|
1174
|
+
listeners,
|
|
1175
|
+
graceExpiryTime,
|
|
1176
|
+
...carriedState
|
|
1177
|
+
});
|
|
1178
|
+
session.log?.info(
|
|
1179
|
+
`session ${session.id} transition from Connected to NoConnection`,
|
|
1180
|
+
{
|
|
1181
|
+
...session.loggingMetadata,
|
|
1182
|
+
tags: ["state-transition"]
|
|
1183
|
+
}
|
|
1184
|
+
);
|
|
1185
|
+
return session;
|
|
1186
|
+
}
|
|
713
1187
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
1188
|
+
};
|
|
1189
|
+
var transitions = SessionStateGraph.transition;
|
|
1190
|
+
var ClientSessionStateGraph = {
|
|
1191
|
+
entrypoint: SessionStateGraph.entrypoints.NoConnection,
|
|
1192
|
+
transition: {
|
|
1193
|
+
// happy paths
|
|
1194
|
+
// NoConnection -> BackingOff: attempt to connect
|
|
1195
|
+
NoConnectionToBackingOff: transitions.NoConnectionToBackingOff,
|
|
1196
|
+
// BackingOff -> Connecting: backoff period elapsed, start connection
|
|
1197
|
+
BackingOffToConnecting: transitions.BackingOffToConnecting,
|
|
1198
|
+
// Connecting -> Handshaking: connection established, start handshake
|
|
1199
|
+
ConnectingToHandshaking: transitions.ConnectingToHandshaking,
|
|
1200
|
+
// Handshaking -> Connected: handshake complete, session ready
|
|
1201
|
+
HandshakingToConnected: transitions.HandshakingToConnected,
|
|
1202
|
+
// disconnect paths
|
|
1203
|
+
// BackingOff -> NoConnection: unused
|
|
1204
|
+
BackingOffToNoConnection: transitions.BackingOffToNoConnection,
|
|
1205
|
+
// Connecting -> NoConnection: connection failed or connection timeout
|
|
1206
|
+
ConnectingToNoConnection: transitions.ConnectingToNoConnection,
|
|
1207
|
+
// Handshaking -> NoConnection: connection closed or handshake timeout
|
|
1208
|
+
HandshakingToNoConnection: transitions.HandshakingToNoConnection,
|
|
1209
|
+
// Connected -> NoConnection: connection closed
|
|
1210
|
+
ConnectedToNoConnection: transitions.ConnectedToNoConnection
|
|
1211
|
+
// destroy/close paths
|
|
1212
|
+
// NoConnection -> x: grace period elapsed
|
|
1213
|
+
// BackingOff -> x: grace period elapsed
|
|
1214
|
+
// Connecting -> x: grace period elapsed
|
|
1215
|
+
// Handshaking -> x: grace period elapsed or invalid handshake message or handshake rejection
|
|
1216
|
+
// Connected -> x: grace period elapsed or invalid message
|
|
726
1217
|
}
|
|
727
|
-
|
|
728
|
-
|
|
1218
|
+
};
|
|
1219
|
+
var ServerSessionStateGraph = {
|
|
1220
|
+
entrypoint: SessionStateGraph.entrypoints.WaitingForHandshake,
|
|
1221
|
+
transition: {
|
|
1222
|
+
// happy paths
|
|
1223
|
+
// WaitingForHandshake -> Connected: handshake complete, session ready
|
|
1224
|
+
WaitingForHandshakeToConnected: transitions.WaitingForHandshakeToConnected,
|
|
1225
|
+
// disconnect paths
|
|
1226
|
+
// Connected -> NoConnection: connection closed
|
|
1227
|
+
ConnectedToNoConnection: transitions.ConnectedToNoConnection
|
|
1228
|
+
// destroy/close paths
|
|
1229
|
+
// WaitingForHandshake -> x: handshake timeout elapsed or invalid handshake message or handshake rejection or connection closed
|
|
729
1230
|
}
|
|
730
1231
|
};
|
|
731
1232
|
|
|
732
1233
|
// transport/transport.ts
|
|
733
|
-
var import_api3 = require("@opentelemetry/api");
|
|
734
1234
|
var Transport = class {
|
|
735
1235
|
/**
|
|
736
1236
|
* The status of the transport.
|
|
737
1237
|
*/
|
|
738
1238
|
status;
|
|
739
|
-
/**
|
|
740
|
-
* The {@link Codec} used to encode and decode messages.
|
|
741
|
-
*/
|
|
742
|
-
codec;
|
|
743
1239
|
/**
|
|
744
1240
|
* The client ID of this transport.
|
|
745
1241
|
*/
|
|
746
1242
|
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
1243
|
/**
|
|
760
1244
|
* The event dispatcher for handling events of type EventTypes.
|
|
761
1245
|
*/
|
|
@@ -765,19 +1249,18 @@ var Transport = class {
|
|
|
765
1249
|
*/
|
|
766
1250
|
options;
|
|
767
1251
|
log;
|
|
1252
|
+
sessions;
|
|
768
1253
|
/**
|
|
769
1254
|
* Creates a new Transport instance.
|
|
770
|
-
* This should also set up {@link onConnect}, and {@link onDisconnect} listeners.
|
|
771
1255
|
* @param codec The codec used to encode and decode messages.
|
|
772
1256
|
* @param clientId The client ID of this transport.
|
|
773
1257
|
*/
|
|
774
1258
|
constructor(clientId, providedOptions) {
|
|
775
1259
|
this.options = { ...defaultTransportOptions, ...providedOptions };
|
|
776
1260
|
this.eventDispatcher = new EventDispatcher();
|
|
777
|
-
this.sessions = /* @__PURE__ */ new Map();
|
|
778
|
-
this.codec = this.options.codec;
|
|
779
1261
|
this.clientId = clientId;
|
|
780
1262
|
this.status = "open";
|
|
1263
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
781
1264
|
}
|
|
782
1265
|
bindLogger(fn, level) {
|
|
783
1266
|
if (typeof fn === "function") {
|
|
@@ -786,362 +1269,34 @@ var Transport = class {
|
|
|
786
1269
|
}
|
|
787
1270
|
this.log = createLogProxy(fn);
|
|
788
1271
|
}
|
|
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
1272
|
/**
|
|
1022
1273
|
* Called when a message is received by this transport.
|
|
1023
1274
|
* You generally shouldn't need to override this in downstream transport implementations.
|
|
1024
1275
|
* @param msg The received message.
|
|
1025
|
-
*/
|
|
1026
|
-
handleMsg(msg
|
|
1027
|
-
if (this.getStatus() !== "open")
|
|
1028
|
-
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
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
/**
|
|
1084
|
-
* Adds a listener to this transport.
|
|
1085
|
-
* @param the type of event to listen for
|
|
1086
|
-
* @param handler The message handler to add.
|
|
1087
|
-
*/
|
|
1088
|
-
addEventListener(type, handler) {
|
|
1089
|
-
this.eventDispatcher.addEventListener(type, handler);
|
|
1090
|
-
}
|
|
1091
|
-
/**
|
|
1092
|
-
* Removes a listener from this transport.
|
|
1093
|
-
* @param the type of event to un-listen on
|
|
1094
|
-
* @param handler The message handler to remove.
|
|
1095
|
-
*/
|
|
1096
|
-
removeEventListener(type, handler) {
|
|
1097
|
-
this.eventDispatcher.removeEventListener(type, handler);
|
|
1098
|
-
}
|
|
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
|
-
});
|
|
1276
|
+
*/
|
|
1277
|
+
handleMsg(msg) {
|
|
1278
|
+
if (this.getStatus() !== "open")
|
|
1279
|
+
return;
|
|
1280
|
+
this.eventDispatcher.dispatchEvent("message", msg);
|
|
1126
1281
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
});
|
|
1282
|
+
/**
|
|
1283
|
+
* Adds a listener to this transport.
|
|
1284
|
+
* @param the type of event to listen for
|
|
1285
|
+
* @param handler The message handler to add.
|
|
1286
|
+
*/
|
|
1287
|
+
addEventListener(type, handler) {
|
|
1288
|
+
this.eventDispatcher.addEventListener(type, handler);
|
|
1135
1289
|
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1290
|
+
/**
|
|
1291
|
+
* Removes a listener from this transport.
|
|
1292
|
+
* @param the type of event to un-listen on
|
|
1293
|
+
* @param handler The message handler to remove.
|
|
1294
|
+
*/
|
|
1295
|
+
removeEventListener(type, handler) {
|
|
1296
|
+
this.eventDispatcher.removeEventListener(type, handler);
|
|
1142
1297
|
}
|
|
1143
|
-
protocolError(
|
|
1144
|
-
this.eventDispatcher.dispatchEvent("protocolError",
|
|
1298
|
+
protocolError(message) {
|
|
1299
|
+
this.eventDispatcher.dispatchEvent("protocolError", message);
|
|
1145
1300
|
}
|
|
1146
1301
|
/**
|
|
1147
1302
|
* Default close implementation for transports. You should override this in the downstream
|
|
@@ -1151,7 +1306,7 @@ var Transport = class {
|
|
|
1151
1306
|
close() {
|
|
1152
1307
|
this.status = "closed";
|
|
1153
1308
|
for (const session of this.sessions.values()) {
|
|
1154
|
-
this.deleteSession(
|
|
1309
|
+
this.deleteSession(session);
|
|
1155
1310
|
}
|
|
1156
1311
|
this.eventDispatcher.dispatchEvent("transportStatus", {
|
|
1157
1312
|
status: this.status
|
|
@@ -1162,6 +1317,75 @@ var Transport = class {
|
|
|
1162
1317
|
getStatus() {
|
|
1163
1318
|
return this.status;
|
|
1164
1319
|
}
|
|
1320
|
+
updateSession(session) {
|
|
1321
|
+
const activeSession = this.sessions.get(session.to);
|
|
1322
|
+
if (activeSession && activeSession.id !== session.id) {
|
|
1323
|
+
const msg = `attempt to transition active session for ${session.to} but active session (${activeSession.id}) is different from handle (${session.id})`;
|
|
1324
|
+
throw new Error(msg);
|
|
1325
|
+
}
|
|
1326
|
+
this.sessions.set(session.to, session);
|
|
1327
|
+
if (!activeSession) {
|
|
1328
|
+
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
1329
|
+
status: "connect",
|
|
1330
|
+
session
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
this.eventDispatcher.dispatchEvent("sessionTransition", {
|
|
1334
|
+
state: session.state,
|
|
1335
|
+
session
|
|
1336
|
+
});
|
|
1337
|
+
return session;
|
|
1338
|
+
}
|
|
1339
|
+
// state transitions
|
|
1340
|
+
deleteSession(session, options) {
|
|
1341
|
+
if (session._isConsumed)
|
|
1342
|
+
return;
|
|
1343
|
+
const loggingMetadata = session.loggingMetadata;
|
|
1344
|
+
if (loggingMetadata.tags && options?.unhealthy) {
|
|
1345
|
+
loggingMetadata.tags.push("unhealthy-session");
|
|
1346
|
+
}
|
|
1347
|
+
session.log?.info(`closing session ${session.id}`, loggingMetadata);
|
|
1348
|
+
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
1349
|
+
status: "disconnect",
|
|
1350
|
+
session
|
|
1351
|
+
});
|
|
1352
|
+
const to = session.to;
|
|
1353
|
+
session.close();
|
|
1354
|
+
this.sessions.delete(to);
|
|
1355
|
+
}
|
|
1356
|
+
// common listeners
|
|
1357
|
+
onSessionGracePeriodElapsed(session) {
|
|
1358
|
+
this.log?.warn(
|
|
1359
|
+
`session to ${session.to} grace period elapsed, closing`,
|
|
1360
|
+
session.loggingMetadata
|
|
1361
|
+
);
|
|
1362
|
+
this.deleteSession(session);
|
|
1363
|
+
}
|
|
1364
|
+
onConnectingFailed(session) {
|
|
1365
|
+
const noConnectionSession = SessionStateGraph.transition.ConnectingToNoConnection(session, {
|
|
1366
|
+
onSessionGracePeriodElapsed: () => {
|
|
1367
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
return this.updateSession(noConnectionSession);
|
|
1371
|
+
}
|
|
1372
|
+
onConnClosed(session) {
|
|
1373
|
+
let noConnectionSession;
|
|
1374
|
+
if (session.state === "Handshaking" /* Handshaking */) {
|
|
1375
|
+
noConnectionSession = SessionStateGraph.transition.HandshakingToNoConnection(session, {
|
|
1376
|
+
onSessionGracePeriodElapsed: () => {
|
|
1377
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1378
|
+
}
|
|
1379
|
+
});
|
|
1380
|
+
} else {
|
|
1381
|
+
noConnectionSession = SessionStateGraph.transition.ConnectedToNoConnection(session, {
|
|
1382
|
+
onSessionGracePeriodElapsed: () => {
|
|
1383
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1384
|
+
}
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
return this.updateSession(noConnectionSession);
|
|
1388
|
+
}
|
|
1165
1389
|
};
|
|
1166
1390
|
|
|
1167
1391
|
// util/stringify.ts
|
|
@@ -1179,10 +1403,6 @@ var ClientTransport = class extends Transport {
|
|
|
1179
1403
|
* The options for this transport.
|
|
1180
1404
|
*/
|
|
1181
1405
|
options;
|
|
1182
|
-
/**
|
|
1183
|
-
* The map of reconnect promises for each client ID.
|
|
1184
|
-
*/
|
|
1185
|
-
inflightConnectionPromises;
|
|
1186
1406
|
retryBudget;
|
|
1187
1407
|
/**
|
|
1188
1408
|
* A flag indicating whether the transport should automatically reconnect
|
|
@@ -1195,358 +1415,325 @@ var ClientTransport = class extends Transport {
|
|
|
1195
1415
|
* Optional handshake options for this client.
|
|
1196
1416
|
*/
|
|
1197
1417
|
handshakeExtensions;
|
|
1418
|
+
sessions;
|
|
1198
1419
|
constructor(clientId, providedOptions) {
|
|
1199
1420
|
super(clientId, providedOptions);
|
|
1421
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
1200
1422
|
this.options = {
|
|
1201
1423
|
...defaultClientTransportOptions,
|
|
1202
1424
|
...providedOptions
|
|
1203
1425
|
};
|
|
1204
|
-
this.inflightConnectionPromises = /* @__PURE__ */ new Map();
|
|
1205
1426
|
this.retryBudget = new LeakyBucketRateLimit(this.options);
|
|
1206
1427
|
}
|
|
1207
1428
|
extendHandshake(options) {
|
|
1208
1429
|
this.handshakeExtensions = options;
|
|
1209
1430
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
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);
|
|
1431
|
+
tryReconnecting(to) {
|
|
1432
|
+
const oldSession = this.sessions.get(to);
|
|
1433
|
+
if (!this.options.enableTransparentSessionReconnects && oldSession) {
|
|
1434
|
+
this.deleteSession(oldSession);
|
|
1435
|
+
}
|
|
1436
|
+
if (this.reconnectOnConnectionDrop && this.getStatus() === "open") {
|
|
1437
|
+
this.connect(to);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
send(to, msg) {
|
|
1441
|
+
if (this.getStatus() === "closed") {
|
|
1442
|
+
const err = "transport is closed, cant send";
|
|
1443
|
+
this.log?.error(err, {
|
|
1444
|
+
clientId: this.clientId,
|
|
1445
|
+
transportMessage: msg,
|
|
1446
|
+
tags: ["invariant-violation"]
|
|
1244
1447
|
});
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1448
|
+
throw new Error(err);
|
|
1449
|
+
}
|
|
1450
|
+
let session = this.sessions.get(to);
|
|
1451
|
+
if (!session) {
|
|
1452
|
+
session = this.createUnconnectedSession(to);
|
|
1453
|
+
}
|
|
1454
|
+
return session.send(msg);
|
|
1455
|
+
}
|
|
1456
|
+
createUnconnectedSession(to) {
|
|
1457
|
+
const session = ClientSessionStateGraph.entrypoint(
|
|
1458
|
+
to,
|
|
1459
|
+
this.clientId,
|
|
1460
|
+
{
|
|
1461
|
+
onSessionGracePeriodElapsed: () => {
|
|
1462
|
+
this.onSessionGracePeriodElapsed(session);
|
|
1259
1463
|
}
|
|
1260
|
-
|
|
1261
|
-
this.
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1464
|
+
},
|
|
1465
|
+
this.options,
|
|
1466
|
+
currentProtocolVersion,
|
|
1467
|
+
this.log
|
|
1468
|
+
);
|
|
1469
|
+
this.updateSession(session);
|
|
1470
|
+
return session;
|
|
1471
|
+
}
|
|
1472
|
+
// listeners
|
|
1473
|
+
onConnectingFailed(session) {
|
|
1474
|
+
const noConnectionSession = super.onConnectingFailed(session);
|
|
1475
|
+
this.tryReconnecting(noConnectionSession.to);
|
|
1476
|
+
return noConnectionSession;
|
|
1477
|
+
}
|
|
1478
|
+
onConnClosed(session) {
|
|
1479
|
+
const noConnectionSession = super.onConnClosed(session);
|
|
1480
|
+
this.tryReconnecting(noConnectionSession.to);
|
|
1481
|
+
return noConnectionSession;
|
|
1482
|
+
}
|
|
1483
|
+
onConnectionEstablished(session, conn) {
|
|
1484
|
+
const handshakingSession = ClientSessionStateGraph.transition.ConnectingToHandshaking(
|
|
1485
|
+
session,
|
|
1486
|
+
conn,
|
|
1487
|
+
{
|
|
1488
|
+
onConnectionErrored: (err) => {
|
|
1489
|
+
const errStr = coerceErrorString(err);
|
|
1490
|
+
this.log?.error(
|
|
1491
|
+
`connection to ${handshakingSession.to} errored during handshake: ${errStr}`,
|
|
1492
|
+
handshakingSession.loggingMetadata
|
|
1493
|
+
);
|
|
1494
|
+
},
|
|
1495
|
+
onConnectionClosed: () => {
|
|
1496
|
+
this.log?.warn(
|
|
1497
|
+
`connection to ${handshakingSession.to} closed during handshake`,
|
|
1498
|
+
handshakingSession.loggingMetadata
|
|
1499
|
+
);
|
|
1500
|
+
this.onConnClosed(handshakingSession);
|
|
1501
|
+
},
|
|
1502
|
+
onHandshake: (msg) => {
|
|
1503
|
+
this.onHandshakeResponse(handshakingSession, msg);
|
|
1504
|
+
},
|
|
1505
|
+
onInvalidHandshake: (reason, code) => {
|
|
1506
|
+
this.log?.error(
|
|
1507
|
+
`invalid handshake: ${reason}`,
|
|
1508
|
+
handshakingSession.loggingMetadata
|
|
1509
|
+
);
|
|
1510
|
+
this.deleteSession(session, { unhealthy: true });
|
|
1511
|
+
this.protocolError({
|
|
1512
|
+
type: ProtocolError.HandshakeFailed,
|
|
1513
|
+
code,
|
|
1514
|
+
message: reason
|
|
1515
|
+
});
|
|
1516
|
+
},
|
|
1517
|
+
onHandshakeTimeout: () => {
|
|
1518
|
+
this.log?.error(
|
|
1519
|
+
`connection to ${handshakingSession.to} timed out during handshake`,
|
|
1520
|
+
handshakingSession.loggingMetadata
|
|
1521
|
+
);
|
|
1522
|
+
this.onConnClosed(handshakingSession);
|
|
1523
|
+
},
|
|
1524
|
+
onSessionGracePeriodElapsed: () => {
|
|
1525
|
+
this.onSessionGracePeriodElapsed(handshakingSession);
|
|
1278
1526
|
}
|
|
1279
|
-
|
|
1527
|
+
}
|
|
1528
|
+
);
|
|
1529
|
+
this.updateSession(handshakingSession);
|
|
1530
|
+
void this.sendHandshake(handshakingSession);
|
|
1531
|
+
return handshakingSession;
|
|
1532
|
+
}
|
|
1533
|
+
rejectHandshakeResponse(session, reason, metadata) {
|
|
1534
|
+
session.conn.telemetry?.span.setStatus({
|
|
1535
|
+
code: import_api3.SpanStatusCode.ERROR,
|
|
1536
|
+
message: reason
|
|
1280
1537
|
});
|
|
1538
|
+
this.log?.warn(reason, metadata);
|
|
1539
|
+
this.deleteSession(session, { unhealthy: true });
|
|
1281
1540
|
}
|
|
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,
|
|
1541
|
+
onHandshakeResponse(session, msg) {
|
|
1542
|
+
if (!import_value2.Value.Check(ControlMessageHandshakeResponseSchema, msg.payload)) {
|
|
1543
|
+
const reason = `received invalid handshake response`;
|
|
1544
|
+
this.rejectHandshakeResponse(session, reason, {
|
|
1545
|
+
...session.loggingMetadata,
|
|
1546
|
+
transportMessage: msg,
|
|
1305
1547
|
validationErrors: [
|
|
1306
|
-
...import_value2.Value.Errors(
|
|
1307
|
-
ControlMessageHandshakeResponseSchema,
|
|
1308
|
-
parsed.payload
|
|
1309
|
-
)
|
|
1548
|
+
...import_value2.Value.Errors(ControlMessageHandshakeResponseSchema, msg.payload)
|
|
1310
1549
|
]
|
|
1311
1550
|
});
|
|
1312
|
-
|
|
1313
|
-
ProtocolError.HandshakeFailed,
|
|
1314
|
-
"invalid handshake resp"
|
|
1315
|
-
);
|
|
1316
|
-
return false;
|
|
1551
|
+
return;
|
|
1317
1552
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1553
|
+
if (!msg.payload.status.ok) {
|
|
1554
|
+
const retriable = import_value2.Value.Check(
|
|
1555
|
+
HandshakeErrorRetriableResponseCodes,
|
|
1556
|
+
msg.payload.status.code
|
|
1557
|
+
);
|
|
1558
|
+
const reason = `handshake failed: ${msg.payload.status.reason}`;
|
|
1559
|
+
const to = session.to;
|
|
1560
|
+
this.rejectHandshakeResponse(session, reason, {
|
|
1561
|
+
...session.loggingMetadata,
|
|
1562
|
+
transportMessage: msg
|
|
1563
|
+
});
|
|
1564
|
+
if (retriable) {
|
|
1565
|
+
this.tryReconnecting(to);
|
|
1331
1566
|
} else {
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1567
|
+
this.protocolError({
|
|
1568
|
+
type: ProtocolError.HandshakeFailed,
|
|
1569
|
+
code: msg.payload.status.code,
|
|
1570
|
+
message: reason
|
|
1335
1571
|
});
|
|
1336
1572
|
}
|
|
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;
|
|
1573
|
+
return;
|
|
1351
1574
|
}
|
|
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
|
|
1575
|
+
if (msg.payload.status.sessionId !== session.id) {
|
|
1576
|
+
const reason = `session id mismatch: expected ${session.id}, got ${msg.payload.status.sessionId}`;
|
|
1577
|
+
this.rejectHandshakeResponse(session, reason, {
|
|
1578
|
+
...session.loggingMetadata,
|
|
1579
|
+
transportMessage: msg
|
|
1366
1580
|
});
|
|
1367
|
-
|
|
1368
|
-
return false;
|
|
1581
|
+
return;
|
|
1369
1582
|
}
|
|
1370
|
-
this.log?.
|
|
1371
|
-
...
|
|
1372
|
-
|
|
1373
|
-
connectedTo: parsed.from,
|
|
1374
|
-
transportMessage: parsed
|
|
1583
|
+
this.log?.info(`handshake from ${msg.from} ok`, {
|
|
1584
|
+
...session.loggingMetadata,
|
|
1585
|
+
transportMessage: msg
|
|
1375
1586
|
});
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1587
|
+
const connectedSession = ClientSessionStateGraph.transition.HandshakingToConnected(session, {
|
|
1588
|
+
onConnectionErrored: (err) => {
|
|
1589
|
+
const errStr = coerceErrorString(err);
|
|
1590
|
+
this.log?.warn(
|
|
1591
|
+
`connection to ${connectedSession.to} errored: ${errStr}`,
|
|
1592
|
+
connectedSession.loggingMetadata
|
|
1593
|
+
);
|
|
1594
|
+
},
|
|
1595
|
+
onConnectionClosed: () => {
|
|
1596
|
+
this.log?.info(
|
|
1597
|
+
`connection to ${connectedSession.to} closed`,
|
|
1598
|
+
connectedSession.loggingMetadata
|
|
1599
|
+
);
|
|
1600
|
+
this.onConnClosed(connectedSession);
|
|
1601
|
+
},
|
|
1602
|
+
onMessage: (msg2) => this.handleMsg(msg2),
|
|
1603
|
+
onInvalidMessage: (reason) => {
|
|
1604
|
+
this.deleteSession(connectedSession, { unhealthy: true });
|
|
1605
|
+
this.protocolError({
|
|
1606
|
+
type: ProtocolError.InvalidMessage,
|
|
1607
|
+
message: reason
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1380
1610
|
});
|
|
1381
|
-
this.
|
|
1382
|
-
this.retryBudget.startRestoringBudget(
|
|
1383
|
-
return session;
|
|
1611
|
+
this.updateSession(connectedSession);
|
|
1612
|
+
this.retryBudget.startRestoringBudget();
|
|
1384
1613
|
}
|
|
1385
1614
|
/**
|
|
1386
1615
|
* Manually attempts to connect to a client.
|
|
1387
1616
|
* @param to The client ID of the node to connect to.
|
|
1388
1617
|
*/
|
|
1389
|
-
|
|
1390
|
-
if (this.
|
|
1391
|
-
this.log?.info(`already connected to ${to}, skipping connect attempt`, {
|
|
1392
|
-
clientId: this.clientId,
|
|
1393
|
-
connectedTo: to
|
|
1394
|
-
});
|
|
1395
|
-
return;
|
|
1396
|
-
}
|
|
1397
|
-
const canProceedWithConnection = () => this.getStatus() === "open";
|
|
1398
|
-
if (!canProceedWithConnection()) {
|
|
1618
|
+
connect(to) {
|
|
1619
|
+
if (this.getStatus() !== "open") {
|
|
1399
1620
|
this.log?.info(
|
|
1400
|
-
`transport state is no longer open, cancelling attempt to connect to ${to}
|
|
1401
|
-
{ clientId: this.clientId, connectedTo: to }
|
|
1621
|
+
`transport state is no longer open, cancelling attempt to connect to ${to}`
|
|
1402
1622
|
);
|
|
1403
1623
|
return;
|
|
1404
1624
|
}
|
|
1405
|
-
let
|
|
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
|
-
}
|
|
1625
|
+
let session = this.sessions.get(to);
|
|
1626
|
+
session ??= this.createUnconnectedSession(to);
|
|
1627
|
+
if (session.state !== "NoConnection" /* NoConnection */) {
|
|
1628
|
+
this.log?.debug(
|
|
1629
|
+
`session to ${to} has state ${session.state}, skipping connect attempt`,
|
|
1630
|
+
session.loggingMetadata
|
|
1472
1631
|
);
|
|
1632
|
+
return;
|
|
1473
1633
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
this.
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
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
|
-
}
|
|
1634
|
+
if (!this.retryBudget.hasBudget()) {
|
|
1635
|
+
const budgetConsumed = this.retryBudget.getBudgetConsumed();
|
|
1636
|
+
const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
|
|
1637
|
+
this.log?.error(errMsg, session.loggingMetadata);
|
|
1638
|
+
this.protocolError({
|
|
1639
|
+
type: ProtocolError.RetriesExceeded,
|
|
1640
|
+
message: errMsg
|
|
1641
|
+
});
|
|
1642
|
+
return;
|
|
1491
1643
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
super.deleteSession({
|
|
1644
|
+
const backoffMs = this.retryBudget.getBackoffMs();
|
|
1645
|
+
this.log?.info(
|
|
1646
|
+
`attempting connection to ${to} (${backoffMs}ms backoff)`,
|
|
1647
|
+
session.loggingMetadata
|
|
1648
|
+
);
|
|
1649
|
+
this.retryBudget.consumeBudget();
|
|
1650
|
+
const backingOffSession = ClientSessionStateGraph.transition.NoConnectionToBackingOff(
|
|
1500
1651
|
session,
|
|
1501
|
-
|
|
1502
|
-
|
|
1652
|
+
backoffMs,
|
|
1653
|
+
{
|
|
1654
|
+
onBackoffFinished: () => {
|
|
1655
|
+
this.onBackoffFinished(backingOffSession);
|
|
1656
|
+
},
|
|
1657
|
+
onSessionGracePeriodElapsed: () => {
|
|
1658
|
+
this.onSessionGracePeriodElapsed(backingOffSession);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
);
|
|
1662
|
+
this.updateSession(backingOffSession);
|
|
1663
|
+
}
|
|
1664
|
+
onBackoffFinished(session) {
|
|
1665
|
+
const connPromise = tracing_default.startActiveSpan("connect", async (span) => {
|
|
1666
|
+
try {
|
|
1667
|
+
return await this.createNewOutgoingConnection(session.to);
|
|
1668
|
+
} catch (err) {
|
|
1669
|
+
const errStr = coerceErrorString(err);
|
|
1670
|
+
span.recordException(errStr);
|
|
1671
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR });
|
|
1672
|
+
throw err;
|
|
1673
|
+
} finally {
|
|
1674
|
+
span.end();
|
|
1675
|
+
}
|
|
1503
1676
|
});
|
|
1677
|
+
const connectingSession = ClientSessionStateGraph.transition.BackingOffToConnecting(
|
|
1678
|
+
session,
|
|
1679
|
+
connPromise,
|
|
1680
|
+
{
|
|
1681
|
+
onConnectionEstablished: (conn) => {
|
|
1682
|
+
this.log?.debug(
|
|
1683
|
+
`connection to ${connectingSession.to} established`,
|
|
1684
|
+
{
|
|
1685
|
+
...conn.loggingMetadata,
|
|
1686
|
+
...connectingSession.loggingMetadata
|
|
1687
|
+
}
|
|
1688
|
+
);
|
|
1689
|
+
this.onConnectionEstablished(connectingSession, conn);
|
|
1690
|
+
},
|
|
1691
|
+
onConnectionFailed: (error) => {
|
|
1692
|
+
const errStr = coerceErrorString(error);
|
|
1693
|
+
this.log?.error(
|
|
1694
|
+
`error connecting to ${connectingSession.to}: ${errStr}`,
|
|
1695
|
+
connectingSession.loggingMetadata
|
|
1696
|
+
);
|
|
1697
|
+
this.onConnectingFailed(connectingSession);
|
|
1698
|
+
},
|
|
1699
|
+
onConnectionTimeout: () => {
|
|
1700
|
+
this.log?.error(
|
|
1701
|
+
`connection to ${connectingSession.to} timed out`,
|
|
1702
|
+
connectingSession.loggingMetadata
|
|
1703
|
+
);
|
|
1704
|
+
this.onConnectingFailed(connectingSession);
|
|
1705
|
+
},
|
|
1706
|
+
onSessionGracePeriodElapsed: () => {
|
|
1707
|
+
this.onSessionGracePeriodElapsed(connectingSession);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
);
|
|
1711
|
+
this.updateSession(connectingSession);
|
|
1504
1712
|
}
|
|
1505
|
-
async sendHandshake(
|
|
1713
|
+
async sendHandshake(session) {
|
|
1506
1714
|
let metadata = void 0;
|
|
1507
1715
|
if (this.handshakeExtensions) {
|
|
1508
1716
|
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
1717
|
}
|
|
1530
|
-
|
|
1718
|
+
if (session._isConsumed) {
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1531
1721
|
const requestMsg = handshakeRequestMessage({
|
|
1532
1722
|
from: this.clientId,
|
|
1533
|
-
to,
|
|
1723
|
+
to: session.to,
|
|
1534
1724
|
sessionId: session.id,
|
|
1535
1725
|
expectedSessionState: {
|
|
1536
|
-
|
|
1537
|
-
|
|
1726
|
+
nextExpectedSeq: session.ack,
|
|
1727
|
+
nextSentSeq: session.nextSeq()
|
|
1538
1728
|
},
|
|
1539
1729
|
metadata,
|
|
1540
1730
|
tracing: getPropagationContext(session.telemetry.ctx)
|
|
1541
1731
|
});
|
|
1542
|
-
this.log?.debug(`sending handshake request to ${to}`, {
|
|
1543
|
-
...
|
|
1544
|
-
clientId: this.clientId,
|
|
1545
|
-
connectedTo: to,
|
|
1732
|
+
this.log?.debug(`sending handshake request to ${session.to}`, {
|
|
1733
|
+
...session.loggingMetadata,
|
|
1546
1734
|
transportMessage: requestMsg
|
|
1547
1735
|
});
|
|
1548
|
-
|
|
1549
|
-
return true;
|
|
1736
|
+
session.sendHandshake(requestMsg);
|
|
1550
1737
|
}
|
|
1551
1738
|
close() {
|
|
1552
1739
|
this.retryBudget.close();
|
|
@@ -1554,54 +1741,126 @@ var ClientTransport = class extends Transport {
|
|
|
1554
1741
|
}
|
|
1555
1742
|
};
|
|
1556
1743
|
|
|
1744
|
+
// transport/connection.ts
|
|
1745
|
+
var Connection = class {
|
|
1746
|
+
id;
|
|
1747
|
+
telemetry;
|
|
1748
|
+
constructor() {
|
|
1749
|
+
this.id = `conn-${generateId()}`;
|
|
1750
|
+
}
|
|
1751
|
+
get loggingMetadata() {
|
|
1752
|
+
const metadata = { connId: this.id };
|
|
1753
|
+
const spanContext = this.telemetry?.span.spanContext();
|
|
1754
|
+
if (this.telemetry?.span.isRecording() && spanContext) {
|
|
1755
|
+
metadata.telemetry = {
|
|
1756
|
+
traceId: spanContext.traceId,
|
|
1757
|
+
spanId: spanContext.spanId
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
return metadata;
|
|
1761
|
+
}
|
|
1762
|
+
// can't use event emitter because we need this to work in both node + browser
|
|
1763
|
+
_dataListeners = /* @__PURE__ */ new Set();
|
|
1764
|
+
_closeListeners = /* @__PURE__ */ new Set();
|
|
1765
|
+
_errorListeners = /* @__PURE__ */ new Set();
|
|
1766
|
+
get dataListeners() {
|
|
1767
|
+
return [...this._dataListeners];
|
|
1768
|
+
}
|
|
1769
|
+
get closeListeners() {
|
|
1770
|
+
return [...this._closeListeners];
|
|
1771
|
+
}
|
|
1772
|
+
get errorListeners() {
|
|
1773
|
+
return [...this._errorListeners];
|
|
1774
|
+
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Handle adding a callback for when a message is received.
|
|
1777
|
+
* @param msg The message that was received.
|
|
1778
|
+
*/
|
|
1779
|
+
addDataListener(cb) {
|
|
1780
|
+
this._dataListeners.add(cb);
|
|
1781
|
+
}
|
|
1782
|
+
removeDataListener(cb) {
|
|
1783
|
+
this._dataListeners.delete(cb);
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Handle adding a callback for when the connection is closed.
|
|
1787
|
+
* This should also be called if an error happens and after notifying all the error listeners.
|
|
1788
|
+
* @param cb The callback to call when the connection is closed.
|
|
1789
|
+
*/
|
|
1790
|
+
addCloseListener(cb) {
|
|
1791
|
+
this._closeListeners.add(cb);
|
|
1792
|
+
}
|
|
1793
|
+
removeCloseListener(cb) {
|
|
1794
|
+
this._closeListeners.delete(cb);
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* Handle adding a callback for when an error is received.
|
|
1798
|
+
* This should only be used for this.logging errors, all cleanup
|
|
1799
|
+
* should be delegated to addCloseListener.
|
|
1800
|
+
*
|
|
1801
|
+
* The implementer should take care such that the implemented
|
|
1802
|
+
* connection will call both the close and error callbacks
|
|
1803
|
+
* on an error.
|
|
1804
|
+
*
|
|
1805
|
+
* @param cb The callback to call when an error is received.
|
|
1806
|
+
*/
|
|
1807
|
+
addErrorListener(cb) {
|
|
1808
|
+
this._errorListeners.add(cb);
|
|
1809
|
+
}
|
|
1810
|
+
removeErrorListener(cb) {
|
|
1811
|
+
this._errorListeners.delete(cb);
|
|
1812
|
+
}
|
|
1813
|
+
};
|
|
1814
|
+
|
|
1557
1815
|
// transport/impls/ws/connection.ts
|
|
1816
|
+
var WS_HEALTHY_CLOSE_CODE = 1e3;
|
|
1558
1817
|
var WebSocketConnection = class extends Connection {
|
|
1559
|
-
errorCb = null;
|
|
1560
|
-
closeCb = null;
|
|
1561
1818
|
ws;
|
|
1562
|
-
|
|
1819
|
+
extras;
|
|
1820
|
+
get loggingMetadata() {
|
|
1821
|
+
const metadata = super.loggingMetadata;
|
|
1822
|
+
if (this.extras) {
|
|
1823
|
+
metadata.extras = this.extras;
|
|
1824
|
+
}
|
|
1825
|
+
return metadata;
|
|
1826
|
+
}
|
|
1827
|
+
constructor(ws, extras) {
|
|
1563
1828
|
super();
|
|
1564
1829
|
this.ws = ws;
|
|
1830
|
+
this.extras = extras;
|
|
1565
1831
|
this.ws.binaryType = "arraybuffer";
|
|
1566
1832
|
let didError = false;
|
|
1567
1833
|
this.ws.onerror = () => {
|
|
1568
1834
|
didError = true;
|
|
1569
1835
|
};
|
|
1570
1836
|
this.ws.onclose = ({ code, reason }) => {
|
|
1571
|
-
if (didError
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
`websocket closed with code and reason: ${code} - ${reason}`
|
|
1575
|
-
)
|
|
1837
|
+
if (didError) {
|
|
1838
|
+
const err = new Error(
|
|
1839
|
+
`websocket closed with code and reason: ${code} - ${reason}`
|
|
1576
1840
|
);
|
|
1841
|
+
for (const cb of this.errorListeners) {
|
|
1842
|
+
cb(err);
|
|
1843
|
+
}
|
|
1577
1844
|
}
|
|
1578
|
-
|
|
1579
|
-
|
|
1845
|
+
for (const cb of this.closeListeners) {
|
|
1846
|
+
cb();
|
|
1847
|
+
}
|
|
1848
|
+
};
|
|
1849
|
+
this.ws.onmessage = (msg) => {
|
|
1850
|
+
for (const cb of this.dataListeners) {
|
|
1851
|
+
cb(msg.data);
|
|
1580
1852
|
}
|
|
1581
1853
|
};
|
|
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
1854
|
}
|
|
1595
1855
|
send(payload) {
|
|
1596
|
-
if (this.ws.readyState
|
|
1597
|
-
this.ws.send(payload);
|
|
1598
|
-
return true;
|
|
1599
|
-
} else {
|
|
1856
|
+
if (this.ws.readyState !== this.ws.OPEN) {
|
|
1600
1857
|
return false;
|
|
1601
1858
|
}
|
|
1859
|
+
this.ws.send(payload);
|
|
1860
|
+
return true;
|
|
1602
1861
|
}
|
|
1603
1862
|
close() {
|
|
1604
|
-
this.ws.close();
|
|
1863
|
+
this.ws.close(WS_HEALTHY_CLOSE_CODE);
|
|
1605
1864
|
}
|
|
1606
1865
|
};
|
|
1607
1866
|
|
|
@@ -1648,11 +1907,11 @@ var WebSocketClientTransport = class extends ClientTransport {
|
|
|
1648
1907
|
};
|
|
1649
1908
|
});
|
|
1650
1909
|
const conn = new WebSocketConnection(ws);
|
|
1651
|
-
this.log?.info(`raw websocket to ${to} ok
|
|
1910
|
+
this.log?.info(`raw websocket to ${to} ok`, {
|
|
1652
1911
|
clientId: this.clientId,
|
|
1653
|
-
connectedTo: to
|
|
1912
|
+
connectedTo: to,
|
|
1913
|
+
...conn.loggingMetadata
|
|
1654
1914
|
});
|
|
1655
|
-
this.handleConnection(conn, to);
|
|
1656
1915
|
return conn;
|
|
1657
1916
|
}
|
|
1658
1917
|
};
|