@replit/river 0.207.1 → 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-7LMZNSVC.js → chunk-B7REV3ZV.js} +6 -5
- package/dist/{chunk-7LMZNSVC.js.map → chunk-B7REV3ZV.js.map} +1 -1
- package/dist/{chunk-QMAVXV4Z.js → chunk-BO7MFCO6.js} +1136 -132
- package/dist/chunk-BO7MFCO6.js.map +1 -0
- package/dist/{chunk-BCCZA7SX.js → chunk-QGPYCXV4.js} +2 -2
- package/dist/{chunk-BCCZA7SX.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-933c87b2.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-4cd29829.d.ts → services-87887bc5.d.ts} +16 -11
- package/dist/testUtil/index.cjs +992 -829
- 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 -193
- 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 -117
- 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 -259
- 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-AJGIY2UB.js +0 -56
- package/dist/chunk-AJGIY2UB.js.map +0 -1
- package/dist/chunk-CRD3HDVN.js +0 -438
- package/dist/chunk-CRD3HDVN.js.map +0 -1
- package/dist/chunk-I27WBSMZ.js +0 -377
- package/dist/chunk-I27WBSMZ.js.map +0 -1
- package/dist/chunk-QMAVXV4Z.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,12 +520,15 @@ var SessionConnected = class extends IdentifiedSession {
|
|
|
426
520
|
conn;
|
|
427
521
|
listeners;
|
|
428
522
|
heartbeatHandle;
|
|
429
|
-
|
|
430
|
-
isActivelyHeartbeating;
|
|
523
|
+
heartbeatMissTimeout;
|
|
524
|
+
isActivelyHeartbeating = false;
|
|
431
525
|
updateBookkeeping(ack, seq) {
|
|
432
526
|
this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
|
|
433
527
|
this.ack = seq + 1;
|
|
434
|
-
this.
|
|
528
|
+
if (this.heartbeatMissTimeout) {
|
|
529
|
+
clearTimeout(this.heartbeatMissTimeout);
|
|
530
|
+
}
|
|
531
|
+
this.startMissingHeartbeatTimeout();
|
|
435
532
|
}
|
|
436
533
|
assertSendOrdering(constructedMsg) {
|
|
437
534
|
if (constructedMsg.seq > this.seqSent + 1) {
|
|
@@ -448,9 +545,13 @@ var SessionConnected = class extends IdentifiedSession {
|
|
|
448
545
|
const constructedMsg = this.constructMsg(msg);
|
|
449
546
|
this.assertSendOrdering(constructedMsg);
|
|
450
547
|
this.sendBuffer.push(constructedMsg);
|
|
451
|
-
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
|
+
}
|
|
452
553
|
this.seqSent = constructedMsg.seq;
|
|
453
|
-
return
|
|
554
|
+
return res;
|
|
454
555
|
}
|
|
455
556
|
constructor(props) {
|
|
456
557
|
super(props);
|
|
@@ -459,6 +560,8 @@ var SessionConnected = class extends IdentifiedSession {
|
|
|
459
560
|
this.conn.addDataListener(this.onMessageData);
|
|
460
561
|
this.conn.addCloseListener(this.listeners.onConnectionClosed);
|
|
461
562
|
this.conn.addErrorListener(this.listeners.onConnectionErrored);
|
|
563
|
+
}
|
|
564
|
+
sendBufferedMessages() {
|
|
462
565
|
if (this.sendBuffer.length > 0) {
|
|
463
566
|
this.log?.info(
|
|
464
567
|
`sending ${this.sendBuffer.length} buffered messages, starting at seq ${this.nextSeq()}`,
|
|
@@ -466,30 +569,15 @@ var SessionConnected = class extends IdentifiedSession {
|
|
|
466
569
|
);
|
|
467
570
|
for (const msg of this.sendBuffer) {
|
|
468
571
|
this.assertSendOrdering(msg);
|
|
469
|
-
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
|
+
}
|
|
470
577
|
this.seqSent = msg.seq;
|
|
471
578
|
}
|
|
472
579
|
}
|
|
473
|
-
|
|
474
|
-
this.heartbeatHandle = setInterval(() => {
|
|
475
|
-
const misses = this.heartbeatMisses;
|
|
476
|
-
const missDuration = misses * this.options.heartbeatIntervalMs;
|
|
477
|
-
if (misses >= this.options.heartbeatsUntilDead) {
|
|
478
|
-
this.log?.info(
|
|
479
|
-
`closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
|
|
480
|
-
this.loggingMetadata
|
|
481
|
-
);
|
|
482
|
-
this.telemetry.span.addEvent("closing connection due to inactivity");
|
|
483
|
-
this.conn.close();
|
|
484
|
-
clearInterval(this.heartbeatHandle);
|
|
485
|
-
this.heartbeatHandle = void 0;
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
if (this.isActivelyHeartbeating) {
|
|
489
|
-
this.sendHeartbeat();
|
|
490
|
-
}
|
|
491
|
-
this.heartbeatMisses++;
|
|
492
|
-
}, this.options.heartbeatIntervalMs);
|
|
580
|
+
return { ok: true, value: void 0 };
|
|
493
581
|
}
|
|
494
582
|
get loggingMetadata() {
|
|
495
583
|
return {
|
|
@@ -497,31 +585,46 @@ var SessionConnected = class extends IdentifiedSession {
|
|
|
497
585
|
...this.conn.loggingMetadata
|
|
498
586
|
};
|
|
499
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
|
+
}
|
|
500
602
|
startActiveHeartbeat() {
|
|
501
603
|
this.isActivelyHeartbeating = true;
|
|
604
|
+
this.heartbeatHandle = setInterval(() => {
|
|
605
|
+
this.sendHeartbeat();
|
|
606
|
+
}, this.options.heartbeatIntervalMs);
|
|
502
607
|
}
|
|
503
608
|
sendHeartbeat() {
|
|
504
609
|
this.log?.debug("sending heartbeat", this.loggingMetadata);
|
|
505
|
-
|
|
610
|
+
const heartbeat = {
|
|
506
611
|
streamId: "heartbeat",
|
|
507
612
|
controlFlags: 1 /* AckBit */,
|
|
508
613
|
payload: {
|
|
509
614
|
type: "ACK"
|
|
510
615
|
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
closeConnection() {
|
|
514
|
-
this.conn.removeDataListener(this.onMessageData);
|
|
515
|
-
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
516
|
-
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
517
|
-
this.conn.close();
|
|
616
|
+
};
|
|
617
|
+
this.send(heartbeat);
|
|
518
618
|
}
|
|
519
619
|
onMessageData = (msg) => {
|
|
520
|
-
const
|
|
521
|
-
if (
|
|
522
|
-
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
|
+
);
|
|
523
625
|
return;
|
|
524
626
|
}
|
|
627
|
+
const parsedMsg = parsedMsgRes.value;
|
|
525
628
|
if (parsedMsg.seq !== this.ack) {
|
|
526
629
|
if (parsedMsg.seq < this.ack) {
|
|
527
630
|
this.log?.debug(
|
|
@@ -542,7 +645,7 @@ var SessionConnected = class extends IdentifiedSession {
|
|
|
542
645
|
code: SpanStatusCode.ERROR,
|
|
543
646
|
message: reason
|
|
544
647
|
});
|
|
545
|
-
this.
|
|
648
|
+
this.conn.close();
|
|
546
649
|
}
|
|
547
650
|
return;
|
|
548
651
|
}
|
|
@@ -560,9 +663,7 @@ var SessionConnected = class extends IdentifiedSession {
|
|
|
560
663
|
transportMessage: parsedMsg
|
|
561
664
|
});
|
|
562
665
|
if (!this.isActivelyHeartbeating) {
|
|
563
|
-
|
|
564
|
-
this.sendHeartbeat();
|
|
565
|
-
});
|
|
666
|
+
this.sendHeartbeat();
|
|
566
667
|
}
|
|
567
668
|
};
|
|
568
669
|
_handleStateExit() {
|
|
@@ -574,6 +675,10 @@ var SessionConnected = class extends IdentifiedSession {
|
|
|
574
675
|
clearInterval(this.heartbeatHandle);
|
|
575
676
|
this.heartbeatHandle = void 0;
|
|
576
677
|
}
|
|
678
|
+
if (this.heartbeatMissTimeout) {
|
|
679
|
+
clearTimeout(this.heartbeatMissTimeout);
|
|
680
|
+
this.heartbeatMissTimeout = void 0;
|
|
681
|
+
}
|
|
577
682
|
}
|
|
578
683
|
_handleClose() {
|
|
579
684
|
super._handleClose();
|
|
@@ -605,6 +710,62 @@ var SessionBackingOff = class extends IdentifiedSessionWithGracePeriod {
|
|
|
605
710
|
}
|
|
606
711
|
};
|
|
607
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
|
+
|
|
608
769
|
// transport/sessionStateMachine/transitions.ts
|
|
609
770
|
function inheritSharedSession(session) {
|
|
610
771
|
return {
|
|
@@ -619,7 +780,8 @@ function inheritSharedSession(session) {
|
|
|
619
780
|
options: session.options,
|
|
620
781
|
log: session.log,
|
|
621
782
|
tracer: session.tracer,
|
|
622
|
-
protocolVersion: session.protocolVersion
|
|
783
|
+
protocolVersion: session.protocolVersion,
|
|
784
|
+
codec: session.codec
|
|
623
785
|
};
|
|
624
786
|
}
|
|
625
787
|
function inheritSharedSessionWithGrace(session) {
|
|
@@ -648,7 +810,8 @@ var SessionStateGraph = {
|
|
|
648
810
|
options,
|
|
649
811
|
protocolVersion,
|
|
650
812
|
tracer,
|
|
651
|
-
log
|
|
813
|
+
log,
|
|
814
|
+
codec: new CodecMessageAdapter(options.codec)
|
|
652
815
|
});
|
|
653
816
|
session.log?.info(`session ${session.id} created in NoConnection state`, {
|
|
654
817
|
...session.loggingMetadata,
|
|
@@ -663,7 +826,8 @@ var SessionStateGraph = {
|
|
|
663
826
|
from,
|
|
664
827
|
options,
|
|
665
828
|
tracer,
|
|
666
|
-
log
|
|
829
|
+
log,
|
|
830
|
+
codec: new CodecMessageAdapter(options.codec)
|
|
667
831
|
});
|
|
668
832
|
session.log?.info(`session created in WaitingForHandshake state`, {
|
|
669
833
|
...session.loggingMetadata,
|
|
@@ -741,6 +905,7 @@ var SessionStateGraph = {
|
|
|
741
905
|
listeners,
|
|
742
906
|
...carriedState
|
|
743
907
|
});
|
|
908
|
+
session.startMissingHeartbeatTimeout();
|
|
744
909
|
session.log?.info(
|
|
745
910
|
`session ${session.id} transition from Handshaking to Connected`,
|
|
746
911
|
{
|
|
@@ -776,7 +941,8 @@ var SessionStateGraph = {
|
|
|
776
941
|
options,
|
|
777
942
|
tracer: pendingSession.tracer,
|
|
778
943
|
log: pendingSession.log,
|
|
779
|
-
protocolVersion
|
|
944
|
+
protocolVersion,
|
|
945
|
+
codec: new CodecMessageAdapter(options.codec)
|
|
780
946
|
}
|
|
781
947
|
);
|
|
782
948
|
pendingSession._handleStateExit();
|
|
@@ -786,6 +952,7 @@ var SessionStateGraph = {
|
|
|
786
952
|
listeners,
|
|
787
953
|
...carriedState
|
|
788
954
|
});
|
|
955
|
+
session.startMissingHeartbeatTimeout();
|
|
789
956
|
conn.telemetry = createConnectionTelemetryInfo(
|
|
790
957
|
session.tracer,
|
|
791
958
|
conn,
|
|
@@ -916,44 +1083,6 @@ var ServerSessionStateGraph = {
|
|
|
916
1083
|
}
|
|
917
1084
|
};
|
|
918
1085
|
|
|
919
|
-
// transport/events.ts
|
|
920
|
-
var ProtocolError = {
|
|
921
|
-
RetriesExceeded: "conn_retry_exceeded",
|
|
922
|
-
HandshakeFailed: "handshake_failed",
|
|
923
|
-
MessageOrderingViolated: "message_ordering_violated",
|
|
924
|
-
InvalidMessage: "invalid_message"
|
|
925
|
-
};
|
|
926
|
-
var EventDispatcher = class {
|
|
927
|
-
eventListeners = {};
|
|
928
|
-
removeAllListeners() {
|
|
929
|
-
this.eventListeners = {};
|
|
930
|
-
}
|
|
931
|
-
numberOfListeners(eventType) {
|
|
932
|
-
return this.eventListeners[eventType]?.size ?? 0;
|
|
933
|
-
}
|
|
934
|
-
addEventListener(eventType, handler) {
|
|
935
|
-
if (!this.eventListeners[eventType]) {
|
|
936
|
-
this.eventListeners[eventType] = /* @__PURE__ */ new Set();
|
|
937
|
-
}
|
|
938
|
-
this.eventListeners[eventType]?.add(handler);
|
|
939
|
-
}
|
|
940
|
-
removeEventListener(eventType, handler) {
|
|
941
|
-
const handlers = this.eventListeners[eventType];
|
|
942
|
-
if (handlers) {
|
|
943
|
-
this.eventListeners[eventType]?.delete(handler);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
dispatchEvent(eventType, event) {
|
|
947
|
-
const handlers = this.eventListeners[eventType];
|
|
948
|
-
if (handlers) {
|
|
949
|
-
const copy = [...handlers];
|
|
950
|
-
for (const handler of copy) {
|
|
951
|
-
handler(event);
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
};
|
|
956
|
-
|
|
957
1086
|
// transport/transport.ts
|
|
958
1087
|
var Transport = class {
|
|
959
1088
|
/**
|
|
@@ -1164,13 +1293,886 @@ var Transport = class {
|
|
|
1164
1293
|
);
|
|
1165
1294
|
}
|
|
1166
1295
|
const sameSession = session.id === sessionId;
|
|
1167
|
-
if (!sameSession) {
|
|
1296
|
+
if (!sameSession || session._isConsumed) {
|
|
1168
1297
|
throw new Error(
|
|
1169
1298
|
`session scope for ${sessionId} has ended (transition), can't send`
|
|
1170
1299
|
);
|
|
1171
1300
|
}
|
|
1172
|
-
|
|
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;
|
|
1173
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
|
|
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();
|
|
1174
2176
|
}
|
|
1175
2177
|
};
|
|
1176
2178
|
|
|
@@ -1262,15 +2264,17 @@ var Connection = class {
|
|
|
1262
2264
|
};
|
|
1263
2265
|
|
|
1264
2266
|
export {
|
|
2267
|
+
BinaryCodec,
|
|
2268
|
+
NaiveJsonCodec,
|
|
2269
|
+
ProtocolError,
|
|
1265
2270
|
defaultTransportOptions,
|
|
1266
2271
|
defaultClientTransportOptions,
|
|
1267
|
-
defaultServerTransportOptions,
|
|
1268
2272
|
SessionState,
|
|
1269
2273
|
SessionStateGraph,
|
|
1270
|
-
ClientSessionStateGraph,
|
|
1271
|
-
ServerSessionStateGraph,
|
|
1272
|
-
ProtocolError,
|
|
1273
2274
|
Transport,
|
|
1274
|
-
|
|
2275
|
+
ClientTransport,
|
|
2276
|
+
ServerTransport,
|
|
2277
|
+
Connection,
|
|
2278
|
+
CodecMessageAdapter
|
|
1275
2279
|
};
|
|
1276
|
-
//# sourceMappingURL=chunk-
|
|
2280
|
+
//# sourceMappingURL=chunk-BO7MFCO6.js.map
|