@replit/river 0.207.2 → 0.207.3
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/dist/adapter-f2b6e211.d.ts +46 -0
- package/dist/{chunk-4HE7UYRL.js → chunk-B7REV3ZV.js} +6 -5
- package/dist/{chunk-4HE7UYRL.js.map → chunk-B7REV3ZV.js.map} +1 -1
- package/dist/{chunk-24EWYOGK.js → chunk-BO7MFCO6.js} +1136 -143
- package/dist/chunk-BO7MFCO6.js.map +1 -0
- package/dist/{chunk-46IVOKJU.js → chunk-QGPYCXV4.js} +2 -2
- package/dist/{chunk-46IVOKJU.js.map → chunk-QGPYCXV4.js.map} +1 -1
- package/dist/codec/index.cjs +157 -23
- package/dist/codec/index.cjs.map +1 -1
- package/dist/codec/index.d.cts +5 -1
- package/dist/codec/index.d.ts +5 -1
- package/dist/codec/index.js +6 -20
- package/dist/codec/index.js.map +1 -1
- package/dist/{connection-a18e31d5.d.ts → connection-06d72f2e.d.ts} +3 -2
- package/dist/index-02554794.d.ts +37 -0
- package/dist/logging/index.d.cts +2 -1
- package/dist/logging/index.d.ts +2 -1
- package/dist/{message-ffacb98a.d.ts → message-01c3e85a.d.ts} +1 -35
- package/dist/router/index.cjs +1 -1
- package/dist/router/index.cjs.map +1 -1
- package/dist/router/index.d.cts +6 -5
- package/dist/router/index.d.ts +6 -5
- package/dist/router/index.js +1 -1
- package/dist/{services-43528f4b.d.ts → services-87887bc5.d.ts} +16 -12
- package/dist/testUtil/index.cjs +809 -657
- package/dist/testUtil/index.cjs.map +1 -1
- package/dist/testUtil/index.d.cts +4 -3
- package/dist/testUtil/index.d.ts +4 -3
- package/dist/testUtil/index.js +18 -13
- package/dist/testUtil/index.js.map +1 -1
- package/dist/transport/impls/ws/client.cjs +293 -204
- package/dist/transport/impls/ws/client.cjs.map +1 -1
- package/dist/transport/impls/ws/client.d.cts +5 -4
- package/dist/transport/impls/ws/client.d.ts +5 -4
- package/dist/transport/impls/ws/client.js +5 -7
- package/dist/transport/impls/ws/client.js.map +1 -1
- package/dist/transport/impls/ws/server.cjs +230 -128
- package/dist/transport/impls/ws/server.cjs.map +1 -1
- package/dist/transport/impls/ws/server.d.cts +5 -4
- package/dist/transport/impls/ws/server.d.ts +5 -4
- package/dist/transport/impls/ws/server.js +5 -7
- package/dist/transport/impls/ws/server.js.map +1 -1
- package/dist/transport/index.cjs +408 -270
- package/dist/transport/index.cjs.map +1 -1
- package/dist/transport/index.d.cts +7 -6
- package/dist/transport/index.d.ts +7 -6
- package/dist/transport/index.js +4 -9
- package/package.json +1 -1
- package/dist/chunk-24EWYOGK.js.map +0 -1
- package/dist/chunk-A7RGOVRV.js +0 -438
- package/dist/chunk-A7RGOVRV.js.map +0 -1
- package/dist/chunk-AJGIY2UB.js +0 -56
- package/dist/chunk-AJGIY2UB.js.map +0 -1
- package/dist/chunk-XV4RQ62N.js +0 -377
- package/dist/chunk-XV4RQ62N.js.map +0 -1
- package/dist/types-3e5768ec.d.ts +0 -20
|
@@ -1,18 +1,114 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ControlMessageHandshakeRequestSchema,
|
|
3
|
+
ControlMessageHandshakeResponseSchema,
|
|
4
|
+
HandshakeErrorCustomHandlerFatalResponseCodes,
|
|
5
|
+
HandshakeErrorRetriableResponseCodes,
|
|
2
6
|
OpaqueTransportMessageSchema,
|
|
7
|
+
acceptedProtocolVersions,
|
|
8
|
+
coerceErrorString,
|
|
3
9
|
createConnectionTelemetryInfo,
|
|
4
10
|
createSessionTelemetryInfo,
|
|
11
|
+
currentProtocolVersion,
|
|
5
12
|
generateId,
|
|
13
|
+
getPropagationContext,
|
|
6
14
|
getTracer,
|
|
15
|
+
handshakeRequestMessage,
|
|
16
|
+
handshakeResponseMessage,
|
|
17
|
+
isAcceptedProtocolVersion,
|
|
7
18
|
isAck
|
|
8
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-QGPYCXV4.js";
|
|
9
20
|
import {
|
|
10
21
|
BaseLogger,
|
|
11
22
|
createLogProxy
|
|
12
23
|
} from "./chunk-CC7RN7GI.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
24
|
+
|
|
25
|
+
// transport/events.ts
|
|
26
|
+
var ProtocolError = {
|
|
27
|
+
RetriesExceeded: "conn_retry_exceeded",
|
|
28
|
+
HandshakeFailed: "handshake_failed",
|
|
29
|
+
MessageOrderingViolated: "message_ordering_violated",
|
|
30
|
+
InvalidMessage: "invalid_message",
|
|
31
|
+
MessageSendFailure: "message_send_failure"
|
|
32
|
+
};
|
|
33
|
+
var EventDispatcher = class {
|
|
34
|
+
eventListeners = {};
|
|
35
|
+
removeAllListeners() {
|
|
36
|
+
this.eventListeners = {};
|
|
37
|
+
}
|
|
38
|
+
numberOfListeners(eventType) {
|
|
39
|
+
return this.eventListeners[eventType]?.size ?? 0;
|
|
40
|
+
}
|
|
41
|
+
addEventListener(eventType, handler) {
|
|
42
|
+
if (!this.eventListeners[eventType]) {
|
|
43
|
+
this.eventListeners[eventType] = /* @__PURE__ */ new Set();
|
|
44
|
+
}
|
|
45
|
+
this.eventListeners[eventType]?.add(handler);
|
|
46
|
+
}
|
|
47
|
+
removeEventListener(eventType, handler) {
|
|
48
|
+
const handlers = this.eventListeners[eventType];
|
|
49
|
+
if (handlers) {
|
|
50
|
+
this.eventListeners[eventType]?.delete(handler);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
dispatchEvent(eventType, event) {
|
|
54
|
+
const handlers = this.eventListeners[eventType];
|
|
55
|
+
if (handlers) {
|
|
56
|
+
const copy = [...handlers];
|
|
57
|
+
for (const handler of copy) {
|
|
58
|
+
handler(event);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// codec/json.ts
|
|
65
|
+
var encoder = new TextEncoder();
|
|
66
|
+
var decoder = new TextDecoder();
|
|
67
|
+
function uint8ArrayToBase64(uint8Array) {
|
|
68
|
+
let binary = "";
|
|
69
|
+
uint8Array.forEach((byte) => {
|
|
70
|
+
binary += String.fromCharCode(byte);
|
|
71
|
+
});
|
|
72
|
+
return btoa(binary);
|
|
73
|
+
}
|
|
74
|
+
function base64ToUint8Array(base64) {
|
|
75
|
+
const binaryString = atob(base64);
|
|
76
|
+
const uint8Array = new Uint8Array(binaryString.length);
|
|
77
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
78
|
+
uint8Array[i] = binaryString.charCodeAt(i);
|
|
79
|
+
}
|
|
80
|
+
return uint8Array;
|
|
81
|
+
}
|
|
82
|
+
var NaiveJsonCodec = {
|
|
83
|
+
toBuffer: (obj) => {
|
|
84
|
+
return encoder.encode(
|
|
85
|
+
JSON.stringify(obj, function replacer(key) {
|
|
86
|
+
const val = this[key];
|
|
87
|
+
if (val instanceof Uint8Array) {
|
|
88
|
+
return { $t: uint8ArrayToBase64(val) };
|
|
89
|
+
} else {
|
|
90
|
+
return val;
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
},
|
|
95
|
+
fromBuffer: (buff) => {
|
|
96
|
+
const parsed = JSON.parse(
|
|
97
|
+
decoder.decode(buff),
|
|
98
|
+
function reviver(_key, val) {
|
|
99
|
+
if (val?.$t) {
|
|
100
|
+
return base64ToUint8Array(val.$t);
|
|
101
|
+
} else {
|
|
102
|
+
return val;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
107
|
+
throw new Error("unpacked msg is not an object");
|
|
108
|
+
}
|
|
109
|
+
return parsed;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
16
112
|
|
|
17
113
|
// transport/options.ts
|
|
18
114
|
var defaultTransportOptions = {
|
|
@@ -40,7 +136,6 @@ var defaultServerTransportOptions = {
|
|
|
40
136
|
};
|
|
41
137
|
|
|
42
138
|
// transport/sessionStateMachine/common.ts
|
|
43
|
-
import { Value } from "@sinclair/typebox/value";
|
|
44
139
|
var SessionState = /* @__PURE__ */ ((SessionState2) => {
|
|
45
140
|
SessionState2["NoConnection"] = "NoConnection";
|
|
46
141
|
SessionState2["BackingOff"] = "BackingOff";
|
|
@@ -109,34 +204,16 @@ var StateMachineState = class {
|
|
|
109
204
|
var CommonSession = class extends StateMachineState {
|
|
110
205
|
from;
|
|
111
206
|
options;
|
|
207
|
+
codec;
|
|
112
208
|
tracer;
|
|
113
209
|
log;
|
|
114
|
-
constructor({ from, options, log, tracer }) {
|
|
210
|
+
constructor({ from, options, log, tracer, codec }) {
|
|
115
211
|
super();
|
|
116
212
|
this.from = from;
|
|
117
213
|
this.options = options;
|
|
118
214
|
this.log = log;
|
|
119
215
|
this.tracer = tracer;
|
|
120
|
-
|
|
121
|
-
parseMsg(msg) {
|
|
122
|
-
const parsedMsg = this.options.codec.fromBuffer(msg);
|
|
123
|
-
if (parsedMsg === null) {
|
|
124
|
-
this.log?.error(
|
|
125
|
-
`received malformed msg: ${Buffer.from(msg).toString("base64")}`,
|
|
126
|
-
this.loggingMetadata
|
|
127
|
-
);
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
if (!Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
|
|
131
|
-
this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
|
|
132
|
-
...this.loggingMetadata,
|
|
133
|
-
validationErrors: [
|
|
134
|
-
...Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
|
|
135
|
-
]
|
|
136
|
-
});
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
return parsedMsg;
|
|
216
|
+
this.codec = codec;
|
|
140
217
|
}
|
|
141
218
|
};
|
|
142
219
|
var IdentifiedSession = class extends CommonSession {
|
|
@@ -196,9 +273,6 @@ var IdentifiedSession = class extends CommonSession {
|
|
|
196
273
|
return metadata;
|
|
197
274
|
}
|
|
198
275
|
constructMsg(partialMsg) {
|
|
199
|
-
if (this._isConsumed) {
|
|
200
|
-
throw new Error(ERR_CONSUMED);
|
|
201
|
-
}
|
|
202
276
|
const msg = {
|
|
203
277
|
...partialMsg,
|
|
204
278
|
id: generateId(),
|
|
@@ -216,7 +290,10 @@ var IdentifiedSession = class extends CommonSession {
|
|
|
216
290
|
send(msg) {
|
|
217
291
|
const constructedMsg = this.constructMsg(msg);
|
|
218
292
|
this.sendBuffer.push(constructedMsg);
|
|
219
|
-
return
|
|
293
|
+
return {
|
|
294
|
+
ok: true,
|
|
295
|
+
value: constructedMsg.id
|
|
296
|
+
};
|
|
220
297
|
}
|
|
221
298
|
_handleStateExit() {
|
|
222
299
|
}
|
|
@@ -248,6 +325,23 @@ var IdentifiedSessionWithGracePeriod = class extends IdentifiedSession {
|
|
|
248
325
|
super._handleClose();
|
|
249
326
|
}
|
|
250
327
|
};
|
|
328
|
+
function sendMessage(conn, codec, msg) {
|
|
329
|
+
const buff = codec.toBuffer(msg);
|
|
330
|
+
if (!buff.ok) {
|
|
331
|
+
return buff;
|
|
332
|
+
}
|
|
333
|
+
const sent = conn.send(buff.value);
|
|
334
|
+
if (!sent) {
|
|
335
|
+
return {
|
|
336
|
+
ok: false,
|
|
337
|
+
reason: "failed to send message"
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
ok: true,
|
|
342
|
+
value: msg.id
|
|
343
|
+
};
|
|
344
|
+
}
|
|
251
345
|
|
|
252
346
|
// transport/sessionStateMachine/SessionConnecting.ts
|
|
253
347
|
var SessionConnecting = class extends IdentifiedSessionWithGracePeriod {
|
|
@@ -300,8 +394,8 @@ var SessionConnecting = class extends IdentifiedSessionWithGracePeriod {
|
|
|
300
394
|
}
|
|
301
395
|
}
|
|
302
396
|
_handleClose() {
|
|
303
|
-
this.bestEffortClose();
|
|
304
397
|
super._handleClose();
|
|
398
|
+
this.bestEffortClose();
|
|
305
399
|
}
|
|
306
400
|
};
|
|
307
401
|
|
|
@@ -341,18 +435,18 @@ var SessionWaitingForHandshake = class extends CommonSession {
|
|
|
341
435
|
};
|
|
342
436
|
}
|
|
343
437
|
onHandshakeData = (msg) => {
|
|
344
|
-
const
|
|
345
|
-
if (
|
|
438
|
+
const parsedMsgRes = this.codec.fromBuffer(msg);
|
|
439
|
+
if (!parsedMsgRes.ok) {
|
|
346
440
|
this.listeners.onInvalidHandshake(
|
|
347
|
-
|
|
441
|
+
`could not parse handshake message: ${parsedMsgRes.reason}`,
|
|
348
442
|
"MALFORMED_HANDSHAKE"
|
|
349
443
|
);
|
|
350
444
|
return;
|
|
351
445
|
}
|
|
352
|
-
this.listeners.onHandshake(
|
|
446
|
+
this.listeners.onHandshake(parsedMsgRes.value);
|
|
353
447
|
};
|
|
354
448
|
sendHandshake(msg) {
|
|
355
|
-
return this.conn
|
|
449
|
+
return sendMessage(this.conn, this.codec, msg);
|
|
356
450
|
}
|
|
357
451
|
_handleStateExit() {
|
|
358
452
|
this.conn.removeDataListener(this.onHandshakeData);
|
|
@@ -390,18 +484,18 @@ var SessionHandshaking = class extends IdentifiedSessionWithGracePeriod {
|
|
|
390
484
|
};
|
|
391
485
|
}
|
|
392
486
|
onHandshakeData = (msg) => {
|
|
393
|
-
const
|
|
394
|
-
if (
|
|
487
|
+
const parsedMsgRes = this.codec.fromBuffer(msg);
|
|
488
|
+
if (!parsedMsgRes.ok) {
|
|
395
489
|
this.listeners.onInvalidHandshake(
|
|
396
|
-
|
|
490
|
+
`could not parse handshake message: ${parsedMsgRes.reason}`,
|
|
397
491
|
"MALFORMED_HANDSHAKE"
|
|
398
492
|
);
|
|
399
493
|
return;
|
|
400
494
|
}
|
|
401
|
-
this.listeners.onHandshake(
|
|
495
|
+
this.listeners.onHandshake(parsedMsgRes.value);
|
|
402
496
|
};
|
|
403
497
|
sendHandshake(msg) {
|
|
404
|
-
return this.conn
|
|
498
|
+
return sendMessage(this.conn, this.codec, msg);
|
|
405
499
|
}
|
|
406
500
|
_handleStateExit() {
|
|
407
501
|
super._handleStateExit();
|
|
@@ -426,25 +520,15 @@ var SessionConnected = class extends IdentifiedSession {
|
|
|
426
520
|
conn;
|
|
427
521
|
listeners;
|
|
428
522
|
heartbeatHandle;
|
|
429
|
-
|
|
430
|
-
isActivelyHeartbeating;
|
|
431
|
-
lastConstructedMsgs = [];
|
|
432
|
-
pushLastConstructedMsgs = (msg) => {
|
|
433
|
-
const trackedMsg = {
|
|
434
|
-
id: msg.id,
|
|
435
|
-
seq: msg.seq,
|
|
436
|
-
streamId: msg.streamId,
|
|
437
|
-
stack: new Error().stack
|
|
438
|
-
};
|
|
439
|
-
this.lastConstructedMsgs.push(trackedMsg);
|
|
440
|
-
if (this.lastConstructedMsgs.length > 10) {
|
|
441
|
-
this.lastConstructedMsgs.shift();
|
|
442
|
-
}
|
|
443
|
-
};
|
|
523
|
+
heartbeatMissTimeout;
|
|
524
|
+
isActivelyHeartbeating = false;
|
|
444
525
|
updateBookkeeping(ack, seq) {
|
|
445
526
|
this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
|
|
446
527
|
this.ack = seq + 1;
|
|
447
|
-
this.
|
|
528
|
+
if (this.heartbeatMissTimeout) {
|
|
529
|
+
clearTimeout(this.heartbeatMissTimeout);
|
|
530
|
+
}
|
|
531
|
+
this.startMissingHeartbeatTimeout();
|
|
448
532
|
}
|
|
449
533
|
assertSendOrdering(constructedMsg) {
|
|
450
534
|
if (constructedMsg.seq > this.seqSent + 1) {
|
|
@@ -452,22 +536,22 @@ var SessionConnected = class extends IdentifiedSession {
|
|
|
452
536
|
this.log?.error(msg, {
|
|
453
537
|
...this.loggingMetadata,
|
|
454
538
|
transportMessage: constructedMsg,
|
|
455
|
-
tags: ["invariant-violation"]
|
|
456
|
-
extras: {
|
|
457
|
-
lastConstructedMsgs: this.lastConstructedMsgs
|
|
458
|
-
}
|
|
539
|
+
tags: ["invariant-violation"]
|
|
459
540
|
});
|
|
460
541
|
throw new Error(msg);
|
|
461
542
|
}
|
|
462
543
|
}
|
|
463
544
|
send(msg) {
|
|
464
545
|
const constructedMsg = this.constructMsg(msg);
|
|
465
|
-
this.pushLastConstructedMsgs(constructedMsg);
|
|
466
546
|
this.assertSendOrdering(constructedMsg);
|
|
467
547
|
this.sendBuffer.push(constructedMsg);
|
|
468
|
-
this.conn
|
|
548
|
+
const res = sendMessage(this.conn, this.codec, constructedMsg);
|
|
549
|
+
if (!res.ok) {
|
|
550
|
+
this.listeners.onMessageSendFailure(constructedMsg, res.reason);
|
|
551
|
+
return res;
|
|
552
|
+
}
|
|
469
553
|
this.seqSent = constructedMsg.seq;
|
|
470
|
-
return
|
|
554
|
+
return res;
|
|
471
555
|
}
|
|
472
556
|
constructor(props) {
|
|
473
557
|
super(props);
|
|
@@ -476,6 +560,8 @@ var SessionConnected = class extends IdentifiedSession {
|
|
|
476
560
|
this.conn.addDataListener(this.onMessageData);
|
|
477
561
|
this.conn.addCloseListener(this.listeners.onConnectionClosed);
|
|
478
562
|
this.conn.addErrorListener(this.listeners.onConnectionErrored);
|
|
563
|
+
}
|
|
564
|
+
sendBufferedMessages() {
|
|
479
565
|
if (this.sendBuffer.length > 0) {
|
|
480
566
|
this.log?.info(
|
|
481
567
|
`sending ${this.sendBuffer.length} buffered messages, starting at seq ${this.nextSeq()}`,
|
|
@@ -483,30 +569,15 @@ var SessionConnected = class extends IdentifiedSession {
|
|
|
483
569
|
);
|
|
484
570
|
for (const msg of this.sendBuffer) {
|
|
485
571
|
this.assertSendOrdering(msg);
|
|
486
|
-
this.conn
|
|
572
|
+
const res = sendMessage(this.conn, this.codec, msg);
|
|
573
|
+
if (!res.ok) {
|
|
574
|
+
this.listeners.onMessageSendFailure(msg, res.reason);
|
|
575
|
+
return res;
|
|
576
|
+
}
|
|
487
577
|
this.seqSent = msg.seq;
|
|
488
578
|
}
|
|
489
579
|
}
|
|
490
|
-
|
|
491
|
-
this.heartbeatHandle = setInterval(() => {
|
|
492
|
-
const misses = this.heartbeatMisses;
|
|
493
|
-
const missDuration = misses * this.options.heartbeatIntervalMs;
|
|
494
|
-
if (misses >= this.options.heartbeatsUntilDead) {
|
|
495
|
-
this.log?.info(
|
|
496
|
-
`closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
|
|
497
|
-
this.loggingMetadata
|
|
498
|
-
);
|
|
499
|
-
this.telemetry.span.addEvent("closing connection due to inactivity");
|
|
500
|
-
this.conn.close();
|
|
501
|
-
clearInterval(this.heartbeatHandle);
|
|
502
|
-
this.heartbeatHandle = void 0;
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
if (this.isActivelyHeartbeating) {
|
|
506
|
-
this.sendHeartbeat();
|
|
507
|
-
}
|
|
508
|
-
this.heartbeatMisses++;
|
|
509
|
-
}, this.options.heartbeatIntervalMs);
|
|
580
|
+
return { ok: true, value: void 0 };
|
|
510
581
|
}
|
|
511
582
|
get loggingMetadata() {
|
|
512
583
|
return {
|
|
@@ -514,25 +585,46 @@ var SessionConnected = class extends IdentifiedSession {
|
|
|
514
585
|
...this.conn.loggingMetadata
|
|
515
586
|
};
|
|
516
587
|
}
|
|
588
|
+
startMissingHeartbeatTimeout() {
|
|
589
|
+
const maxMisses = this.options.heartbeatsUntilDead;
|
|
590
|
+
const missDuration = maxMisses * this.options.heartbeatIntervalMs;
|
|
591
|
+
this.heartbeatMissTimeout = setTimeout(() => {
|
|
592
|
+
this.log?.info(
|
|
593
|
+
`closing connection to ${this.to} due to inactivity (missed ${maxMisses} heartbeats which is ${missDuration}ms)`,
|
|
594
|
+
this.loggingMetadata
|
|
595
|
+
);
|
|
596
|
+
this.telemetry.span.addEvent(
|
|
597
|
+
"closing connection due to missing heartbeat"
|
|
598
|
+
);
|
|
599
|
+
this.conn.close();
|
|
600
|
+
}, missDuration);
|
|
601
|
+
}
|
|
517
602
|
startActiveHeartbeat() {
|
|
518
603
|
this.isActivelyHeartbeating = true;
|
|
604
|
+
this.heartbeatHandle = setInterval(() => {
|
|
605
|
+
this.sendHeartbeat();
|
|
606
|
+
}, this.options.heartbeatIntervalMs);
|
|
519
607
|
}
|
|
520
608
|
sendHeartbeat() {
|
|
521
609
|
this.log?.debug("sending heartbeat", this.loggingMetadata);
|
|
522
|
-
|
|
610
|
+
const heartbeat = {
|
|
523
611
|
streamId: "heartbeat",
|
|
524
612
|
controlFlags: 1 /* AckBit */,
|
|
525
613
|
payload: {
|
|
526
614
|
type: "ACK"
|
|
527
615
|
}
|
|
528
|
-
}
|
|
616
|
+
};
|
|
617
|
+
this.send(heartbeat);
|
|
529
618
|
}
|
|
530
619
|
onMessageData = (msg) => {
|
|
531
|
-
const
|
|
532
|
-
if (
|
|
533
|
-
this.listeners.onInvalidMessage(
|
|
620
|
+
const parsedMsgRes = this.codec.fromBuffer(msg);
|
|
621
|
+
if (!parsedMsgRes.ok) {
|
|
622
|
+
this.listeners.onInvalidMessage(
|
|
623
|
+
`could not parse message: ${parsedMsgRes.reason}`
|
|
624
|
+
);
|
|
534
625
|
return;
|
|
535
626
|
}
|
|
627
|
+
const parsedMsg = parsedMsgRes.value;
|
|
536
628
|
if (parsedMsg.seq !== this.ack) {
|
|
537
629
|
if (parsedMsg.seq < this.ack) {
|
|
538
630
|
this.log?.debug(
|
|
@@ -571,9 +663,7 @@ var SessionConnected = class extends IdentifiedSession {
|
|
|
571
663
|
transportMessage: parsedMsg
|
|
572
664
|
});
|
|
573
665
|
if (!this.isActivelyHeartbeating) {
|
|
574
|
-
|
|
575
|
-
this.sendHeartbeat();
|
|
576
|
-
});
|
|
666
|
+
this.sendHeartbeat();
|
|
577
667
|
}
|
|
578
668
|
};
|
|
579
669
|
_handleStateExit() {
|
|
@@ -585,6 +675,10 @@ var SessionConnected = class extends IdentifiedSession {
|
|
|
585
675
|
clearInterval(this.heartbeatHandle);
|
|
586
676
|
this.heartbeatHandle = void 0;
|
|
587
677
|
}
|
|
678
|
+
if (this.heartbeatMissTimeout) {
|
|
679
|
+
clearTimeout(this.heartbeatMissTimeout);
|
|
680
|
+
this.heartbeatMissTimeout = void 0;
|
|
681
|
+
}
|
|
588
682
|
}
|
|
589
683
|
_handleClose() {
|
|
590
684
|
super._handleClose();
|
|
@@ -616,6 +710,62 @@ var SessionBackingOff = class extends IdentifiedSessionWithGracePeriod {
|
|
|
616
710
|
}
|
|
617
711
|
};
|
|
618
712
|
|
|
713
|
+
// codec/binary.ts
|
|
714
|
+
import { decode, encode } from "@msgpack/msgpack";
|
|
715
|
+
var BinaryCodec = {
|
|
716
|
+
toBuffer(obj) {
|
|
717
|
+
return encode(obj, { ignoreUndefined: true });
|
|
718
|
+
},
|
|
719
|
+
fromBuffer: (buff) => {
|
|
720
|
+
const res = decode(buff);
|
|
721
|
+
if (typeof res !== "object" || res === null) {
|
|
722
|
+
throw new Error("unpacked msg is not an object");
|
|
723
|
+
}
|
|
724
|
+
return res;
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
// codec/adapter.ts
|
|
729
|
+
import { Value } from "@sinclair/typebox/value";
|
|
730
|
+
var CodecMessageAdapter = class {
|
|
731
|
+
constructor(codec) {
|
|
732
|
+
this.codec = codec;
|
|
733
|
+
}
|
|
734
|
+
toBuffer(msg) {
|
|
735
|
+
try {
|
|
736
|
+
return {
|
|
737
|
+
ok: true,
|
|
738
|
+
value: this.codec.toBuffer(msg)
|
|
739
|
+
};
|
|
740
|
+
} catch (e) {
|
|
741
|
+
return {
|
|
742
|
+
ok: false,
|
|
743
|
+
reason: coerceErrorString(e)
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
fromBuffer(buf) {
|
|
748
|
+
try {
|
|
749
|
+
const parsedMsg = this.codec.fromBuffer(buf);
|
|
750
|
+
if (!Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
|
|
751
|
+
return {
|
|
752
|
+
ok: false,
|
|
753
|
+
reason: "transport message schema mismatch"
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
return {
|
|
757
|
+
ok: true,
|
|
758
|
+
value: parsedMsg
|
|
759
|
+
};
|
|
760
|
+
} catch (e) {
|
|
761
|
+
return {
|
|
762
|
+
ok: false,
|
|
763
|
+
reason: coerceErrorString(e)
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
|
|
619
769
|
// transport/sessionStateMachine/transitions.ts
|
|
620
770
|
function inheritSharedSession(session) {
|
|
621
771
|
return {
|
|
@@ -630,7 +780,8 @@ function inheritSharedSession(session) {
|
|
|
630
780
|
options: session.options,
|
|
631
781
|
log: session.log,
|
|
632
782
|
tracer: session.tracer,
|
|
633
|
-
protocolVersion: session.protocolVersion
|
|
783
|
+
protocolVersion: session.protocolVersion,
|
|
784
|
+
codec: session.codec
|
|
634
785
|
};
|
|
635
786
|
}
|
|
636
787
|
function inheritSharedSessionWithGrace(session) {
|
|
@@ -659,7 +810,8 @@ var SessionStateGraph = {
|
|
|
659
810
|
options,
|
|
660
811
|
protocolVersion,
|
|
661
812
|
tracer,
|
|
662
|
-
log
|
|
813
|
+
log,
|
|
814
|
+
codec: new CodecMessageAdapter(options.codec)
|
|
663
815
|
});
|
|
664
816
|
session.log?.info(`session ${session.id} created in NoConnection state`, {
|
|
665
817
|
...session.loggingMetadata,
|
|
@@ -674,7 +826,8 @@ var SessionStateGraph = {
|
|
|
674
826
|
from,
|
|
675
827
|
options,
|
|
676
828
|
tracer,
|
|
677
|
-
log
|
|
829
|
+
log,
|
|
830
|
+
codec: new CodecMessageAdapter(options.codec)
|
|
678
831
|
});
|
|
679
832
|
session.log?.info(`session created in WaitingForHandshake state`, {
|
|
680
833
|
...session.loggingMetadata,
|
|
@@ -752,6 +905,7 @@ var SessionStateGraph = {
|
|
|
752
905
|
listeners,
|
|
753
906
|
...carriedState
|
|
754
907
|
});
|
|
908
|
+
session.startMissingHeartbeatTimeout();
|
|
755
909
|
session.log?.info(
|
|
756
910
|
`session ${session.id} transition from Handshaking to Connected`,
|
|
757
911
|
{
|
|
@@ -787,7 +941,8 @@ var SessionStateGraph = {
|
|
|
787
941
|
options,
|
|
788
942
|
tracer: pendingSession.tracer,
|
|
789
943
|
log: pendingSession.log,
|
|
790
|
-
protocolVersion
|
|
944
|
+
protocolVersion,
|
|
945
|
+
codec: new CodecMessageAdapter(options.codec)
|
|
791
946
|
}
|
|
792
947
|
);
|
|
793
948
|
pendingSession._handleStateExit();
|
|
@@ -797,6 +952,7 @@ var SessionStateGraph = {
|
|
|
797
952
|
listeners,
|
|
798
953
|
...carriedState
|
|
799
954
|
});
|
|
955
|
+
session.startMissingHeartbeatTimeout();
|
|
800
956
|
conn.telemetry = createConnectionTelemetryInfo(
|
|
801
957
|
session.tracer,
|
|
802
958
|
conn,
|
|
@@ -927,44 +1083,6 @@ var ServerSessionStateGraph = {
|
|
|
927
1083
|
}
|
|
928
1084
|
};
|
|
929
1085
|
|
|
930
|
-
// transport/events.ts
|
|
931
|
-
var ProtocolError = {
|
|
932
|
-
RetriesExceeded: "conn_retry_exceeded",
|
|
933
|
-
HandshakeFailed: "handshake_failed",
|
|
934
|
-
MessageOrderingViolated: "message_ordering_violated",
|
|
935
|
-
InvalidMessage: "invalid_message"
|
|
936
|
-
};
|
|
937
|
-
var EventDispatcher = class {
|
|
938
|
-
eventListeners = {};
|
|
939
|
-
removeAllListeners() {
|
|
940
|
-
this.eventListeners = {};
|
|
941
|
-
}
|
|
942
|
-
numberOfListeners(eventType) {
|
|
943
|
-
return this.eventListeners[eventType]?.size ?? 0;
|
|
944
|
-
}
|
|
945
|
-
addEventListener(eventType, handler) {
|
|
946
|
-
if (!this.eventListeners[eventType]) {
|
|
947
|
-
this.eventListeners[eventType] = /* @__PURE__ */ new Set();
|
|
948
|
-
}
|
|
949
|
-
this.eventListeners[eventType]?.add(handler);
|
|
950
|
-
}
|
|
951
|
-
removeEventListener(eventType, handler) {
|
|
952
|
-
const handlers = this.eventListeners[eventType];
|
|
953
|
-
if (handlers) {
|
|
954
|
-
this.eventListeners[eventType]?.delete(handler);
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
dispatchEvent(eventType, event) {
|
|
958
|
-
const handlers = this.eventListeners[eventType];
|
|
959
|
-
if (handlers) {
|
|
960
|
-
const copy = [...handlers];
|
|
961
|
-
for (const handler of copy) {
|
|
962
|
-
handler(event);
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
};
|
|
967
|
-
|
|
968
1086
|
// transport/transport.ts
|
|
969
1087
|
var Transport = class {
|
|
970
1088
|
/**
|
|
@@ -1175,13 +1293,886 @@ var Transport = class {
|
|
|
1175
1293
|
);
|
|
1176
1294
|
}
|
|
1177
1295
|
const sameSession = session.id === sessionId;
|
|
1178
|
-
if (!sameSession) {
|
|
1296
|
+
if (!sameSession || session._isConsumed) {
|
|
1179
1297
|
throw new Error(
|
|
1180
1298
|
`session scope for ${sessionId} has ended (transition), can't send`
|
|
1181
1299
|
);
|
|
1182
1300
|
}
|
|
1183
|
-
|
|
1301
|
+
const res = session.send(msg);
|
|
1302
|
+
if (!res.ok) {
|
|
1303
|
+
throw new Error(res.reason);
|
|
1304
|
+
}
|
|
1305
|
+
return res.value;
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
// transport/client.ts
|
|
1311
|
+
import { SpanStatusCode as SpanStatusCode2 } from "@opentelemetry/api";
|
|
1312
|
+
|
|
1313
|
+
// transport/rateLimit.ts
|
|
1314
|
+
var LeakyBucketRateLimit = class {
|
|
1315
|
+
budgetConsumed;
|
|
1316
|
+
intervalHandle;
|
|
1317
|
+
options;
|
|
1318
|
+
constructor(options) {
|
|
1319
|
+
this.options = options;
|
|
1320
|
+
this.budgetConsumed = 0;
|
|
1321
|
+
}
|
|
1322
|
+
getBackoffMs() {
|
|
1323
|
+
if (this.getBudgetConsumed() === 0) {
|
|
1324
|
+
return 0;
|
|
1325
|
+
}
|
|
1326
|
+
const exponent = Math.max(0, this.getBudgetConsumed() - 1);
|
|
1327
|
+
const jitter = Math.floor(Math.random() * this.options.maxJitterMs);
|
|
1328
|
+
const backoffMs = Math.min(
|
|
1329
|
+
this.options.baseIntervalMs * 2 ** exponent,
|
|
1330
|
+
this.options.maxBackoffMs
|
|
1331
|
+
);
|
|
1332
|
+
return backoffMs + jitter;
|
|
1333
|
+
}
|
|
1334
|
+
get totalBudgetRestoreTime() {
|
|
1335
|
+
return this.options.budgetRestoreIntervalMs * this.options.attemptBudgetCapacity;
|
|
1336
|
+
}
|
|
1337
|
+
consumeBudget() {
|
|
1338
|
+
this.stopLeak();
|
|
1339
|
+
this.budgetConsumed = this.getBudgetConsumed() + 1;
|
|
1340
|
+
}
|
|
1341
|
+
getBudgetConsumed() {
|
|
1342
|
+
return this.budgetConsumed;
|
|
1343
|
+
}
|
|
1344
|
+
hasBudget() {
|
|
1345
|
+
return this.getBudgetConsumed() < this.options.attemptBudgetCapacity;
|
|
1346
|
+
}
|
|
1347
|
+
startRestoringBudget() {
|
|
1348
|
+
if (this.intervalHandle) {
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
const restoreBudgetForUser = () => {
|
|
1352
|
+
const currentBudget = this.budgetConsumed;
|
|
1353
|
+
if (!currentBudget) {
|
|
1354
|
+
this.stopLeak();
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
const newBudget = currentBudget - 1;
|
|
1358
|
+
if (newBudget === 0) {
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
this.budgetConsumed = newBudget;
|
|
1362
|
+
};
|
|
1363
|
+
this.intervalHandle = setInterval(
|
|
1364
|
+
restoreBudgetForUser,
|
|
1365
|
+
this.options.budgetRestoreIntervalMs
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
stopLeak() {
|
|
1369
|
+
if (!this.intervalHandle) {
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
clearInterval(this.intervalHandle);
|
|
1373
|
+
this.intervalHandle = void 0;
|
|
1374
|
+
}
|
|
1375
|
+
close() {
|
|
1376
|
+
this.stopLeak();
|
|
1377
|
+
}
|
|
1378
|
+
};
|
|
1379
|
+
|
|
1380
|
+
// transport/client.ts
|
|
1381
|
+
import { Value as Value2 } from "@sinclair/typebox/value";
|
|
1382
|
+
var ClientTransport = class extends Transport {
|
|
1383
|
+
/**
|
|
1384
|
+
* The options for this transport.
|
|
1385
|
+
*/
|
|
1386
|
+
options;
|
|
1387
|
+
retryBudget;
|
|
1388
|
+
/**
|
|
1389
|
+
* A flag indicating whether the transport should automatically reconnect
|
|
1390
|
+
* when a connection is dropped.
|
|
1391
|
+
* Realistically, this should always be true for clients unless you are writing
|
|
1392
|
+
* tests or a special case where you don't want to reconnect.
|
|
1393
|
+
*/
|
|
1394
|
+
reconnectOnConnectionDrop = true;
|
|
1395
|
+
/**
|
|
1396
|
+
* Optional handshake options for this client.
|
|
1397
|
+
*/
|
|
1398
|
+
handshakeExtensions;
|
|
1399
|
+
sessions;
|
|
1400
|
+
constructor(clientId, providedOptions) {
|
|
1401
|
+
super(clientId, providedOptions);
|
|
1402
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
1403
|
+
this.options = {
|
|
1404
|
+
...defaultClientTransportOptions,
|
|
1405
|
+
...providedOptions
|
|
1406
|
+
};
|
|
1407
|
+
this.retryBudget = new LeakyBucketRateLimit(this.options);
|
|
1408
|
+
}
|
|
1409
|
+
extendHandshake(options) {
|
|
1410
|
+
this.handshakeExtensions = options;
|
|
1411
|
+
}
|
|
1412
|
+
tryReconnecting(to) {
|
|
1413
|
+
const oldSession = this.sessions.get(to);
|
|
1414
|
+
if (!this.options.enableTransparentSessionReconnects && oldSession) {
|
|
1415
|
+
this.deleteSession(oldSession);
|
|
1416
|
+
}
|
|
1417
|
+
if (this.reconnectOnConnectionDrop && this.getStatus() === "open") {
|
|
1418
|
+
this.connect(to);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
/*
|
|
1422
|
+
* Creates a raw unconnected session object.
|
|
1423
|
+
* This is mostly a River internal, you shouldn't need to use this directly.
|
|
1424
|
+
*/
|
|
1425
|
+
createUnconnectedSession(to) {
|
|
1426
|
+
const session = ClientSessionStateGraph.entrypoint(
|
|
1427
|
+
to,
|
|
1428
|
+
this.clientId,
|
|
1429
|
+
{
|
|
1430
|
+
onSessionGracePeriodElapsed: () => {
|
|
1431
|
+
this.onSessionGracePeriodElapsed(session);
|
|
1432
|
+
}
|
|
1433
|
+
},
|
|
1434
|
+
this.options,
|
|
1435
|
+
currentProtocolVersion,
|
|
1436
|
+
this.tracer,
|
|
1437
|
+
this.log
|
|
1438
|
+
);
|
|
1439
|
+
this.createSession(session);
|
|
1440
|
+
return session;
|
|
1441
|
+
}
|
|
1442
|
+
// listeners
|
|
1443
|
+
onConnectingFailed(session) {
|
|
1444
|
+
const noConnectionSession = super.onConnectingFailed(session);
|
|
1445
|
+
this.tryReconnecting(noConnectionSession.to);
|
|
1446
|
+
return noConnectionSession;
|
|
1447
|
+
}
|
|
1448
|
+
onConnClosed(session) {
|
|
1449
|
+
const noConnectionSession = super.onConnClosed(session);
|
|
1450
|
+
this.tryReconnecting(noConnectionSession.to);
|
|
1451
|
+
return noConnectionSession;
|
|
1452
|
+
}
|
|
1453
|
+
onConnectionEstablished(session, conn) {
|
|
1454
|
+
const handshakingSession = ClientSessionStateGraph.transition.ConnectingToHandshaking(
|
|
1455
|
+
session,
|
|
1456
|
+
conn,
|
|
1457
|
+
{
|
|
1458
|
+
onConnectionErrored: (err) => {
|
|
1459
|
+
const errStr = coerceErrorString(err);
|
|
1460
|
+
this.log?.error(
|
|
1461
|
+
`connection to ${handshakingSession.to} errored during handshake: ${errStr}`,
|
|
1462
|
+
handshakingSession.loggingMetadata
|
|
1463
|
+
);
|
|
1464
|
+
},
|
|
1465
|
+
onConnectionClosed: () => {
|
|
1466
|
+
this.log?.warn(
|
|
1467
|
+
`connection to ${handshakingSession.to} closed during handshake`,
|
|
1468
|
+
handshakingSession.loggingMetadata
|
|
1469
|
+
);
|
|
1470
|
+
this.onConnClosed(handshakingSession);
|
|
1471
|
+
},
|
|
1472
|
+
onHandshake: (msg) => {
|
|
1473
|
+
this.onHandshakeResponse(handshakingSession, msg);
|
|
1474
|
+
},
|
|
1475
|
+
onInvalidHandshake: (reason, code) => {
|
|
1476
|
+
this.log?.error(
|
|
1477
|
+
`invalid handshake: ${reason}`,
|
|
1478
|
+
handshakingSession.loggingMetadata
|
|
1479
|
+
);
|
|
1480
|
+
this.deleteSession(session, { unhealthy: true });
|
|
1481
|
+
this.protocolError({
|
|
1482
|
+
type: ProtocolError.HandshakeFailed,
|
|
1483
|
+
code,
|
|
1484
|
+
message: reason
|
|
1485
|
+
});
|
|
1486
|
+
},
|
|
1487
|
+
onHandshakeTimeout: () => {
|
|
1488
|
+
this.log?.error(
|
|
1489
|
+
`connection to ${handshakingSession.to} timed out during handshake`,
|
|
1490
|
+
handshakingSession.loggingMetadata
|
|
1491
|
+
);
|
|
1492
|
+
this.onConnClosed(handshakingSession);
|
|
1493
|
+
},
|
|
1494
|
+
onSessionGracePeriodElapsed: () => {
|
|
1495
|
+
this.onSessionGracePeriodElapsed(handshakingSession);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
);
|
|
1499
|
+
this.updateSession(handshakingSession);
|
|
1500
|
+
void this.sendHandshake(handshakingSession);
|
|
1501
|
+
return handshakingSession;
|
|
1502
|
+
}
|
|
1503
|
+
rejectHandshakeResponse(session, reason, metadata) {
|
|
1504
|
+
session.conn.telemetry?.span.setStatus({
|
|
1505
|
+
code: SpanStatusCode2.ERROR,
|
|
1506
|
+
message: reason
|
|
1507
|
+
});
|
|
1508
|
+
this.log?.warn(reason, metadata);
|
|
1509
|
+
this.deleteSession(session, { unhealthy: true });
|
|
1510
|
+
}
|
|
1511
|
+
onHandshakeResponse(session, msg) {
|
|
1512
|
+
if (!Value2.Check(ControlMessageHandshakeResponseSchema, msg.payload)) {
|
|
1513
|
+
const reason = `received invalid handshake response`;
|
|
1514
|
+
this.rejectHandshakeResponse(session, reason, {
|
|
1515
|
+
...session.loggingMetadata,
|
|
1516
|
+
transportMessage: msg,
|
|
1517
|
+
validationErrors: [
|
|
1518
|
+
...Value2.Errors(ControlMessageHandshakeResponseSchema, msg.payload)
|
|
1519
|
+
]
|
|
1520
|
+
});
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
if (!msg.payload.status.ok) {
|
|
1524
|
+
const retriable = Value2.Check(
|
|
1525
|
+
HandshakeErrorRetriableResponseCodes,
|
|
1526
|
+
msg.payload.status.code
|
|
1527
|
+
);
|
|
1528
|
+
const reason = `handshake failed: ${msg.payload.status.reason}`;
|
|
1529
|
+
const to = session.to;
|
|
1530
|
+
this.rejectHandshakeResponse(session, reason, {
|
|
1531
|
+
...session.loggingMetadata,
|
|
1532
|
+
transportMessage: msg
|
|
1533
|
+
});
|
|
1534
|
+
if (retriable) {
|
|
1535
|
+
this.tryReconnecting(to);
|
|
1536
|
+
} else {
|
|
1537
|
+
this.protocolError({
|
|
1538
|
+
type: ProtocolError.HandshakeFailed,
|
|
1539
|
+
code: msg.payload.status.code,
|
|
1540
|
+
message: reason
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
if (msg.payload.status.sessionId !== session.id) {
|
|
1546
|
+
const reason = `session id mismatch: expected ${session.id}, got ${msg.payload.status.sessionId}`;
|
|
1547
|
+
this.rejectHandshakeResponse(session, reason, {
|
|
1548
|
+
...session.loggingMetadata,
|
|
1549
|
+
transportMessage: msg
|
|
1550
|
+
});
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
this.log?.info(`handshake from ${msg.from} ok`, {
|
|
1554
|
+
...session.loggingMetadata,
|
|
1555
|
+
transportMessage: msg
|
|
1556
|
+
});
|
|
1557
|
+
const connectedSession = ClientSessionStateGraph.transition.HandshakingToConnected(session, {
|
|
1558
|
+
onConnectionErrored: (err) => {
|
|
1559
|
+
const errStr = coerceErrorString(err);
|
|
1560
|
+
this.log?.warn(
|
|
1561
|
+
`connection to ${connectedSession.to} errored: ${errStr}`,
|
|
1562
|
+
connectedSession.loggingMetadata
|
|
1563
|
+
);
|
|
1564
|
+
},
|
|
1565
|
+
onConnectionClosed: () => {
|
|
1566
|
+
this.log?.info(
|
|
1567
|
+
`connection to ${connectedSession.to} closed`,
|
|
1568
|
+
connectedSession.loggingMetadata
|
|
1569
|
+
);
|
|
1570
|
+
this.onConnClosed(connectedSession);
|
|
1571
|
+
},
|
|
1572
|
+
onMessage: (msg2) => {
|
|
1573
|
+
this.handleMsg(msg2);
|
|
1574
|
+
},
|
|
1575
|
+
onInvalidMessage: (reason) => {
|
|
1576
|
+
this.log?.error(`invalid message: ${reason}`, {
|
|
1577
|
+
...connectedSession.loggingMetadata,
|
|
1578
|
+
transportMessage: msg
|
|
1579
|
+
});
|
|
1580
|
+
this.protocolError({
|
|
1581
|
+
type: ProtocolError.InvalidMessage,
|
|
1582
|
+
message: reason
|
|
1583
|
+
});
|
|
1584
|
+
this.deleteSession(connectedSession, { unhealthy: true });
|
|
1585
|
+
},
|
|
1586
|
+
onMessageSendFailure: (msg2, reason) => {
|
|
1587
|
+
this.log?.error(`failed to send message: ${reason}`, {
|
|
1588
|
+
...connectedSession.loggingMetadata,
|
|
1589
|
+
transportMessage: msg2
|
|
1590
|
+
});
|
|
1591
|
+
this.protocolError({
|
|
1592
|
+
type: ProtocolError.MessageSendFailure,
|
|
1593
|
+
message: reason
|
|
1594
|
+
});
|
|
1595
|
+
this.deleteSession(connectedSession, { unhealthy: true });
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
const res = connectedSession.sendBufferedMessages();
|
|
1599
|
+
if (!res.ok) {
|
|
1600
|
+
this.log?.error(`failed to send buffered messages: ${res.reason}`, {
|
|
1601
|
+
...connectedSession.loggingMetadata,
|
|
1602
|
+
transportMessage: msg
|
|
1603
|
+
});
|
|
1604
|
+
this.protocolError({
|
|
1605
|
+
type: ProtocolError.MessageSendFailure,
|
|
1606
|
+
message: res.reason
|
|
1607
|
+
});
|
|
1608
|
+
this.deleteSession(connectedSession, { unhealthy: true });
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
this.updateSession(connectedSession);
|
|
1612
|
+
this.retryBudget.startRestoringBudget();
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Manually attempts to connect to a client.
|
|
1616
|
+
* @param to The client ID of the node to connect to.
|
|
1617
|
+
*/
|
|
1618
|
+
connect(to) {
|
|
1619
|
+
if (this.getStatus() !== "open") {
|
|
1620
|
+
this.log?.info(
|
|
1621
|
+
`transport state is no longer open, cancelling attempt to connect to ${to}`
|
|
1622
|
+
);
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
const session = this.sessions.get(to) ?? this.createUnconnectedSession(to);
|
|
1626
|
+
if (session.state !== "NoConnection" /* NoConnection */) {
|
|
1627
|
+
this.log?.debug(
|
|
1628
|
+
`session to ${to} has state ${session.state}, skipping connect attempt`,
|
|
1629
|
+
session.loggingMetadata
|
|
1630
|
+
);
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
if (!this.retryBudget.hasBudget()) {
|
|
1634
|
+
const budgetConsumed = this.retryBudget.getBudgetConsumed();
|
|
1635
|
+
const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
|
|
1636
|
+
this.log?.error(errMsg, session.loggingMetadata);
|
|
1637
|
+
this.protocolError({
|
|
1638
|
+
type: ProtocolError.RetriesExceeded,
|
|
1639
|
+
message: errMsg
|
|
1640
|
+
});
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
const backoffMs = this.retryBudget.getBackoffMs();
|
|
1644
|
+
this.log?.info(
|
|
1645
|
+
`attempting connection to ${to} (${backoffMs}ms backoff)`,
|
|
1646
|
+
session.loggingMetadata
|
|
1647
|
+
);
|
|
1648
|
+
this.retryBudget.consumeBudget();
|
|
1649
|
+
const backingOffSession = ClientSessionStateGraph.transition.NoConnectionToBackingOff(
|
|
1650
|
+
session,
|
|
1651
|
+
backoffMs,
|
|
1652
|
+
{
|
|
1653
|
+
onBackoffFinished: () => {
|
|
1654
|
+
this.onBackoffFinished(backingOffSession);
|
|
1655
|
+
},
|
|
1656
|
+
onSessionGracePeriodElapsed: () => {
|
|
1657
|
+
this.onSessionGracePeriodElapsed(backingOffSession);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
);
|
|
1661
|
+
this.updateSession(backingOffSession);
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Manually kills all sessions to the server (including all pending state).
|
|
1665
|
+
* This is useful for when you want to close all connections to a server
|
|
1666
|
+
* and don't want to wait for the grace period to elapse.
|
|
1667
|
+
*/
|
|
1668
|
+
hardDisconnect() {
|
|
1669
|
+
const sessions = Array.from(this.sessions.values());
|
|
1670
|
+
for (const session of sessions) {
|
|
1671
|
+
this.deleteSession(session);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
onBackoffFinished(session) {
|
|
1675
|
+
const connPromise = session.tracer.startActiveSpan(
|
|
1676
|
+
"connect",
|
|
1677
|
+
async (span) => {
|
|
1678
|
+
try {
|
|
1679
|
+
return await this.createNewOutgoingConnection(session.to);
|
|
1680
|
+
} catch (err) {
|
|
1681
|
+
const errStr = coerceErrorString(err);
|
|
1682
|
+
span.recordException(errStr);
|
|
1683
|
+
span.setStatus({ code: SpanStatusCode2.ERROR });
|
|
1684
|
+
throw err;
|
|
1685
|
+
} finally {
|
|
1686
|
+
span.end();
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
);
|
|
1690
|
+
const connectingSession = ClientSessionStateGraph.transition.BackingOffToConnecting(
|
|
1691
|
+
session,
|
|
1692
|
+
connPromise,
|
|
1693
|
+
{
|
|
1694
|
+
onConnectionEstablished: (conn) => {
|
|
1695
|
+
this.log?.debug(
|
|
1696
|
+
`connection to ${connectingSession.to} established`,
|
|
1697
|
+
{
|
|
1698
|
+
...conn.loggingMetadata,
|
|
1699
|
+
...connectingSession.loggingMetadata
|
|
1700
|
+
}
|
|
1701
|
+
);
|
|
1702
|
+
this.onConnectionEstablished(connectingSession, conn);
|
|
1703
|
+
},
|
|
1704
|
+
onConnectionFailed: (error) => {
|
|
1705
|
+
const errStr = coerceErrorString(error);
|
|
1706
|
+
this.log?.error(
|
|
1707
|
+
`error connecting to ${connectingSession.to}: ${errStr}`,
|
|
1708
|
+
connectingSession.loggingMetadata
|
|
1709
|
+
);
|
|
1710
|
+
this.onConnectingFailed(connectingSession);
|
|
1711
|
+
},
|
|
1712
|
+
onConnectionTimeout: () => {
|
|
1713
|
+
this.log?.error(
|
|
1714
|
+
`connection to ${connectingSession.to} timed out`,
|
|
1715
|
+
connectingSession.loggingMetadata
|
|
1716
|
+
);
|
|
1717
|
+
this.onConnectingFailed(connectingSession);
|
|
1718
|
+
},
|
|
1719
|
+
onSessionGracePeriodElapsed: () => {
|
|
1720
|
+
this.onSessionGracePeriodElapsed(connectingSession);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
);
|
|
1724
|
+
this.updateSession(connectingSession);
|
|
1725
|
+
}
|
|
1726
|
+
async sendHandshake(session) {
|
|
1727
|
+
let metadata = void 0;
|
|
1728
|
+
if (this.handshakeExtensions) {
|
|
1729
|
+
metadata = await this.handshakeExtensions.construct();
|
|
1730
|
+
}
|
|
1731
|
+
if (session._isConsumed) {
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
const requestMsg = handshakeRequestMessage({
|
|
1735
|
+
from: this.clientId,
|
|
1736
|
+
to: session.to,
|
|
1737
|
+
sessionId: session.id,
|
|
1738
|
+
expectedSessionState: {
|
|
1739
|
+
nextExpectedSeq: session.ack,
|
|
1740
|
+
nextSentSeq: session.nextSeq()
|
|
1741
|
+
},
|
|
1742
|
+
metadata,
|
|
1743
|
+
tracing: getPropagationContext(session.telemetry.ctx)
|
|
1744
|
+
});
|
|
1745
|
+
this.log?.debug(`sending handshake request to ${session.to}`, {
|
|
1746
|
+
...session.loggingMetadata,
|
|
1747
|
+
transportMessage: requestMsg
|
|
1748
|
+
});
|
|
1749
|
+
const res = session.sendHandshake(requestMsg);
|
|
1750
|
+
if (!res.ok) {
|
|
1751
|
+
this.log?.error(`failed to send handshake request: ${res.reason}`, {
|
|
1752
|
+
...session.loggingMetadata,
|
|
1753
|
+
transportMessage: requestMsg
|
|
1754
|
+
});
|
|
1755
|
+
this.protocolError({
|
|
1756
|
+
type: ProtocolError.MessageSendFailure,
|
|
1757
|
+
message: res.reason
|
|
1758
|
+
});
|
|
1759
|
+
this.deleteSession(session, { unhealthy: true });
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
close() {
|
|
1763
|
+
this.retryBudget.close();
|
|
1764
|
+
super.close();
|
|
1765
|
+
}
|
|
1766
|
+
};
|
|
1767
|
+
|
|
1768
|
+
// transport/server.ts
|
|
1769
|
+
import { SpanStatusCode as SpanStatusCode3 } from "@opentelemetry/api";
|
|
1770
|
+
import { Value as Value3 } from "@sinclair/typebox/value";
|
|
1771
|
+
var ServerTransport = class extends Transport {
|
|
1772
|
+
/**
|
|
1773
|
+
* The options for this transport.
|
|
1774
|
+
*/
|
|
1775
|
+
options;
|
|
1776
|
+
/**
|
|
1777
|
+
* Optional handshake options for the server.
|
|
1778
|
+
*/
|
|
1779
|
+
handshakeExtensions;
|
|
1780
|
+
/**
|
|
1781
|
+
* A map of session handshake data for each session.
|
|
1782
|
+
*/
|
|
1783
|
+
sessionHandshakeMetadata = /* @__PURE__ */ new Map();
|
|
1784
|
+
sessions = /* @__PURE__ */ new Map();
|
|
1785
|
+
pendingSessions = /* @__PURE__ */ new Set();
|
|
1786
|
+
constructor(clientId, providedOptions) {
|
|
1787
|
+
super(clientId, providedOptions);
|
|
1788
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
1789
|
+
this.options = {
|
|
1790
|
+
...defaultServerTransportOptions,
|
|
1791
|
+
...providedOptions
|
|
1184
1792
|
};
|
|
1793
|
+
this.log?.info(`initiated server transport`, {
|
|
1794
|
+
clientId: this.clientId,
|
|
1795
|
+
protocolVersion: currentProtocolVersion
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
extendHandshake(options) {
|
|
1799
|
+
this.handshakeExtensions = options;
|
|
1800
|
+
}
|
|
1801
|
+
deletePendingSession(pendingSession) {
|
|
1802
|
+
pendingSession.close();
|
|
1803
|
+
this.pendingSessions.delete(pendingSession);
|
|
1804
|
+
}
|
|
1805
|
+
deleteSession(session, options) {
|
|
1806
|
+
this.sessionHandshakeMetadata.delete(session.to);
|
|
1807
|
+
super.deleteSession(session, options);
|
|
1808
|
+
}
|
|
1809
|
+
handleConnection(conn) {
|
|
1810
|
+
if (this.getStatus() !== "open")
|
|
1811
|
+
return;
|
|
1812
|
+
this.log?.info(`new incoming connection`, {
|
|
1813
|
+
...conn.loggingMetadata,
|
|
1814
|
+
clientId: this.clientId
|
|
1815
|
+
});
|
|
1816
|
+
let receivedHandshake = false;
|
|
1817
|
+
const pendingSession = ServerSessionStateGraph.entrypoint(
|
|
1818
|
+
this.clientId,
|
|
1819
|
+
conn,
|
|
1820
|
+
{
|
|
1821
|
+
onConnectionClosed: () => {
|
|
1822
|
+
this.log?.warn(
|
|
1823
|
+
`connection from unknown closed before handshake finished`,
|
|
1824
|
+
pendingSession.loggingMetadata
|
|
1825
|
+
);
|
|
1826
|
+
this.deletePendingSession(pendingSession);
|
|
1827
|
+
},
|
|
1828
|
+
onConnectionErrored: (err) => {
|
|
1829
|
+
const errorString = coerceErrorString(err);
|
|
1830
|
+
this.log?.warn(
|
|
1831
|
+
`connection from unknown errored before handshake finished: ${errorString}`,
|
|
1832
|
+
pendingSession.loggingMetadata
|
|
1833
|
+
);
|
|
1834
|
+
this.deletePendingSession(pendingSession);
|
|
1835
|
+
},
|
|
1836
|
+
onHandshakeTimeout: () => {
|
|
1837
|
+
this.log?.warn(
|
|
1838
|
+
`connection from unknown timed out before handshake finished`,
|
|
1839
|
+
pendingSession.loggingMetadata
|
|
1840
|
+
);
|
|
1841
|
+
this.deletePendingSession(pendingSession);
|
|
1842
|
+
},
|
|
1843
|
+
onHandshake: (msg) => {
|
|
1844
|
+
if (receivedHandshake) {
|
|
1845
|
+
this.log?.error(
|
|
1846
|
+
`received multiple handshake messages from pending session`,
|
|
1847
|
+
{
|
|
1848
|
+
...pendingSession.loggingMetadata,
|
|
1849
|
+
connectedTo: msg.from,
|
|
1850
|
+
transportMessage: msg
|
|
1851
|
+
}
|
|
1852
|
+
);
|
|
1853
|
+
this.deletePendingSession(pendingSession);
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
receivedHandshake = true;
|
|
1857
|
+
void this.onHandshakeRequest(pendingSession, msg);
|
|
1858
|
+
},
|
|
1859
|
+
onInvalidHandshake: (reason, code) => {
|
|
1860
|
+
this.log?.error(
|
|
1861
|
+
`invalid handshake: ${reason}`,
|
|
1862
|
+
pendingSession.loggingMetadata
|
|
1863
|
+
);
|
|
1864
|
+
this.deletePendingSession(pendingSession);
|
|
1865
|
+
this.protocolError({
|
|
1866
|
+
type: ProtocolError.HandshakeFailed,
|
|
1867
|
+
code,
|
|
1868
|
+
message: reason
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
},
|
|
1872
|
+
this.options,
|
|
1873
|
+
this.tracer,
|
|
1874
|
+
this.log
|
|
1875
|
+
);
|
|
1876
|
+
this.pendingSessions.add(pendingSession);
|
|
1877
|
+
}
|
|
1878
|
+
rejectHandshakeRequest(session, to, reason, code, metadata) {
|
|
1879
|
+
session.conn.telemetry?.span.setStatus({
|
|
1880
|
+
code: SpanStatusCode3.ERROR,
|
|
1881
|
+
message: reason
|
|
1882
|
+
});
|
|
1883
|
+
this.log?.warn(reason, metadata);
|
|
1884
|
+
const responseMsg = handshakeResponseMessage({
|
|
1885
|
+
from: this.clientId,
|
|
1886
|
+
to,
|
|
1887
|
+
status: {
|
|
1888
|
+
ok: false,
|
|
1889
|
+
code,
|
|
1890
|
+
reason
|
|
1891
|
+
}
|
|
1892
|
+
});
|
|
1893
|
+
const res = session.sendHandshake(responseMsg);
|
|
1894
|
+
if (!res.ok) {
|
|
1895
|
+
this.log?.error(`failed to send handshake response: ${res.reason}`, {
|
|
1896
|
+
...session.loggingMetadata,
|
|
1897
|
+
transportMessage: responseMsg
|
|
1898
|
+
});
|
|
1899
|
+
this.protocolError({
|
|
1900
|
+
type: ProtocolError.MessageSendFailure,
|
|
1901
|
+
message: res.reason
|
|
1902
|
+
});
|
|
1903
|
+
this.deletePendingSession(session);
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
this.protocolError({
|
|
1907
|
+
type: ProtocolError.HandshakeFailed,
|
|
1908
|
+
code,
|
|
1909
|
+
message: reason
|
|
1910
|
+
});
|
|
1911
|
+
this.deletePendingSession(session);
|
|
1912
|
+
}
|
|
1913
|
+
async onHandshakeRequest(session, msg) {
|
|
1914
|
+
if (!Value3.Check(ControlMessageHandshakeRequestSchema, msg.payload)) {
|
|
1915
|
+
this.rejectHandshakeRequest(
|
|
1916
|
+
session,
|
|
1917
|
+
msg.from,
|
|
1918
|
+
"received invalid handshake request",
|
|
1919
|
+
"MALFORMED_HANDSHAKE",
|
|
1920
|
+
{
|
|
1921
|
+
...session.loggingMetadata,
|
|
1922
|
+
transportMessage: msg,
|
|
1923
|
+
connectedTo: msg.from,
|
|
1924
|
+
validationErrors: [
|
|
1925
|
+
...Value3.Errors(ControlMessageHandshakeRequestSchema, msg.payload)
|
|
1926
|
+
]
|
|
1927
|
+
}
|
|
1928
|
+
);
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
const gotVersion = msg.payload.protocolVersion;
|
|
1932
|
+
if (!isAcceptedProtocolVersion(gotVersion)) {
|
|
1933
|
+
this.rejectHandshakeRequest(
|
|
1934
|
+
session,
|
|
1935
|
+
msg.from,
|
|
1936
|
+
`expected protocol version oneof [${acceptedProtocolVersions.toString()}], got ${gotVersion}`,
|
|
1937
|
+
"PROTOCOL_VERSION_MISMATCH",
|
|
1938
|
+
{
|
|
1939
|
+
...session.loggingMetadata,
|
|
1940
|
+
connectedTo: msg.from,
|
|
1941
|
+
transportMessage: msg
|
|
1942
|
+
}
|
|
1943
|
+
);
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
let parsedMetadata = {};
|
|
1947
|
+
if (this.handshakeExtensions) {
|
|
1948
|
+
if (!Value3.Check(this.handshakeExtensions.schema, msg.payload.metadata)) {
|
|
1949
|
+
this.rejectHandshakeRequest(
|
|
1950
|
+
session,
|
|
1951
|
+
msg.from,
|
|
1952
|
+
"received malformed handshake metadata",
|
|
1953
|
+
"MALFORMED_HANDSHAKE_META",
|
|
1954
|
+
{
|
|
1955
|
+
...session.loggingMetadata,
|
|
1956
|
+
connectedTo: msg.from,
|
|
1957
|
+
validationErrors: [
|
|
1958
|
+
...Value3.Errors(
|
|
1959
|
+
this.handshakeExtensions.schema,
|
|
1960
|
+
msg.payload.metadata
|
|
1961
|
+
)
|
|
1962
|
+
]
|
|
1963
|
+
}
|
|
1964
|
+
);
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1967
|
+
const previousParsedMetadata = this.sessionHandshakeMetadata.get(
|
|
1968
|
+
msg.from
|
|
1969
|
+
);
|
|
1970
|
+
const parsedMetadataOrFailureCode = await this.handshakeExtensions.validate(
|
|
1971
|
+
msg.payload.metadata,
|
|
1972
|
+
previousParsedMetadata
|
|
1973
|
+
);
|
|
1974
|
+
if (session._isConsumed) {
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
if (Value3.Check(
|
|
1978
|
+
HandshakeErrorCustomHandlerFatalResponseCodes,
|
|
1979
|
+
parsedMetadataOrFailureCode
|
|
1980
|
+
)) {
|
|
1981
|
+
this.rejectHandshakeRequest(
|
|
1982
|
+
session,
|
|
1983
|
+
msg.from,
|
|
1984
|
+
"rejected by handshake handler",
|
|
1985
|
+
parsedMetadataOrFailureCode,
|
|
1986
|
+
{
|
|
1987
|
+
...session.loggingMetadata,
|
|
1988
|
+
connectedTo: msg.from,
|
|
1989
|
+
clientId: this.clientId
|
|
1990
|
+
}
|
|
1991
|
+
);
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1994
|
+
parsedMetadata = parsedMetadataOrFailureCode;
|
|
1995
|
+
}
|
|
1996
|
+
let connectCase = "new session";
|
|
1997
|
+
const clientNextExpectedSeq = msg.payload.expectedSessionState.nextExpectedSeq;
|
|
1998
|
+
const clientNextSentSeq = msg.payload.expectedSessionState.nextSentSeq;
|
|
1999
|
+
let oldSession = this.sessions.get(msg.from);
|
|
2000
|
+
if (this.options.enableTransparentSessionReconnects && oldSession && oldSession.id === msg.payload.sessionId) {
|
|
2001
|
+
connectCase = "transparent reconnection";
|
|
2002
|
+
const ourNextSeq = oldSession.nextSeq();
|
|
2003
|
+
const ourAck = oldSession.ack;
|
|
2004
|
+
if (clientNextSentSeq > ourAck) {
|
|
2005
|
+
this.rejectHandshakeRequest(
|
|
2006
|
+
session,
|
|
2007
|
+
msg.from,
|
|
2008
|
+
`client is in the future: server wanted next message to be ${ourAck} but client would have sent ${clientNextSentSeq}`,
|
|
2009
|
+
"SESSION_STATE_MISMATCH",
|
|
2010
|
+
{
|
|
2011
|
+
...session.loggingMetadata,
|
|
2012
|
+
connectedTo: msg.from,
|
|
2013
|
+
transportMessage: msg
|
|
2014
|
+
}
|
|
2015
|
+
);
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
if (ourNextSeq > clientNextExpectedSeq) {
|
|
2019
|
+
this.rejectHandshakeRequest(
|
|
2020
|
+
session,
|
|
2021
|
+
msg.from,
|
|
2022
|
+
`server is in the future: client wanted next message to be ${clientNextExpectedSeq} but server would have sent ${ourNextSeq}`,
|
|
2023
|
+
"SESSION_STATE_MISMATCH",
|
|
2024
|
+
{
|
|
2025
|
+
...session.loggingMetadata,
|
|
2026
|
+
connectedTo: msg.from,
|
|
2027
|
+
transportMessage: msg
|
|
2028
|
+
}
|
|
2029
|
+
);
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
if (oldSession.state !== "NoConnection" /* NoConnection */) {
|
|
2033
|
+
const noConnectionSession = ServerSessionStateGraph.transition.ConnectedToNoConnection(
|
|
2034
|
+
oldSession,
|
|
2035
|
+
{
|
|
2036
|
+
onSessionGracePeriodElapsed: () => {
|
|
2037
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
);
|
|
2041
|
+
oldSession = noConnectionSession;
|
|
2042
|
+
this.updateSession(oldSession);
|
|
2043
|
+
}
|
|
2044
|
+
} else if (oldSession) {
|
|
2045
|
+
connectCase = "hard reconnection";
|
|
2046
|
+
this.log?.info(
|
|
2047
|
+
`client is reconnecting to a new session (${msg.payload.sessionId}) with an old session (${oldSession.id}) already existing, closing old session`,
|
|
2048
|
+
{
|
|
2049
|
+
...session.loggingMetadata,
|
|
2050
|
+
connectedTo: msg.from,
|
|
2051
|
+
sessionId: msg.payload.sessionId
|
|
2052
|
+
}
|
|
2053
|
+
);
|
|
2054
|
+
this.deleteSession(oldSession);
|
|
2055
|
+
oldSession = void 0;
|
|
2056
|
+
}
|
|
2057
|
+
if (!oldSession && (clientNextSentSeq > 0 || clientNextExpectedSeq > 0)) {
|
|
2058
|
+
connectCase = "unknown session";
|
|
2059
|
+
const rejectionMessage = this.options.enableTransparentSessionReconnects ? `client is trying to reconnect to a session the server don't know about: ${msg.payload.sessionId}` : `client is attempting a transparent reconnect to a session but the server does not support it: ${msg.payload.sessionId}`;
|
|
2060
|
+
this.rejectHandshakeRequest(
|
|
2061
|
+
session,
|
|
2062
|
+
msg.from,
|
|
2063
|
+
rejectionMessage,
|
|
2064
|
+
"SESSION_STATE_MISMATCH",
|
|
2065
|
+
{
|
|
2066
|
+
...session.loggingMetadata,
|
|
2067
|
+
connectedTo: msg.from,
|
|
2068
|
+
transportMessage: msg
|
|
2069
|
+
}
|
|
2070
|
+
);
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
const sessionId = msg.payload.sessionId;
|
|
2074
|
+
this.log?.info(
|
|
2075
|
+
`handshake from ${msg.from} ok (${connectCase}), responding with handshake success`,
|
|
2076
|
+
{
|
|
2077
|
+
...session.loggingMetadata,
|
|
2078
|
+
connectedTo: msg.from
|
|
2079
|
+
}
|
|
2080
|
+
);
|
|
2081
|
+
const responseMsg = handshakeResponseMessage({
|
|
2082
|
+
from: this.clientId,
|
|
2083
|
+
to: msg.from,
|
|
2084
|
+
status: {
|
|
2085
|
+
ok: true,
|
|
2086
|
+
sessionId
|
|
2087
|
+
}
|
|
2088
|
+
});
|
|
2089
|
+
const res = session.sendHandshake(responseMsg);
|
|
2090
|
+
if (!res.ok) {
|
|
2091
|
+
this.log?.error(`failed to send handshake response: ${res.reason}`, {
|
|
2092
|
+
...session.loggingMetadata,
|
|
2093
|
+
transportMessage: responseMsg
|
|
2094
|
+
});
|
|
2095
|
+
this.protocolError({
|
|
2096
|
+
type: ProtocolError.MessageSendFailure,
|
|
2097
|
+
message: res.reason
|
|
2098
|
+
});
|
|
2099
|
+
this.deletePendingSession(session);
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
this.pendingSessions.delete(session);
|
|
2103
|
+
const connectedSession = ServerSessionStateGraph.transition.WaitingForHandshakeToConnected(
|
|
2104
|
+
session,
|
|
2105
|
+
// by this point oldSession is either no connection or we dont have an old session
|
|
2106
|
+
oldSession,
|
|
2107
|
+
sessionId,
|
|
2108
|
+
msg.from,
|
|
2109
|
+
msg.tracing,
|
|
2110
|
+
{
|
|
2111
|
+
onConnectionErrored: (err) => {
|
|
2112
|
+
const errStr = coerceErrorString(err);
|
|
2113
|
+
this.log?.warn(
|
|
2114
|
+
`connection to ${connectedSession.to} errored: ${errStr}`,
|
|
2115
|
+
connectedSession.loggingMetadata
|
|
2116
|
+
);
|
|
2117
|
+
},
|
|
2118
|
+
onConnectionClosed: () => {
|
|
2119
|
+
this.log?.info(
|
|
2120
|
+
`connection to ${connectedSession.to} closed`,
|
|
2121
|
+
connectedSession.loggingMetadata
|
|
2122
|
+
);
|
|
2123
|
+
this.onConnClosed(connectedSession);
|
|
2124
|
+
},
|
|
2125
|
+
onMessage: (msg2) => {
|
|
2126
|
+
this.handleMsg(msg2);
|
|
2127
|
+
},
|
|
2128
|
+
onInvalidMessage: (reason) => {
|
|
2129
|
+
this.log?.error(`invalid message: ${reason}`, {
|
|
2130
|
+
...connectedSession.loggingMetadata,
|
|
2131
|
+
transportMessage: msg
|
|
2132
|
+
});
|
|
2133
|
+
this.protocolError({
|
|
2134
|
+
type: ProtocolError.InvalidMessage,
|
|
2135
|
+
message: reason
|
|
2136
|
+
});
|
|
2137
|
+
this.deleteSession(connectedSession, { unhealthy: true });
|
|
2138
|
+
},
|
|
2139
|
+
onMessageSendFailure: (msg2, reason) => {
|
|
2140
|
+
this.log?.error(`failed to send message: ${reason}`, {
|
|
2141
|
+
...connectedSession.loggingMetadata,
|
|
2142
|
+
transportMessage: msg2
|
|
2143
|
+
});
|
|
2144
|
+
this.protocolError({
|
|
2145
|
+
type: ProtocolError.MessageSendFailure,
|
|
2146
|
+
message: reason
|
|
2147
|
+
});
|
|
2148
|
+
this.deleteSession(connectedSession, { unhealthy: true });
|
|
2149
|
+
}
|
|
2150
|
+
},
|
|
2151
|
+
gotVersion
|
|
2152
|
+
);
|
|
2153
|
+
const bufferSendRes = connectedSession.sendBufferedMessages();
|
|
2154
|
+
if (!bufferSendRes.ok) {
|
|
2155
|
+
this.log?.error(
|
|
2156
|
+
`failed to send buffered messages: ${bufferSendRes.reason}`,
|
|
2157
|
+
{
|
|
2158
|
+
...connectedSession.loggingMetadata,
|
|
2159
|
+
transportMessage: msg
|
|
2160
|
+
}
|
|
2161
|
+
);
|
|
2162
|
+
this.protocolError({
|
|
2163
|
+
type: ProtocolError.MessageSendFailure,
|
|
2164
|
+
message: bufferSendRes.reason
|
|
2165
|
+
});
|
|
2166
|
+
this.deleteSession(connectedSession, { unhealthy: true });
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
this.sessionHandshakeMetadata.set(connectedSession.to, parsedMetadata);
|
|
2170
|
+
if (oldSession) {
|
|
2171
|
+
this.updateSession(connectedSession);
|
|
2172
|
+
} else {
|
|
2173
|
+
this.createSession(connectedSession);
|
|
2174
|
+
}
|
|
2175
|
+
connectedSession.startActiveHeartbeat();
|
|
1185
2176
|
}
|
|
1186
2177
|
};
|
|
1187
2178
|
|
|
@@ -1273,15 +2264,17 @@ var Connection = class {
|
|
|
1273
2264
|
};
|
|
1274
2265
|
|
|
1275
2266
|
export {
|
|
2267
|
+
BinaryCodec,
|
|
2268
|
+
NaiveJsonCodec,
|
|
2269
|
+
ProtocolError,
|
|
1276
2270
|
defaultTransportOptions,
|
|
1277
2271
|
defaultClientTransportOptions,
|
|
1278
|
-
defaultServerTransportOptions,
|
|
1279
2272
|
SessionState,
|
|
1280
2273
|
SessionStateGraph,
|
|
1281
|
-
ClientSessionStateGraph,
|
|
1282
|
-
ServerSessionStateGraph,
|
|
1283
|
-
ProtocolError,
|
|
1284
2274
|
Transport,
|
|
1285
|
-
|
|
2275
|
+
ClientTransport,
|
|
2276
|
+
ServerTransport,
|
|
2277
|
+
Connection,
|
|
2278
|
+
CodecMessageAdapter
|
|
1286
2279
|
};
|
|
1287
|
-
//# sourceMappingURL=chunk-
|
|
2280
|
+
//# sourceMappingURL=chunk-BO7MFCO6.js.map
|