@replit/river 0.200.0-rc.2 → 0.200.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -20
- package/dist/chunk-2BF4VMUZ.js +50 -0
- package/dist/chunk-2BF4VMUZ.js.map +1 -0
- package/dist/chunk-BZQQXMVF.js +401 -0
- package/dist/chunk-BZQQXMVF.js.map +1 -0
- package/dist/{chunk-4VNY34QG.js → chunk-F7Z2QQRL.js} +24 -18
- package/dist/chunk-F7Z2QQRL.js.map +1 -0
- package/dist/chunk-IMFMNIEO.js +384 -0
- package/dist/chunk-IMFMNIEO.js.map +1 -0
- package/dist/chunk-RDGHFHXN.js +658 -0
- package/dist/chunk-RDGHFHXN.js.map +1 -0
- package/dist/chunk-SI4YHBTI.js +277 -0
- package/dist/chunk-SI4YHBTI.js.map +1 -0
- package/dist/{chunk-7CKIN3JT.js → chunk-SIRRYRLQ.js} +73 -490
- package/dist/chunk-SIRRYRLQ.js.map +1 -0
- package/dist/{chunk-S5RL45KH.js → chunk-V57VWV5S.js} +80 -44
- package/dist/chunk-V57VWV5S.js.map +1 -0
- package/dist/{chunk-QMM35C3H.js → chunk-VXYHC666.js} +1 -1
- package/dist/chunk-VXYHC666.js.map +1 -0
- package/dist/client-f56a6da3.d.ts +49 -0
- package/dist/{connection-f900e390.d.ts → connection-11991b13.d.ts} +1 -5
- package/dist/connection-6031a354.d.ts +11 -0
- package/dist/context-73df8978.d.ts +528 -0
- package/dist/logging/index.cjs.map +1 -1
- package/dist/logging/index.d.cts +1 -1
- package/dist/logging/index.d.ts +1 -1
- package/dist/logging/index.js +1 -1
- package/dist/{index-10ebd26a.d.ts → message-fd349b27.d.ts} +31 -31
- package/dist/router/index.cjs +125 -502
- package/dist/router/index.cjs.map +1 -1
- package/dist/router/index.d.cts +11 -46
- package/dist/router/index.d.ts +11 -46
- package/dist/router/index.js +2 -4
- package/dist/server-9f31d98f.d.ts +42 -0
- package/dist/{services-970f97bb.d.ts → services-c67758fc.d.ts} +5 -602
- package/dist/transport/impls/uds/client.cjs +1247 -1238
- package/dist/transport/impls/uds/client.cjs.map +1 -1
- package/dist/transport/impls/uds/client.d.cts +4 -3
- package/dist/transport/impls/uds/client.d.ts +4 -3
- package/dist/transport/impls/uds/client.js +7 -13
- package/dist/transport/impls/uds/client.js.map +1 -1
- package/dist/transport/impls/uds/server.cjs +1311 -1170
- package/dist/transport/impls/uds/server.cjs.map +1 -1
- package/dist/transport/impls/uds/server.d.cts +4 -4
- package/dist/transport/impls/uds/server.d.ts +4 -4
- package/dist/transport/impls/uds/server.js +6 -6
- package/dist/transport/impls/ws/client.cjs +988 -987
- package/dist/transport/impls/ws/client.cjs.map +1 -1
- package/dist/transport/impls/ws/client.d.cts +6 -5
- package/dist/transport/impls/ws/client.d.ts +6 -5
- package/dist/transport/impls/ws/client.js +6 -7
- package/dist/transport/impls/ws/client.js.map +1 -1
- package/dist/transport/impls/ws/server.cjs +1192 -1066
- package/dist/transport/impls/ws/server.cjs.map +1 -1
- package/dist/transport/impls/ws/server.d.cts +4 -4
- package/dist/transport/impls/ws/server.d.ts +4 -4
- package/dist/transport/impls/ws/server.js +6 -6
- package/dist/transport/index.cjs +1446 -1380
- package/dist/transport/index.cjs.map +1 -1
- package/dist/transport/index.d.cts +4 -26
- package/dist/transport/index.d.ts +4 -26
- package/dist/transport/index.js +9 -9
- package/dist/util/testHelpers.cjs +746 -303
- package/dist/util/testHelpers.cjs.map +1 -1
- package/dist/util/testHelpers.d.cts +9 -4
- package/dist/util/testHelpers.d.ts +9 -4
- package/dist/util/testHelpers.js +36 -10
- package/dist/util/testHelpers.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-47TFNAY2.js +0 -476
- package/dist/chunk-47TFNAY2.js.map +0 -1
- package/dist/chunk-4VNY34QG.js.map +0 -1
- package/dist/chunk-7CKIN3JT.js.map +0 -1
- package/dist/chunk-CZP4LK3F.js +0 -335
- package/dist/chunk-CZP4LK3F.js.map +0 -1
- package/dist/chunk-DJCW3SKT.js +0 -59
- package/dist/chunk-DJCW3SKT.js.map +0 -1
- package/dist/chunk-NQWDT6GS.js +0 -347
- package/dist/chunk-NQWDT6GS.js.map +0 -1
- package/dist/chunk-ONUXWVRC.js +0 -492
- package/dist/chunk-ONUXWVRC.js.map +0 -1
- package/dist/chunk-QMM35C3H.js.map +0 -1
- package/dist/chunk-S5RL45KH.js.map +0 -1
- package/dist/connection-3f117047.d.ts +0 -17
package/dist/transport/index.cjs
CHANGED
|
@@ -25,18 +25,189 @@ __export(transport_exports, {
|
|
|
25
25
|
OpaqueTransportMessageSchema: () => OpaqueTransportMessageSchema,
|
|
26
26
|
ProtocolError: () => ProtocolError,
|
|
27
27
|
ServerTransport: () => ServerTransport,
|
|
28
|
-
|
|
28
|
+
SessionState: () => SessionState,
|
|
29
29
|
Transport: () => Transport,
|
|
30
30
|
TransportMessageSchema: () => TransportMessageSchema
|
|
31
31
|
});
|
|
32
32
|
module.exports = __toCommonJS(transport_exports);
|
|
33
33
|
|
|
34
|
-
//
|
|
35
|
-
var
|
|
34
|
+
// logging/log.ts
|
|
35
|
+
var LoggingLevels = {
|
|
36
|
+
debug: -1,
|
|
37
|
+
info: 0,
|
|
38
|
+
warn: 1,
|
|
39
|
+
error: 2
|
|
40
|
+
};
|
|
41
|
+
var cleanedLogFn = (log) => {
|
|
42
|
+
return (msg, metadata) => {
|
|
43
|
+
if (!metadata?.transportMessage) {
|
|
44
|
+
log(msg, metadata);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const { payload, ...rest } = metadata.transportMessage;
|
|
48
|
+
metadata.transportMessage = rest;
|
|
49
|
+
log(msg, metadata);
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
var BaseLogger = class {
|
|
53
|
+
minLevel;
|
|
54
|
+
output;
|
|
55
|
+
constructor(output, minLevel = "info") {
|
|
56
|
+
this.minLevel = minLevel;
|
|
57
|
+
this.output = output;
|
|
58
|
+
}
|
|
59
|
+
debug(msg, metadata) {
|
|
60
|
+
if (LoggingLevels[this.minLevel] <= LoggingLevels.debug) {
|
|
61
|
+
this.output(msg, metadata ?? {}, "debug");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
info(msg, metadata) {
|
|
65
|
+
if (LoggingLevels[this.minLevel] <= LoggingLevels.info) {
|
|
66
|
+
this.output(msg, metadata ?? {}, "info");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
warn(msg, metadata) {
|
|
70
|
+
if (LoggingLevels[this.minLevel] <= LoggingLevels.warn) {
|
|
71
|
+
this.output(msg, metadata ?? {}, "warn");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
error(msg, metadata) {
|
|
75
|
+
if (LoggingLevels[this.minLevel] <= LoggingLevels.error) {
|
|
76
|
+
this.output(msg, metadata ?? {}, "error");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var createLogProxy = (log) => ({
|
|
81
|
+
debug: cleanedLogFn(log.debug.bind(log)),
|
|
82
|
+
info: cleanedLogFn(log.info.bind(log)),
|
|
83
|
+
warn: cleanedLogFn(log.warn.bind(log)),
|
|
84
|
+
error: cleanedLogFn(log.error.bind(log))
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// transport/events.ts
|
|
88
|
+
var ProtocolError = {
|
|
89
|
+
RetriesExceeded: "conn_retry_exceeded",
|
|
90
|
+
HandshakeFailed: "handshake_failed",
|
|
91
|
+
MessageOrderingViolated: "message_ordering_violated"
|
|
92
|
+
};
|
|
93
|
+
var EventDispatcher = class {
|
|
94
|
+
eventListeners = {};
|
|
95
|
+
removeAllListeners() {
|
|
96
|
+
this.eventListeners = {};
|
|
97
|
+
}
|
|
98
|
+
numberOfListeners(eventType) {
|
|
99
|
+
return this.eventListeners[eventType]?.size ?? 0;
|
|
100
|
+
}
|
|
101
|
+
addEventListener(eventType, handler) {
|
|
102
|
+
if (!this.eventListeners[eventType]) {
|
|
103
|
+
this.eventListeners[eventType] = /* @__PURE__ */ new Set();
|
|
104
|
+
}
|
|
105
|
+
this.eventListeners[eventType]?.add(handler);
|
|
106
|
+
}
|
|
107
|
+
removeEventListener(eventType, handler) {
|
|
108
|
+
const handlers = this.eventListeners[eventType];
|
|
109
|
+
if (handlers) {
|
|
110
|
+
this.eventListeners[eventType]?.delete(handler);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
dispatchEvent(eventType, event) {
|
|
114
|
+
const handlers = this.eventListeners[eventType];
|
|
115
|
+
if (handlers) {
|
|
116
|
+
const copy = [...handlers];
|
|
117
|
+
for (const handler of copy) {
|
|
118
|
+
handler(event);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// codec/json.ts
|
|
125
|
+
var encoder = new TextEncoder();
|
|
126
|
+
var decoder = new TextDecoder();
|
|
127
|
+
function uint8ArrayToBase64(uint8Array) {
|
|
128
|
+
let binary = "";
|
|
129
|
+
uint8Array.forEach((byte) => {
|
|
130
|
+
binary += String.fromCharCode(byte);
|
|
131
|
+
});
|
|
132
|
+
return btoa(binary);
|
|
133
|
+
}
|
|
134
|
+
function base64ToUint8Array(base64) {
|
|
135
|
+
const binaryString = atob(base64);
|
|
136
|
+
const uint8Array = new Uint8Array(binaryString.length);
|
|
137
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
138
|
+
uint8Array[i] = binaryString.charCodeAt(i);
|
|
139
|
+
}
|
|
140
|
+
return uint8Array;
|
|
141
|
+
}
|
|
142
|
+
var NaiveJsonCodec = {
|
|
143
|
+
toBuffer: (obj) => {
|
|
144
|
+
return encoder.encode(
|
|
145
|
+
JSON.stringify(obj, function replacer(key) {
|
|
146
|
+
const val = this[key];
|
|
147
|
+
if (val instanceof Uint8Array) {
|
|
148
|
+
return { $t: uint8ArrayToBase64(val) };
|
|
149
|
+
} else {
|
|
150
|
+
return val;
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
},
|
|
155
|
+
fromBuffer: (buff) => {
|
|
156
|
+
try {
|
|
157
|
+
const parsed = JSON.parse(
|
|
158
|
+
decoder.decode(buff),
|
|
159
|
+
function reviver(_key, val) {
|
|
160
|
+
if (val?.$t) {
|
|
161
|
+
return base64ToUint8Array(val.$t);
|
|
162
|
+
} else {
|
|
163
|
+
return val;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
if (typeof parsed === "object")
|
|
168
|
+
return parsed;
|
|
169
|
+
return null;
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// transport/options.ts
|
|
177
|
+
var defaultTransportOptions = {
|
|
178
|
+
heartbeatIntervalMs: 1e3,
|
|
179
|
+
heartbeatsUntilDead: 2,
|
|
180
|
+
sessionDisconnectGraceMs: 5e3,
|
|
181
|
+
connectionTimeoutMs: 2e3,
|
|
182
|
+
handshakeTimeoutMs: 1e3,
|
|
183
|
+
codec: NaiveJsonCodec
|
|
184
|
+
};
|
|
185
|
+
var defaultConnectionRetryOptions = {
|
|
186
|
+
baseIntervalMs: 250,
|
|
187
|
+
maxJitterMs: 200,
|
|
188
|
+
maxBackoffMs: 32e3,
|
|
189
|
+
attemptBudgetCapacity: 5,
|
|
190
|
+
budgetRestoreIntervalMs: 200
|
|
191
|
+
};
|
|
192
|
+
var defaultClientTransportOptions = {
|
|
193
|
+
...defaultTransportOptions,
|
|
194
|
+
...defaultConnectionRetryOptions
|
|
195
|
+
};
|
|
196
|
+
var defaultServerTransportOptions = {
|
|
197
|
+
...defaultTransportOptions
|
|
198
|
+
};
|
|
36
199
|
|
|
37
200
|
// transport/message.ts
|
|
38
201
|
var import_typebox = require("@sinclair/typebox");
|
|
202
|
+
|
|
203
|
+
// transport/id.ts
|
|
39
204
|
var import_nanoid = require("nanoid");
|
|
205
|
+
var alphabet = (0, import_nanoid.customAlphabet)(
|
|
206
|
+
"1234567890abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ"
|
|
207
|
+
);
|
|
208
|
+
var generateId = () => alphabet(12);
|
|
209
|
+
|
|
210
|
+
// transport/message.ts
|
|
40
211
|
var TransportMessageSchema = (t) => import_typebox.Type.Object({
|
|
41
212
|
id: import_typebox.Type.String(),
|
|
42
213
|
from: import_typebox.Type.String(),
|
|
@@ -61,7 +232,8 @@ var ControlMessageAckSchema = import_typebox.Type.Object({
|
|
|
61
232
|
var ControlMessageCloseSchema = import_typebox.Type.Object({
|
|
62
233
|
type: import_typebox.Type.Literal("CLOSE")
|
|
63
234
|
});
|
|
64
|
-
var
|
|
235
|
+
var currentProtocolVersion = "v2.0";
|
|
236
|
+
var acceptedProtocolVersions = ["v1.1", currentProtocolVersion];
|
|
65
237
|
var ControlMessageHandshakeRequestSchema = import_typebox.Type.Object({
|
|
66
238
|
type: import_typebox.Type.Literal("HANDSHAKE_REQ"),
|
|
67
239
|
protocolVersion: import_typebox.Type.String(),
|
|
@@ -71,18 +243,29 @@ var ControlMessageHandshakeRequestSchema = import_typebox.Type.Object({
|
|
|
71
243
|
* used by the server to know whether this is a new or a reestablished connection, and whether it
|
|
72
244
|
* is compatible with what it already has.
|
|
73
245
|
*/
|
|
74
|
-
expectedSessionState: import_typebox.Type.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
})
|
|
83
|
-
),
|
|
246
|
+
expectedSessionState: import_typebox.Type.Object({
|
|
247
|
+
// what the client expects the server to send next
|
|
248
|
+
nextExpectedSeq: import_typebox.Type.Integer(),
|
|
249
|
+
// TODO: remove optional once we know all servers
|
|
250
|
+
// are nextSentSeq here
|
|
251
|
+
// what the server expects the client to send next
|
|
252
|
+
nextSentSeq: import_typebox.Type.Optional(import_typebox.Type.Integer())
|
|
253
|
+
}),
|
|
84
254
|
metadata: import_typebox.Type.Optional(import_typebox.Type.Unknown())
|
|
85
255
|
});
|
|
256
|
+
var HandshakeErrorRetriableResponseCodes = import_typebox.Type.Union([
|
|
257
|
+
import_typebox.Type.Literal("SESSION_STATE_MISMATCH")
|
|
258
|
+
]);
|
|
259
|
+
var HandshakeErrorFatalResponseCodes = import_typebox.Type.Union([
|
|
260
|
+
import_typebox.Type.Literal("MALFORMED_HANDSHAKE_META"),
|
|
261
|
+
import_typebox.Type.Literal("MALFORMED_HANDSHAKE"),
|
|
262
|
+
import_typebox.Type.Literal("PROTOCOL_VERSION_MISMATCH"),
|
|
263
|
+
import_typebox.Type.Literal("REJECTED_BY_CUSTOM_HANDLER")
|
|
264
|
+
]);
|
|
265
|
+
var HandshakeErrorResponseCodes = import_typebox.Type.Union([
|
|
266
|
+
HandshakeErrorRetriableResponseCodes,
|
|
267
|
+
HandshakeErrorFatalResponseCodes
|
|
268
|
+
]);
|
|
86
269
|
var ControlMessageHandshakeResponseSchema = import_typebox.Type.Object({
|
|
87
270
|
type: import_typebox.Type.Literal("HANDSHAKE_RESP"),
|
|
88
271
|
status: import_typebox.Type.Union([
|
|
@@ -92,7 +275,10 @@ var ControlMessageHandshakeResponseSchema = import_typebox.Type.Object({
|
|
|
92
275
|
}),
|
|
93
276
|
import_typebox.Type.Object({
|
|
94
277
|
ok: import_typebox.Type.Literal(false),
|
|
95
|
-
reason: import_typebox.Type.String()
|
|
278
|
+
reason: import_typebox.Type.String(),
|
|
279
|
+
// TODO: remove optional once we know all servers
|
|
280
|
+
// are sending code here
|
|
281
|
+
code: import_typebox.Type.Optional(HandshakeErrorResponseCodes)
|
|
96
282
|
})
|
|
97
283
|
])
|
|
98
284
|
});
|
|
@@ -114,36 +300,35 @@ function handshakeRequestMessage({
|
|
|
114
300
|
tracing
|
|
115
301
|
}) {
|
|
116
302
|
return {
|
|
117
|
-
id: (
|
|
303
|
+
id: generateId(),
|
|
118
304
|
from,
|
|
119
305
|
to,
|
|
120
306
|
seq: 0,
|
|
121
307
|
ack: 0,
|
|
122
|
-
streamId: (
|
|
308
|
+
streamId: generateId(),
|
|
123
309
|
controlFlags: 0,
|
|
124
310
|
tracing,
|
|
125
311
|
payload: {
|
|
126
312
|
type: "HANDSHAKE_REQ",
|
|
127
|
-
protocolVersion:
|
|
313
|
+
protocolVersion: currentProtocolVersion,
|
|
128
314
|
sessionId,
|
|
129
315
|
expectedSessionState,
|
|
130
316
|
metadata
|
|
131
317
|
}
|
|
132
318
|
};
|
|
133
319
|
}
|
|
134
|
-
var SESSION_STATE_MISMATCH = "session state mismatch";
|
|
135
320
|
function handshakeResponseMessage({
|
|
136
321
|
from,
|
|
137
322
|
to,
|
|
138
323
|
status
|
|
139
324
|
}) {
|
|
140
325
|
return {
|
|
141
|
-
id: (
|
|
326
|
+
id: generateId(),
|
|
142
327
|
from,
|
|
143
328
|
to,
|
|
144
329
|
seq: 0,
|
|
145
330
|
ack: 0,
|
|
146
|
-
streamId: (
|
|
331
|
+
streamId: generateId(),
|
|
147
332
|
controlFlags: 0,
|
|
148
333
|
payload: {
|
|
149
334
|
type: "HANDSHAKE_RESP",
|
|
@@ -155,104 +340,232 @@ function isAck(controlFlag) {
|
|
|
155
340
|
return (controlFlag & 1 /* AckBit */) === 1 /* AckBit */;
|
|
156
341
|
}
|
|
157
342
|
|
|
158
|
-
//
|
|
159
|
-
var
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
minLevel;
|
|
178
|
-
output;
|
|
179
|
-
constructor(output, minLevel = "info") {
|
|
180
|
-
this.minLevel = minLevel;
|
|
181
|
-
this.output = output;
|
|
343
|
+
// transport/sessionStateMachine/common.ts
|
|
344
|
+
var import_value = require("@sinclair/typebox/value");
|
|
345
|
+
var SessionState = /* @__PURE__ */ ((SessionState2) => {
|
|
346
|
+
SessionState2["NoConnection"] = "NoConnection";
|
|
347
|
+
SessionState2["Connecting"] = "Connecting";
|
|
348
|
+
SessionState2["Handshaking"] = "Handshaking";
|
|
349
|
+
SessionState2["Connected"] = "Connected";
|
|
350
|
+
SessionState2["WaitingForHandshake"] = "WaitingForHandshake";
|
|
351
|
+
return SessionState2;
|
|
352
|
+
})(SessionState || {});
|
|
353
|
+
var ERR_CONSUMED = `session state has been consumed and is no longer valid`;
|
|
354
|
+
var StateMachineState = class {
|
|
355
|
+
/*
|
|
356
|
+
* Whether this state has been consumed
|
|
357
|
+
* and we've moved on to another state
|
|
358
|
+
*/
|
|
359
|
+
_isConsumed;
|
|
360
|
+
close() {
|
|
361
|
+
this._handleClose();
|
|
182
362
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
363
|
+
constructor() {
|
|
364
|
+
this._isConsumed = false;
|
|
365
|
+
return new Proxy(this, {
|
|
366
|
+
get(target, prop) {
|
|
367
|
+
if (prop === "_isConsumed" || prop === "id" || prop === "state") {
|
|
368
|
+
return Reflect.get(target, prop);
|
|
369
|
+
}
|
|
370
|
+
if (prop === "_handleStateExit") {
|
|
371
|
+
return () => {
|
|
372
|
+
target._isConsumed = true;
|
|
373
|
+
target._handleStateExit();
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
if (prop === "_handleClose") {
|
|
377
|
+
return () => {
|
|
378
|
+
target._handleStateExit();
|
|
379
|
+
target._handleClose();
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
if (target._isConsumed) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
`${ERR_CONSUMED}: getting ${prop.toString()} on consumed state`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return Reflect.get(target, prop);
|
|
388
|
+
},
|
|
389
|
+
set(target, prop, value) {
|
|
390
|
+
if (target._isConsumed) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
`${ERR_CONSUMED}: setting ${prop.toString()} on consumed state`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
return Reflect.set(target, prop, value);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
187
398
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
399
|
+
};
|
|
400
|
+
var CommonSession = class extends StateMachineState {
|
|
401
|
+
from;
|
|
402
|
+
options;
|
|
403
|
+
log;
|
|
404
|
+
constructor(from, options, log) {
|
|
405
|
+
super();
|
|
406
|
+
this.from = from;
|
|
407
|
+
this.options = options;
|
|
408
|
+
this.log = log;
|
|
192
409
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
410
|
+
parseMsg(msg) {
|
|
411
|
+
const parsedMsg = this.options.codec.fromBuffer(msg);
|
|
412
|
+
if (parsedMsg === null) {
|
|
413
|
+
const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));
|
|
414
|
+
this.log?.error(
|
|
415
|
+
`received malformed msg: ${decodedBuffer}`,
|
|
416
|
+
this.loggingMetadata
|
|
417
|
+
);
|
|
418
|
+
return null;
|
|
196
419
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
420
|
+
if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
|
|
421
|
+
this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
|
|
422
|
+
...this.loggingMetadata,
|
|
423
|
+
validationErrors: [
|
|
424
|
+
...import_value.Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
|
|
425
|
+
]
|
|
426
|
+
});
|
|
427
|
+
return null;
|
|
201
428
|
}
|
|
429
|
+
return parsedMsg;
|
|
202
430
|
}
|
|
203
431
|
};
|
|
204
|
-
var
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
this.
|
|
432
|
+
var IdentifiedSession = class extends CommonSession {
|
|
433
|
+
id;
|
|
434
|
+
telemetry;
|
|
435
|
+
to;
|
|
436
|
+
protocolVersion;
|
|
437
|
+
/**
|
|
438
|
+
* Index of the message we will send next (excluding handshake)
|
|
439
|
+
*/
|
|
440
|
+
seq;
|
|
441
|
+
/**
|
|
442
|
+
* Number of unique messages we've received this session (excluding handshake)
|
|
443
|
+
*/
|
|
444
|
+
ack;
|
|
445
|
+
sendBuffer;
|
|
446
|
+
constructor(id, from, to, seq, ack, sendBuffer, telemetry, options, protocolVersion, log) {
|
|
447
|
+
super(from, options, log);
|
|
448
|
+
this.id = id;
|
|
449
|
+
this.to = to;
|
|
450
|
+
this.seq = seq;
|
|
451
|
+
this.ack = ack;
|
|
452
|
+
this.sendBuffer = sendBuffer;
|
|
453
|
+
this.telemetry = telemetry;
|
|
454
|
+
this.log = log;
|
|
455
|
+
this.protocolVersion = protocolVersion;
|
|
221
456
|
}
|
|
222
|
-
|
|
223
|
-
|
|
457
|
+
get loggingMetadata() {
|
|
458
|
+
const spanContext = this.telemetry.span.spanContext();
|
|
459
|
+
return {
|
|
460
|
+
clientId: this.from,
|
|
461
|
+
connectedTo: this.to,
|
|
462
|
+
sessionId: this.id,
|
|
463
|
+
telemetry: {
|
|
464
|
+
traceId: spanContext.traceId,
|
|
465
|
+
spanId: spanContext.spanId
|
|
466
|
+
}
|
|
467
|
+
};
|
|
224
468
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
469
|
+
constructMsg(partialMsg) {
|
|
470
|
+
const msg = {
|
|
471
|
+
...partialMsg,
|
|
472
|
+
id: generateId(),
|
|
473
|
+
to: this.to,
|
|
474
|
+
from: this.from,
|
|
475
|
+
seq: this.seq,
|
|
476
|
+
ack: this.ack
|
|
477
|
+
};
|
|
478
|
+
this.seq++;
|
|
479
|
+
return msg;
|
|
230
480
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (handlers) {
|
|
234
|
-
this.eventListeners[eventType]?.delete(handler);
|
|
235
|
-
}
|
|
481
|
+
nextSeq() {
|
|
482
|
+
return this.sendBuffer.length > 0 ? this.sendBuffer[0].seq : this.seq;
|
|
236
483
|
}
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
484
|
+
send(msg) {
|
|
485
|
+
const constructedMsg = this.constructMsg(msg);
|
|
486
|
+
this.sendBuffer.push(constructedMsg);
|
|
487
|
+
return constructedMsg.id;
|
|
488
|
+
}
|
|
489
|
+
_handleStateExit() {
|
|
490
|
+
}
|
|
491
|
+
_handleClose() {
|
|
492
|
+
this.sendBuffer.length = 0;
|
|
493
|
+
this.telemetry.span.end();
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// transport/sessionStateMachine/SessionConnecting.ts
|
|
498
|
+
var SessionConnecting = class extends IdentifiedSession {
|
|
499
|
+
state = "Connecting" /* Connecting */;
|
|
500
|
+
connPromise;
|
|
501
|
+
listeners;
|
|
502
|
+
connectionTimeout;
|
|
503
|
+
constructor(connPromise, listeners, ...args) {
|
|
504
|
+
super(...args);
|
|
505
|
+
this.connPromise = connPromise;
|
|
506
|
+
this.listeners = listeners;
|
|
507
|
+
this.connectionTimeout = setTimeout(() => {
|
|
508
|
+
listeners.onConnectionTimeout();
|
|
509
|
+
}, this.options.connectionTimeoutMs);
|
|
510
|
+
connPromise.then(
|
|
511
|
+
(conn) => {
|
|
512
|
+
if (this._isConsumed)
|
|
513
|
+
return;
|
|
514
|
+
listeners.onConnectionEstablished(conn);
|
|
515
|
+
},
|
|
516
|
+
(err) => {
|
|
517
|
+
if (this._isConsumed)
|
|
518
|
+
return;
|
|
519
|
+
listeners.onConnectionFailed(err);
|
|
243
520
|
}
|
|
244
|
-
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
// close a pending connection if it resolves, ignore errors if the promise
|
|
524
|
+
// ends up rejected anyways
|
|
525
|
+
bestEffortClose() {
|
|
526
|
+
void this.connPromise.then((conn) => conn.close()).catch(() => {
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
_handleStateExit() {
|
|
530
|
+
super._handleStateExit();
|
|
531
|
+
clearTimeout(this.connectionTimeout);
|
|
532
|
+
this.connectionTimeout = void 0;
|
|
533
|
+
}
|
|
534
|
+
_handleClose() {
|
|
535
|
+
this.bestEffortClose();
|
|
536
|
+
super._handleClose();
|
|
245
537
|
}
|
|
246
538
|
};
|
|
247
539
|
|
|
248
|
-
// transport/
|
|
249
|
-
var
|
|
540
|
+
// transport/sessionStateMachine/SessionNoConnection.ts
|
|
541
|
+
var SessionNoConnection = class extends IdentifiedSession {
|
|
542
|
+
state = "NoConnection" /* NoConnection */;
|
|
543
|
+
listeners;
|
|
544
|
+
gracePeriodTimeout;
|
|
545
|
+
constructor(listeners, ...args) {
|
|
546
|
+
super(...args);
|
|
547
|
+
this.listeners = listeners;
|
|
548
|
+
this.gracePeriodTimeout = setTimeout(() => {
|
|
549
|
+
this.listeners.onSessionGracePeriodElapsed();
|
|
550
|
+
}, this.options.sessionDisconnectGraceMs);
|
|
551
|
+
}
|
|
552
|
+
_handleClose() {
|
|
553
|
+
super._handleClose();
|
|
554
|
+
}
|
|
555
|
+
_handleStateExit() {
|
|
556
|
+
super._handleStateExit();
|
|
557
|
+
if (this.gracePeriodTimeout) {
|
|
558
|
+
clearTimeout(this.gracePeriodTimeout);
|
|
559
|
+
this.gracePeriodTimeout = void 0;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
};
|
|
250
563
|
|
|
251
564
|
// tracing/index.ts
|
|
252
565
|
var import_api = require("@opentelemetry/api");
|
|
253
566
|
|
|
254
567
|
// package.json
|
|
255
|
-
var version = "0.200.0-rc.
|
|
568
|
+
var version = "0.200.0-rc.4";
|
|
256
569
|
|
|
257
570
|
// tracing/index.ts
|
|
258
571
|
function getPropagationContext(ctx) {
|
|
@@ -263,16 +576,16 @@ function getPropagationContext(ctx) {
|
|
|
263
576
|
import_api.propagation.inject(ctx, tracing);
|
|
264
577
|
return tracing;
|
|
265
578
|
}
|
|
266
|
-
function createSessionTelemetryInfo(
|
|
579
|
+
function createSessionTelemetryInfo(sessionId, to, from, propagationCtx) {
|
|
267
580
|
const parentCtx = propagationCtx ? import_api.propagation.extract(import_api.context.active(), propagationCtx) : import_api.context.active();
|
|
268
581
|
const span = tracer.startSpan(
|
|
269
|
-
`session ${
|
|
582
|
+
`session ${sessionId}`,
|
|
270
583
|
{
|
|
271
584
|
attributes: {
|
|
272
585
|
component: "river",
|
|
273
|
-
"river.session.id":
|
|
274
|
-
"river.session.to":
|
|
275
|
-
"river.session.from":
|
|
586
|
+
"river.session.id": sessionId,
|
|
587
|
+
"river.session.to": to,
|
|
588
|
+
"river.session.from": from
|
|
276
589
|
}
|
|
277
590
|
},
|
|
278
591
|
parentCtx
|
|
@@ -280,173 +593,155 @@ function createSessionTelemetryInfo(session, propagationCtx) {
|
|
|
280
593
|
const ctx = import_api.trace.setSpan(parentCtx, span);
|
|
281
594
|
return { span, ctx };
|
|
282
595
|
}
|
|
283
|
-
function createConnectionTelemetryInfo(connection, info) {
|
|
284
|
-
const span = tracer.startSpan(
|
|
285
|
-
`connection ${connection.id}`,
|
|
286
|
-
{
|
|
287
|
-
attributes: {
|
|
288
|
-
component: "river",
|
|
289
|
-
"river.connection.id": connection.id
|
|
290
|
-
},
|
|
291
|
-
links: [{ context: info.span.spanContext() }]
|
|
292
|
-
},
|
|
293
|
-
info.ctx
|
|
294
|
-
);
|
|
295
|
-
const ctx = import_api.trace.setSpan(info.ctx, span);
|
|
296
|
-
return { span, ctx };
|
|
297
|
-
}
|
|
298
596
|
var tracer = import_api.trace.getTracer("river", version);
|
|
299
597
|
var tracing_default = tracer;
|
|
300
598
|
|
|
301
|
-
// transport/
|
|
302
|
-
var
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
this.
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
599
|
+
// transport/sessionStateMachine/SessionWaitingForHandshake.ts
|
|
600
|
+
var SessionWaitingForHandshake = class extends CommonSession {
|
|
601
|
+
state = "WaitingForHandshake" /* WaitingForHandshake */;
|
|
602
|
+
conn;
|
|
603
|
+
listeners;
|
|
604
|
+
handshakeTimeout;
|
|
605
|
+
constructor(conn, listeners, ...args) {
|
|
606
|
+
super(...args);
|
|
607
|
+
this.conn = conn;
|
|
608
|
+
this.listeners = listeners;
|
|
609
|
+
this.handshakeTimeout = setTimeout(() => {
|
|
610
|
+
listeners.onHandshakeTimeout();
|
|
611
|
+
}, this.options.handshakeTimeoutMs);
|
|
612
|
+
this.conn.addDataListener(this.onHandshakeData);
|
|
613
|
+
this.conn.addErrorListener(listeners.onConnectionErrored);
|
|
614
|
+
this.conn.addCloseListener(listeners.onConnectionClosed);
|
|
615
|
+
}
|
|
616
|
+
onHandshakeData = (msg) => {
|
|
617
|
+
const parsedMsg = this.parseMsg(msg);
|
|
618
|
+
if (parsedMsg === null) {
|
|
619
|
+
this.listeners.onInvalidHandshake("could not parse message");
|
|
620
|
+
return;
|
|
319
621
|
}
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
};
|
|
323
|
-
var Session = class {
|
|
324
|
-
codec;
|
|
325
|
-
options;
|
|
326
|
-
telemetry;
|
|
327
|
-
/**
|
|
328
|
-
* The buffer of messages that have been sent but not yet acknowledged.
|
|
329
|
-
*/
|
|
330
|
-
sendBuffer = [];
|
|
331
|
-
/**
|
|
332
|
-
* The active connection associated with this session
|
|
333
|
-
*/
|
|
334
|
-
connection;
|
|
335
|
-
/**
|
|
336
|
-
* A connection that is currently undergoing handshaking. Used to distinguish between the active
|
|
337
|
-
* connection, but still be able to close it if needed.
|
|
338
|
-
*/
|
|
339
|
-
handshakingConnection;
|
|
340
|
-
from;
|
|
341
|
-
to;
|
|
342
|
-
/**
|
|
343
|
-
* The unique ID of this session.
|
|
344
|
-
*/
|
|
345
|
-
id;
|
|
346
|
-
/**
|
|
347
|
-
* What the other side advertised as their session ID
|
|
348
|
-
* for this session.
|
|
349
|
-
*/
|
|
350
|
-
advertisedSessionId;
|
|
351
|
-
/**
|
|
352
|
-
* Number of messages we've sent along this session (excluding handshake and acks)
|
|
353
|
-
*/
|
|
354
|
-
seq = 0;
|
|
355
|
-
/**
|
|
356
|
-
* Number of unique messages we've received this session (excluding handshake and acks)
|
|
357
|
-
*/
|
|
358
|
-
ack = 0;
|
|
359
|
-
/**
|
|
360
|
-
* The grace period between when the inner connection is disconnected
|
|
361
|
-
* and when we should consider the entire session disconnected.
|
|
362
|
-
*/
|
|
363
|
-
disconnectionGrace;
|
|
364
|
-
/**
|
|
365
|
-
* Number of heartbeats we've sent without a response.
|
|
366
|
-
*/
|
|
367
|
-
heartbeatMisses;
|
|
368
|
-
/**
|
|
369
|
-
* The interval for sending heartbeats.
|
|
370
|
-
*/
|
|
371
|
-
heartbeat;
|
|
372
|
-
log;
|
|
373
|
-
constructor(conn, from, to, options, propagationCtx) {
|
|
374
|
-
this.id = `session-${nanoid2(12)}`;
|
|
375
|
-
this.options = options;
|
|
376
|
-
this.from = from;
|
|
377
|
-
this.to = to;
|
|
378
|
-
this.connection = conn;
|
|
379
|
-
this.codec = options.codec;
|
|
380
|
-
this.heartbeatMisses = 0;
|
|
381
|
-
this.heartbeat = setInterval(
|
|
382
|
-
() => this.sendHeartbeat(),
|
|
383
|
-
options.heartbeatIntervalMs
|
|
384
|
-
);
|
|
385
|
-
this.telemetry = createSessionTelemetryInfo(this, propagationCtx);
|
|
386
|
-
}
|
|
387
|
-
bindLogger(log) {
|
|
388
|
-
this.log = log;
|
|
389
|
-
}
|
|
622
|
+
this.listeners.onHandshake(parsedMsg);
|
|
623
|
+
};
|
|
390
624
|
get loggingMetadata() {
|
|
391
|
-
const spanContext = this.telemetry.span.spanContext();
|
|
392
625
|
return {
|
|
393
626
|
clientId: this.from,
|
|
394
|
-
|
|
395
|
-
sessionId: this.id,
|
|
396
|
-
connId: this.connection?.id,
|
|
397
|
-
telemetry: {
|
|
398
|
-
traceId: spanContext.traceId,
|
|
399
|
-
spanId: spanContext.spanId
|
|
400
|
-
}
|
|
627
|
+
connId: this.conn.id
|
|
401
628
|
};
|
|
402
629
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
630
|
+
sendHandshake(msg) {
|
|
631
|
+
return this.conn.send(this.options.codec.toBuffer(msg));
|
|
632
|
+
}
|
|
633
|
+
_handleStateExit() {
|
|
634
|
+
this.conn.removeDataListener(this.onHandshakeData);
|
|
635
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
636
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
637
|
+
clearTimeout(this.handshakeTimeout);
|
|
638
|
+
this.handshakeTimeout = void 0;
|
|
639
|
+
}
|
|
640
|
+
_handleClose() {
|
|
641
|
+
this.conn.close();
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
// transport/sessionStateMachine/SessionHandshaking.ts
|
|
646
|
+
var SessionHandshaking = class extends IdentifiedSession {
|
|
647
|
+
state = "Handshaking" /* Handshaking */;
|
|
648
|
+
conn;
|
|
649
|
+
listeners;
|
|
650
|
+
handshakeTimeout;
|
|
651
|
+
constructor(conn, listeners, ...args) {
|
|
652
|
+
super(...args);
|
|
653
|
+
this.conn = conn;
|
|
654
|
+
this.listeners = listeners;
|
|
655
|
+
this.handshakeTimeout = setTimeout(() => {
|
|
656
|
+
listeners.onHandshakeTimeout();
|
|
657
|
+
}, this.options.handshakeTimeoutMs);
|
|
658
|
+
this.conn.addDataListener(this.onHandshakeData);
|
|
659
|
+
this.conn.addErrorListener(listeners.onConnectionErrored);
|
|
660
|
+
this.conn.addCloseListener(listeners.onConnectionClosed);
|
|
661
|
+
}
|
|
662
|
+
onHandshakeData = (msg) => {
|
|
663
|
+
const parsedMsg = this.parseMsg(msg);
|
|
664
|
+
if (parsedMsg === null) {
|
|
665
|
+
this.listeners.onInvalidHandshake("could not parse message");
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
this.listeners.onHandshake(parsedMsg);
|
|
669
|
+
};
|
|
670
|
+
sendHandshake(msg) {
|
|
671
|
+
return this.conn.send(this.options.codec.toBuffer(msg));
|
|
672
|
+
}
|
|
673
|
+
_handleStateExit() {
|
|
674
|
+
super._handleStateExit();
|
|
675
|
+
this.conn.removeDataListener(this.onHandshakeData);
|
|
676
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
677
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
678
|
+
clearTimeout(this.handshakeTimeout);
|
|
679
|
+
}
|
|
680
|
+
_handleClose() {
|
|
681
|
+
super._handleClose();
|
|
682
|
+
this.conn.close();
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
// transport/sessionStateMachine/SessionConnected.ts
|
|
687
|
+
var import_api2 = require("@opentelemetry/api");
|
|
688
|
+
var SessionConnected = class extends IdentifiedSession {
|
|
689
|
+
state = "Connected" /* Connected */;
|
|
690
|
+
conn;
|
|
691
|
+
listeners;
|
|
692
|
+
heartbeatHandle;
|
|
693
|
+
heartbeatMisses = 0;
|
|
694
|
+
get isActivelyHeartbeating() {
|
|
695
|
+
return this.heartbeatHandle !== void 0;
|
|
696
|
+
}
|
|
697
|
+
updateBookkeeping(ack, seq) {
|
|
698
|
+
this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
|
|
699
|
+
this.ack = seq + 1;
|
|
700
|
+
this.heartbeatMisses = 0;
|
|
701
|
+
}
|
|
411
702
|
send(msg) {
|
|
412
|
-
const
|
|
413
|
-
this.
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
transportMessage: fullMsg
|
|
426
|
-
}
|
|
427
|
-
);
|
|
428
|
-
} else {
|
|
703
|
+
const constructedMsg = this.constructMsg(msg);
|
|
704
|
+
this.sendBuffer.push(constructedMsg);
|
|
705
|
+
this.conn.send(this.options.codec.toBuffer(constructedMsg));
|
|
706
|
+
return constructedMsg.id;
|
|
707
|
+
}
|
|
708
|
+
constructor(conn, listeners, ...args) {
|
|
709
|
+
super(...args);
|
|
710
|
+
this.conn = conn;
|
|
711
|
+
this.listeners = listeners;
|
|
712
|
+
this.conn.addDataListener(this.onMessageData);
|
|
713
|
+
this.conn.addCloseListener(listeners.onConnectionClosed);
|
|
714
|
+
this.conn.addErrorListener(listeners.onConnectionErrored);
|
|
715
|
+
if (this.sendBuffer.length > 0) {
|
|
429
716
|
this.log?.debug(
|
|
430
|
-
`
|
|
431
|
-
|
|
717
|
+
`sending ${this.sendBuffer.length} buffered messages`,
|
|
718
|
+
this.loggingMetadata
|
|
432
719
|
);
|
|
433
720
|
}
|
|
434
|
-
|
|
721
|
+
for (const msg of this.sendBuffer) {
|
|
722
|
+
conn.send(this.options.codec.toBuffer(msg));
|
|
723
|
+
}
|
|
435
724
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if (this.
|
|
725
|
+
startActiveHeartbeat() {
|
|
726
|
+
this.heartbeatHandle = setInterval(() => {
|
|
727
|
+
const misses = this.heartbeatMisses;
|
|
728
|
+
const missDuration = misses * this.options.heartbeatIntervalMs;
|
|
729
|
+
if (misses >= this.options.heartbeatsUntilDead) {
|
|
441
730
|
this.log?.info(
|
|
442
731
|
`closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
|
|
443
732
|
this.loggingMetadata
|
|
444
733
|
);
|
|
445
734
|
this.telemetry.span.addEvent("closing connection due to inactivity");
|
|
446
|
-
this.
|
|
735
|
+
this.conn.close();
|
|
736
|
+
clearInterval(this.heartbeatHandle);
|
|
737
|
+
this.heartbeatHandle = void 0;
|
|
738
|
+
return;
|
|
447
739
|
}
|
|
448
|
-
|
|
449
|
-
|
|
740
|
+
this.sendHeartbeat();
|
|
741
|
+
this.heartbeatMisses++;
|
|
742
|
+
}, this.options.heartbeatIntervalMs);
|
|
743
|
+
}
|
|
744
|
+
sendHeartbeat() {
|
|
450
745
|
this.send({
|
|
451
746
|
streamId: "heartbeat",
|
|
452
747
|
controlFlags: 1 /* AckBit */,
|
|
@@ -454,585 +749,296 @@ var Session = class {
|
|
|
454
749
|
type: "ACK"
|
|
455
750
|
}
|
|
456
751
|
});
|
|
457
|
-
this.heartbeatMisses++;
|
|
458
|
-
}
|
|
459
|
-
resetBufferedMessages() {
|
|
460
|
-
this.sendBuffer = [];
|
|
461
|
-
this.seq = 0;
|
|
462
|
-
this.ack = 0;
|
|
463
752
|
}
|
|
464
|
-
|
|
465
|
-
this.
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
message: errMsg
|
|
481
|
-
});
|
|
482
|
-
this.log?.error(errMsg, {
|
|
753
|
+
onMessageData = (msg) => {
|
|
754
|
+
const parsedMsg = this.parseMsg(msg);
|
|
755
|
+
if (parsedMsg === null)
|
|
756
|
+
return;
|
|
757
|
+
if (parsedMsg.seq !== this.ack) {
|
|
758
|
+
if (parsedMsg.seq < this.ack) {
|
|
759
|
+
this.log?.debug(
|
|
760
|
+
`received duplicate msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack}), discarding`,
|
|
761
|
+
{
|
|
762
|
+
...this.loggingMetadata,
|
|
763
|
+
transportMessage: parsedMsg
|
|
764
|
+
}
|
|
765
|
+
);
|
|
766
|
+
} else {
|
|
767
|
+
const reason = `received out-of-order msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack})`;
|
|
768
|
+
this.log?.error(reason, {
|
|
483
769
|
...this.loggingMetadata,
|
|
484
|
-
transportMessage:
|
|
485
|
-
connId: conn.id,
|
|
770
|
+
transportMessage: parsedMsg,
|
|
486
771
|
tags: ["invariant-violation"]
|
|
487
772
|
});
|
|
488
|
-
|
|
489
|
-
|
|
773
|
+
this.telemetry.span.setStatus({
|
|
774
|
+
code: import_api2.SpanStatusCode.ERROR,
|
|
775
|
+
message: reason
|
|
776
|
+
});
|
|
777
|
+
this.listeners.onInvalidMessage(reason);
|
|
490
778
|
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
updateBookkeeping(ack, seq) {
|
|
494
|
-
if (seq + 1 < this.ack) {
|
|
495
|
-
this.log?.error(`received stale seq ${seq} + 1 < ${this.ack}`, {
|
|
496
|
-
...this.loggingMetadata,
|
|
497
|
-
tags: ["invariant-violation"]
|
|
498
|
-
});
|
|
499
779
|
return;
|
|
500
780
|
}
|
|
501
|
-
this.
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
`closing old inner connection from session to ${this.to}`,
|
|
509
|
-
this.loggingMetadata
|
|
510
|
-
);
|
|
511
|
-
this.connection.close();
|
|
512
|
-
this.connection = void 0;
|
|
513
|
-
}
|
|
514
|
-
replaceWithNewConnection(newConn, isTransparentReconnect) {
|
|
515
|
-
this.closeStaleConnection(newConn);
|
|
516
|
-
this.cancelGrace();
|
|
517
|
-
if (isTransparentReconnect) {
|
|
518
|
-
this.sendBufferedMessages(newConn);
|
|
519
|
-
}
|
|
520
|
-
this.connection = newConn;
|
|
521
|
-
this.handshakingConnection = void 0;
|
|
522
|
-
}
|
|
523
|
-
replaceWithNewHandshakingConnection(newConn) {
|
|
524
|
-
this.handshakingConnection = newConn;
|
|
525
|
-
}
|
|
526
|
-
beginGrace(cb) {
|
|
527
|
-
this.log?.info(
|
|
528
|
-
`starting ${this.options.sessionDisconnectGraceMs}ms grace period until session to ${this.to} is closed`,
|
|
529
|
-
this.loggingMetadata
|
|
530
|
-
);
|
|
531
|
-
this.cancelGrace();
|
|
532
|
-
this.disconnectionGrace = setTimeout(() => {
|
|
533
|
-
this.log?.info(
|
|
534
|
-
`grace period for ${this.to} elapsed`,
|
|
535
|
-
this.loggingMetadata
|
|
536
|
-
);
|
|
537
|
-
cb();
|
|
538
|
-
}, this.options.sessionDisconnectGraceMs);
|
|
539
|
-
}
|
|
540
|
-
// called on reconnect of the underlying session
|
|
541
|
-
cancelGrace() {
|
|
542
|
-
this.heartbeatMisses = 0;
|
|
543
|
-
clearTimeout(this.disconnectionGrace);
|
|
544
|
-
this.disconnectionGrace = void 0;
|
|
545
|
-
}
|
|
546
|
-
/**
|
|
547
|
-
* Used to close the handshaking connection, if set.
|
|
548
|
-
*/
|
|
549
|
-
closeHandshakingConnection(expectedHandshakingConn) {
|
|
550
|
-
if (this.handshakingConnection === void 0)
|
|
551
|
-
return;
|
|
552
|
-
if (expectedHandshakingConn !== void 0 && this.handshakingConnection === expectedHandshakingConn) {
|
|
781
|
+
this.log?.debug(`received msg`, {
|
|
782
|
+
...this.loggingMetadata,
|
|
783
|
+
transportMessage: parsedMsg
|
|
784
|
+
});
|
|
785
|
+
this.updateBookkeeping(parsedMsg.ack, parsedMsg.seq);
|
|
786
|
+
if (!isAck(parsedMsg.controlFlags)) {
|
|
787
|
+
this.listeners.onMessage(parsedMsg);
|
|
553
788
|
return;
|
|
554
789
|
}
|
|
555
|
-
this.
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
this.closeStaleConnection();
|
|
562
|
-
this.cancelGrace();
|
|
563
|
-
this.resetBufferedMessages();
|
|
564
|
-
clearInterval(this.heartbeat);
|
|
565
|
-
}
|
|
566
|
-
get connected() {
|
|
567
|
-
return this.connection !== void 0;
|
|
568
|
-
}
|
|
569
|
-
get nextExpectedAck() {
|
|
570
|
-
return this.seq;
|
|
571
|
-
}
|
|
572
|
-
get nextExpectedSeq() {
|
|
573
|
-
return this.ack;
|
|
574
|
-
}
|
|
575
|
-
/**
|
|
576
|
-
* Check that the peer's next expected seq number matches something that is in our send buffer
|
|
577
|
-
* _or_ matches our actual next seq.
|
|
578
|
-
*/
|
|
579
|
-
nextExpectedSeqInRange(nextExpectedSeq) {
|
|
580
|
-
for (const msg of this.sendBuffer) {
|
|
581
|
-
if (nextExpectedSeq === msg.seq) {
|
|
582
|
-
return true;
|
|
583
|
-
}
|
|
790
|
+
this.log?.debug(`discarding msg (ack bit set)`, {
|
|
791
|
+
...this.loggingMetadata,
|
|
792
|
+
transportMessage: parsedMsg
|
|
793
|
+
});
|
|
794
|
+
if (!this.isActivelyHeartbeating) {
|
|
795
|
+
this.sendHeartbeat();
|
|
584
796
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
this.
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
from: this.from,
|
|
598
|
-
seq: this.seq,
|
|
599
|
-
ack: this.ack
|
|
600
|
-
};
|
|
601
|
-
this.seq++;
|
|
602
|
-
this.sendBuffer.push(msg);
|
|
603
|
-
return msg;
|
|
604
|
-
}
|
|
605
|
-
inspectSendBuffer() {
|
|
606
|
-
return this.sendBuffer;
|
|
797
|
+
};
|
|
798
|
+
_handleStateExit() {
|
|
799
|
+
super._handleStateExit();
|
|
800
|
+
this.conn.removeDataListener(this.onMessageData);
|
|
801
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
802
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
803
|
+
clearInterval(this.heartbeatHandle);
|
|
804
|
+
this.heartbeatHandle = void 0;
|
|
805
|
+
}
|
|
806
|
+
_handleClose() {
|
|
807
|
+
super._handleClose();
|
|
808
|
+
this.conn.close();
|
|
607
809
|
}
|
|
608
810
|
};
|
|
609
811
|
|
|
610
|
-
// transport/
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
812
|
+
// transport/sessionStateMachine/transitions.ts
|
|
813
|
+
function inheritSharedSession(session) {
|
|
814
|
+
return [
|
|
815
|
+
session.id,
|
|
816
|
+
session.from,
|
|
817
|
+
session.to,
|
|
818
|
+
session.seq,
|
|
819
|
+
session.ack,
|
|
820
|
+
session.sendBuffer,
|
|
821
|
+
session.telemetry,
|
|
822
|
+
session.options,
|
|
823
|
+
session.protocolVersion,
|
|
824
|
+
session.log
|
|
825
|
+
];
|
|
622
826
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
827
|
+
var SessionStateGraph = {
|
|
828
|
+
entrypoints: {
|
|
829
|
+
NoConnection(to, from, listeners, options, protocolVersion, log) {
|
|
830
|
+
const id = `session-${generateId()}`;
|
|
831
|
+
const telemetry = createSessionTelemetryInfo(id, to, from);
|
|
832
|
+
const sendBuffer = [];
|
|
833
|
+
const session = new SessionNoConnection(
|
|
834
|
+
listeners,
|
|
835
|
+
id,
|
|
836
|
+
from,
|
|
837
|
+
to,
|
|
838
|
+
0,
|
|
839
|
+
0,
|
|
840
|
+
sendBuffer,
|
|
841
|
+
telemetry,
|
|
842
|
+
options,
|
|
843
|
+
protocolVersion,
|
|
844
|
+
log
|
|
845
|
+
);
|
|
846
|
+
session.log?.info(`session ${session.id} created in NoConnection state`, {
|
|
847
|
+
...session.loggingMetadata,
|
|
848
|
+
tags: ["state-transition"]
|
|
849
|
+
});
|
|
850
|
+
return session;
|
|
851
|
+
},
|
|
852
|
+
WaitingForHandshake(from, conn, listeners, options, log) {
|
|
853
|
+
const session = new SessionWaitingForHandshake(
|
|
854
|
+
conn,
|
|
855
|
+
listeners,
|
|
856
|
+
from,
|
|
857
|
+
options,
|
|
858
|
+
log
|
|
859
|
+
);
|
|
860
|
+
session.log?.info(`session created in WaitingForHandshake state`, {
|
|
861
|
+
...session.loggingMetadata,
|
|
862
|
+
tags: ["state-transition"]
|
|
863
|
+
});
|
|
864
|
+
return session;
|
|
865
|
+
}
|
|
643
866
|
},
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
867
|
+
// All of the transitions 'move'/'consume' the old session and return a new one.
|
|
868
|
+
// After a session is transitioned, any usage of the old session will throw.
|
|
869
|
+
transition: {
|
|
870
|
+
// happy path transitions
|
|
871
|
+
NoConnectionToConnecting(oldSession, connPromise, listeners) {
|
|
872
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
873
|
+
oldSession._handleStateExit();
|
|
874
|
+
const session = new SessionConnecting(
|
|
875
|
+
connPromise,
|
|
876
|
+
listeners,
|
|
877
|
+
...carriedState
|
|
878
|
+
);
|
|
879
|
+
session.log?.info(
|
|
880
|
+
`session ${session.id} transition from NoConnection to Connecting`,
|
|
881
|
+
{
|
|
882
|
+
...session.loggingMetadata,
|
|
883
|
+
tags: ["state-transition"]
|
|
654
884
|
}
|
|
655
885
|
);
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
// transport/options.ts
|
|
666
|
-
var defaultTransportOptions = {
|
|
667
|
-
heartbeatIntervalMs: 1e3,
|
|
668
|
-
heartbeatsUntilDead: 2,
|
|
669
|
-
sessionDisconnectGraceMs: 5e3,
|
|
670
|
-
codec: NaiveJsonCodec
|
|
671
|
-
};
|
|
672
|
-
var defaultConnectionRetryOptions = {
|
|
673
|
-
baseIntervalMs: 250,
|
|
674
|
-
maxJitterMs: 200,
|
|
675
|
-
maxBackoffMs: 32e3,
|
|
676
|
-
attemptBudgetCapacity: 5,
|
|
677
|
-
budgetRestoreIntervalMs: 200
|
|
678
|
-
};
|
|
679
|
-
var defaultClientTransportOptions = {
|
|
680
|
-
...defaultTransportOptions,
|
|
681
|
-
...defaultConnectionRetryOptions
|
|
682
|
-
};
|
|
683
|
-
var defaultServerTransportOptions = {
|
|
684
|
-
...defaultTransportOptions
|
|
685
|
-
};
|
|
686
|
-
|
|
687
|
-
// transport/transport.ts
|
|
688
|
-
var Transport = class {
|
|
689
|
-
/**
|
|
690
|
-
* The status of the transport.
|
|
691
|
-
*/
|
|
692
|
-
status;
|
|
693
|
-
/**
|
|
694
|
-
* The {@link Codec} used to encode and decode messages.
|
|
695
|
-
*/
|
|
696
|
-
codec;
|
|
697
|
-
/**
|
|
698
|
-
* The client ID of this transport.
|
|
699
|
-
*/
|
|
700
|
-
clientId;
|
|
701
|
-
/**
|
|
702
|
-
* The map of {@link Session}s managed by this transport.
|
|
703
|
-
*/
|
|
704
|
-
sessions;
|
|
705
|
-
/**
|
|
706
|
-
* The map of {@link Connection}s managed by this transport.
|
|
707
|
-
*/
|
|
708
|
-
get connections() {
|
|
709
|
-
return new Map(
|
|
710
|
-
[...this.sessions].map(([client, session]) => [client, session.connection]).filter((entry) => entry[1] !== void 0)
|
|
711
|
-
);
|
|
712
|
-
}
|
|
713
|
-
/**
|
|
714
|
-
* The event dispatcher for handling events of type EventTypes.
|
|
715
|
-
*/
|
|
716
|
-
eventDispatcher;
|
|
717
|
-
/**
|
|
718
|
-
* The options for this transport.
|
|
719
|
-
*/
|
|
720
|
-
options;
|
|
721
|
-
log;
|
|
722
|
-
/**
|
|
723
|
-
* Creates a new Transport instance.
|
|
724
|
-
* This should also set up {@link onConnect}, and {@link onDisconnect} listeners.
|
|
725
|
-
* @param codec The codec used to encode and decode messages.
|
|
726
|
-
* @param clientId The client ID of this transport.
|
|
727
|
-
*/
|
|
728
|
-
constructor(clientId, providedOptions) {
|
|
729
|
-
this.options = { ...defaultTransportOptions, ...providedOptions };
|
|
730
|
-
this.eventDispatcher = new EventDispatcher();
|
|
731
|
-
this.sessions = /* @__PURE__ */ new Map();
|
|
732
|
-
this.codec = this.options.codec;
|
|
733
|
-
this.clientId = clientId;
|
|
734
|
-
this.status = "open";
|
|
735
|
-
}
|
|
736
|
-
bindLogger(fn, level) {
|
|
737
|
-
if (typeof fn === "function") {
|
|
738
|
-
this.log = createLogProxy(new BaseLogger(fn, level));
|
|
739
|
-
return;
|
|
740
|
-
}
|
|
741
|
-
this.log = createLogProxy(fn);
|
|
742
|
-
}
|
|
743
|
-
/**
|
|
744
|
-
* Called when a new connection is established
|
|
745
|
-
* and we know the identity of the connected client.
|
|
746
|
-
* @param conn The connection object.
|
|
747
|
-
*/
|
|
748
|
-
onConnect(conn, session, isTransparentReconnect) {
|
|
749
|
-
this.eventDispatcher.dispatchEvent("connectionStatus", {
|
|
750
|
-
status: "connect",
|
|
751
|
-
conn
|
|
752
|
-
});
|
|
753
|
-
conn.telemetry = createConnectionTelemetryInfo(conn, session.telemetry);
|
|
754
|
-
session.replaceWithNewConnection(conn, isTransparentReconnect);
|
|
755
|
-
this.log?.info(`connected to ${session.to}`, {
|
|
756
|
-
...conn.loggingMetadata,
|
|
757
|
-
...session.loggingMetadata
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
createSession(to, conn, propagationCtx) {
|
|
761
|
-
const session = new Session(
|
|
762
|
-
conn,
|
|
763
|
-
this.clientId,
|
|
764
|
-
to,
|
|
765
|
-
this.options,
|
|
766
|
-
propagationCtx
|
|
767
|
-
);
|
|
768
|
-
if (this.log) {
|
|
769
|
-
session.bindLogger(this.log);
|
|
770
|
-
}
|
|
771
|
-
const currentSession = this.sessions.get(session.to);
|
|
772
|
-
if (currentSession) {
|
|
773
|
-
this.log?.warn(
|
|
774
|
-
`session ${session.id} from ${session.to} surreptitiously replacing ${currentSession.id}`,
|
|
886
|
+
return session;
|
|
887
|
+
},
|
|
888
|
+
ConnectingToHandshaking(oldSession, conn, listeners) {
|
|
889
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
890
|
+
oldSession._handleStateExit();
|
|
891
|
+
const session = new SessionHandshaking(conn, listeners, ...carriedState);
|
|
892
|
+
session.log?.info(
|
|
893
|
+
`session ${session.id} transition from Connecting to Handshaking`,
|
|
775
894
|
{
|
|
776
|
-
...
|
|
777
|
-
tags: ["
|
|
895
|
+
...session.loggingMetadata,
|
|
896
|
+
tags: ["state-transition"]
|
|
778
897
|
}
|
|
779
898
|
);
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
to,
|
|
794
|
-
conn,
|
|
795
|
-
sessionId,
|
|
796
|
-
propagationCtx
|
|
797
|
-
}) {
|
|
798
|
-
let session = this.sessions.get(to);
|
|
799
|
-
if (session !== void 0) {
|
|
800
|
-
this.log?.info(
|
|
801
|
-
`session for ${to} already exists, replacing it with a new session as requested`,
|
|
802
|
-
session.loggingMetadata
|
|
899
|
+
return session;
|
|
900
|
+
},
|
|
901
|
+
HandshakingToConnected(oldSession, listeners) {
|
|
902
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
903
|
+
const conn = oldSession.conn;
|
|
904
|
+
oldSession._handleStateExit();
|
|
905
|
+
const session = new SessionConnected(conn, listeners, ...carriedState);
|
|
906
|
+
session.log?.info(
|
|
907
|
+
`session ${session.id} transition from Handshaking to Connected`,
|
|
908
|
+
{
|
|
909
|
+
...session.loggingMetadata,
|
|
910
|
+
tags: ["state-transition"]
|
|
911
|
+
}
|
|
803
912
|
);
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
) {
|
|
827
|
-
return false;
|
|
828
|
-
}
|
|
829
|
-
this.log?.info(
|
|
830
|
-
`reused existing session for ${to}`,
|
|
831
|
-
session.loggingMetadata
|
|
832
|
-
);
|
|
833
|
-
return session;
|
|
834
|
-
}
|
|
835
|
-
getOrCreateSession({
|
|
836
|
-
to,
|
|
837
|
-
conn,
|
|
838
|
-
handshakingConn,
|
|
839
|
-
sessionId,
|
|
840
|
-
propagationCtx
|
|
841
|
-
}) {
|
|
842
|
-
let session = this.sessions.get(to);
|
|
843
|
-
const isReconnect = session !== void 0;
|
|
844
|
-
let isTransparentReconnect = isReconnect;
|
|
845
|
-
if (session?.advertisedSessionId !== void 0 && sessionId !== void 0 && session.advertisedSessionId !== sessionId) {
|
|
846
|
-
this.log?.info(
|
|
847
|
-
`session for ${to} already exists but has a different session id (expected: ${session.advertisedSessionId}, got: ${sessionId}), creating a new one`,
|
|
848
|
-
session.loggingMetadata
|
|
913
|
+
return session;
|
|
914
|
+
},
|
|
915
|
+
WaitingForHandshakeToConnected(pendingSession, oldSession, sessionId, to, propagationCtx, listeners, protocolVersion) {
|
|
916
|
+
const conn = pendingSession.conn;
|
|
917
|
+
const { from, options } = pendingSession;
|
|
918
|
+
const carriedState = oldSession ? (
|
|
919
|
+
// old session exists, inherit state
|
|
920
|
+
inheritSharedSession(oldSession)
|
|
921
|
+
) : (
|
|
922
|
+
// old session does not exist, create new state
|
|
923
|
+
[
|
|
924
|
+
sessionId,
|
|
925
|
+
from,
|
|
926
|
+
to,
|
|
927
|
+
0,
|
|
928
|
+
0,
|
|
929
|
+
[],
|
|
930
|
+
createSessionTelemetryInfo(sessionId, to, from, propagationCtx),
|
|
931
|
+
options,
|
|
932
|
+
protocolVersion,
|
|
933
|
+
pendingSession.log
|
|
934
|
+
]
|
|
849
935
|
);
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
session = this.createSession(to, conn, propagationCtx);
|
|
860
|
-
this.log?.info(
|
|
861
|
-
`no session for ${to}, created a new one`,
|
|
862
|
-
session.loggingMetadata
|
|
936
|
+
pendingSession._handleStateExit();
|
|
937
|
+
oldSession?._handleStateExit();
|
|
938
|
+
const session = new SessionConnected(conn, listeners, ...carriedState);
|
|
939
|
+
session.log?.info(
|
|
940
|
+
`session ${session.id} transition from WaitingForHandshake to Connected`,
|
|
941
|
+
{
|
|
942
|
+
...session.loggingMetadata,
|
|
943
|
+
tags: ["state-transition"]
|
|
944
|
+
}
|
|
863
945
|
);
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
session
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
946
|
+
return session;
|
|
947
|
+
},
|
|
948
|
+
// disconnect paths
|
|
949
|
+
ConnectingToNoConnection(oldSession, listeners) {
|
|
950
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
951
|
+
oldSession.bestEffortClose();
|
|
952
|
+
oldSession._handleStateExit();
|
|
953
|
+
const session = new SessionNoConnection(listeners, ...carriedState);
|
|
954
|
+
session.log?.info(
|
|
955
|
+
`session ${session.id} transition from Connecting to NoConnection`,
|
|
956
|
+
{
|
|
957
|
+
...session.loggingMetadata,
|
|
958
|
+
tags: ["state-transition"]
|
|
959
|
+
}
|
|
960
|
+
);
|
|
961
|
+
return session;
|
|
962
|
+
},
|
|
963
|
+
HandshakingToNoConnection(oldSession, listeners) {
|
|
964
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
965
|
+
oldSession.conn.close();
|
|
966
|
+
oldSession._handleStateExit();
|
|
967
|
+
const session = new SessionNoConnection(listeners, ...carriedState);
|
|
968
|
+
session.log?.info(
|
|
969
|
+
`session ${session.id} transition from Handshaking to NoConnection`,
|
|
970
|
+
{
|
|
971
|
+
...session.loggingMetadata,
|
|
972
|
+
tags: ["state-transition"]
|
|
973
|
+
}
|
|
974
|
+
);
|
|
975
|
+
return session;
|
|
976
|
+
},
|
|
977
|
+
ConnectedToNoConnection(oldSession, listeners) {
|
|
978
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
979
|
+
oldSession.conn.close();
|
|
980
|
+
oldSession._handleStateExit();
|
|
981
|
+
const session = new SessionNoConnection(listeners, ...carriedState);
|
|
982
|
+
session.log?.info(
|
|
983
|
+
`session ${session.id} transition from Connected to NoConnection`,
|
|
887
984
|
{
|
|
888
985
|
...session.loggingMetadata,
|
|
889
|
-
tags: ["
|
|
986
|
+
tags: ["state-transition"]
|
|
890
987
|
}
|
|
891
988
|
);
|
|
892
|
-
return;
|
|
989
|
+
return session;
|
|
893
990
|
}
|
|
894
|
-
this.sessions.delete(session.to);
|
|
895
|
-
this.log?.info(
|
|
896
|
-
`session ${session.id} disconnect from ${session.to}`,
|
|
897
|
-
session.loggingMetadata
|
|
898
|
-
);
|
|
899
|
-
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
900
|
-
status: "disconnect",
|
|
901
|
-
session
|
|
902
|
-
});
|
|
903
991
|
}
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
// transport/transport.ts
|
|
995
|
+
var Transport = class {
|
|
904
996
|
/**
|
|
905
|
-
* The
|
|
906
|
-
* @param conn The connection object.
|
|
907
|
-
* @param connectedTo The peer we are connected to.
|
|
997
|
+
* The status of the transport.
|
|
908
998
|
*/
|
|
909
|
-
|
|
910
|
-
if (session.connection !== void 0 && session.connection.id !== conn.id) {
|
|
911
|
-
session.telemetry.span.addEvent("onDisconnect race");
|
|
912
|
-
this.log?.warn("onDisconnect race", {
|
|
913
|
-
clientId: this.clientId,
|
|
914
|
-
...session.loggingMetadata,
|
|
915
|
-
...conn.loggingMetadata,
|
|
916
|
-
tags: ["invariant-violation"]
|
|
917
|
-
});
|
|
918
|
-
return;
|
|
919
|
-
}
|
|
920
|
-
conn.telemetry?.span.end();
|
|
921
|
-
this.eventDispatcher.dispatchEvent("connectionStatus", {
|
|
922
|
-
status: "disconnect",
|
|
923
|
-
conn
|
|
924
|
-
});
|
|
925
|
-
session.connection = void 0;
|
|
926
|
-
session.beginGrace(() => {
|
|
927
|
-
if (session.connection !== void 0) {
|
|
928
|
-
session.telemetry.span.addEvent("session grace period race");
|
|
929
|
-
this.log?.warn("session grace period race", {
|
|
930
|
-
clientId: this.clientId,
|
|
931
|
-
...session.loggingMetadata,
|
|
932
|
-
...conn.loggingMetadata,
|
|
933
|
-
tags: ["invariant-violation"]
|
|
934
|
-
});
|
|
935
|
-
return;
|
|
936
|
-
}
|
|
937
|
-
session.telemetry.span.addEvent("session grace period expired");
|
|
938
|
-
this.deleteSession({
|
|
939
|
-
session,
|
|
940
|
-
closeHandshakingConnection: true,
|
|
941
|
-
handshakingConn: conn
|
|
942
|
-
});
|
|
943
|
-
});
|
|
944
|
-
}
|
|
999
|
+
status;
|
|
945
1000
|
/**
|
|
946
|
-
*
|
|
947
|
-
* @param msg The message to parse.
|
|
948
|
-
* @returns The parsed message, or null if the message is malformed or invalid.
|
|
1001
|
+
* The client ID of this transport.
|
|
949
1002
|
*/
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1003
|
+
clientId;
|
|
1004
|
+
/**
|
|
1005
|
+
* The event dispatcher for handling events of type EventTypes.
|
|
1006
|
+
*/
|
|
1007
|
+
eventDispatcher;
|
|
1008
|
+
/**
|
|
1009
|
+
* The options for this transport.
|
|
1010
|
+
*/
|
|
1011
|
+
options;
|
|
1012
|
+
log;
|
|
1013
|
+
sessions;
|
|
1014
|
+
/**
|
|
1015
|
+
* Creates a new Transport instance.
|
|
1016
|
+
* @param codec The codec used to encode and decode messages.
|
|
1017
|
+
* @param clientId The client ID of this transport.
|
|
1018
|
+
*/
|
|
1019
|
+
constructor(clientId, providedOptions) {
|
|
1020
|
+
this.options = { ...defaultTransportOptions, ...providedOptions };
|
|
1021
|
+
this.eventDispatcher = new EventDispatcher();
|
|
1022
|
+
this.clientId = clientId;
|
|
1023
|
+
this.status = "open";
|
|
1024
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
1025
|
+
}
|
|
1026
|
+
bindLogger(fn, level) {
|
|
1027
|
+
if (typeof fn === "function") {
|
|
1028
|
+
this.log = createLogProxy(new BaseLogger(fn, level));
|
|
1029
|
+
return;
|
|
972
1030
|
}
|
|
973
|
-
|
|
1031
|
+
this.log = createLogProxy(fn);
|
|
974
1032
|
}
|
|
975
1033
|
/**
|
|
976
1034
|
* Called when a message is received by this transport.
|
|
977
1035
|
* You generally shouldn't need to override this in downstream transport implementations.
|
|
978
1036
|
* @param msg The received message.
|
|
979
1037
|
*/
|
|
980
|
-
handleMsg(msg
|
|
1038
|
+
handleMsg(msg) {
|
|
981
1039
|
if (this.getStatus() !== "open")
|
|
982
1040
|
return;
|
|
983
|
-
|
|
984
|
-
if (!session) {
|
|
985
|
-
this.log?.error(`received message for unknown session from ${msg.from}`, {
|
|
986
|
-
clientId: this.clientId,
|
|
987
|
-
transportMessage: msg,
|
|
988
|
-
...conn.loggingMetadata,
|
|
989
|
-
tags: ["invariant-violation"]
|
|
990
|
-
});
|
|
991
|
-
return;
|
|
992
|
-
}
|
|
993
|
-
session.cancelGrace();
|
|
994
|
-
this.log?.debug(`received msg`, {
|
|
995
|
-
clientId: this.clientId,
|
|
996
|
-
transportMessage: msg,
|
|
997
|
-
...conn.loggingMetadata
|
|
998
|
-
});
|
|
999
|
-
if (msg.seq !== session.nextExpectedSeq) {
|
|
1000
|
-
if (msg.seq < session.nextExpectedSeq) {
|
|
1001
|
-
this.log?.debug(
|
|
1002
|
-
`received duplicate msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq}), discarding`,
|
|
1003
|
-
{
|
|
1004
|
-
clientId: this.clientId,
|
|
1005
|
-
transportMessage: msg,
|
|
1006
|
-
...conn.loggingMetadata
|
|
1007
|
-
}
|
|
1008
|
-
);
|
|
1009
|
-
} else {
|
|
1010
|
-
const errMsg = `received out-of-order msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq})`;
|
|
1011
|
-
this.log?.error(`${errMsg}, marking connection as dead`, {
|
|
1012
|
-
clientId: this.clientId,
|
|
1013
|
-
transportMessage: msg,
|
|
1014
|
-
...conn.loggingMetadata,
|
|
1015
|
-
tags: ["invariant-violation"]
|
|
1016
|
-
});
|
|
1017
|
-
this.protocolError(ProtocolError.MessageOrderingViolated, errMsg);
|
|
1018
|
-
session.telemetry.span.setStatus({
|
|
1019
|
-
code: import_api3.SpanStatusCode.ERROR,
|
|
1020
|
-
message: "message order violated"
|
|
1021
|
-
});
|
|
1022
|
-
this.deleteSession({ session, closeHandshakingConnection: true });
|
|
1023
|
-
}
|
|
1024
|
-
return;
|
|
1025
|
-
}
|
|
1026
|
-
session.updateBookkeeping(msg.ack, msg.seq);
|
|
1027
|
-
if (!isAck(msg.controlFlags)) {
|
|
1028
|
-
this.eventDispatcher.dispatchEvent("message", msg);
|
|
1029
|
-
} else {
|
|
1030
|
-
this.log?.debug(`discarding msg (ack bit set)`, {
|
|
1031
|
-
clientId: this.clientId,
|
|
1032
|
-
transportMessage: msg,
|
|
1033
|
-
...conn.loggingMetadata
|
|
1034
|
-
});
|
|
1035
|
-
}
|
|
1041
|
+
this.eventDispatcher.dispatchEvent("message", msg);
|
|
1036
1042
|
}
|
|
1037
1043
|
/**
|
|
1038
1044
|
* Adds a listener to this transport.
|
|
@@ -1050,50 +1056,6 @@ var Transport = class {
|
|
|
1050
1056
|
removeEventListener(type, handler) {
|
|
1051
1057
|
this.eventDispatcher.removeEventListener(type, handler);
|
|
1052
1058
|
}
|
|
1053
|
-
/**
|
|
1054
|
-
* Sends a message over this transport, delegating to the appropriate connection to actually
|
|
1055
|
-
* send the message.
|
|
1056
|
-
* @param msg The message to send.
|
|
1057
|
-
* @returns The ID of the sent message or undefined if it wasn't sent
|
|
1058
|
-
*/
|
|
1059
|
-
send(to, msg) {
|
|
1060
|
-
if (this.getStatus() === "closed") {
|
|
1061
|
-
const err = "transport is closed, cant send";
|
|
1062
|
-
this.log?.error(err, {
|
|
1063
|
-
clientId: this.clientId,
|
|
1064
|
-
transportMessage: msg,
|
|
1065
|
-
tags: ["invariant-violation"]
|
|
1066
|
-
});
|
|
1067
|
-
throw new Error(err);
|
|
1068
|
-
}
|
|
1069
|
-
return this.getOrCreateSession({ to }).session.send(msg);
|
|
1070
|
-
}
|
|
1071
|
-
// control helpers
|
|
1072
|
-
sendCloseControl(to, streamId) {
|
|
1073
|
-
return this.send(to, {
|
|
1074
|
-
streamId,
|
|
1075
|
-
controlFlags: 8 /* StreamClosedBit */,
|
|
1076
|
-
payload: {
|
|
1077
|
-
type: "CLOSE"
|
|
1078
|
-
}
|
|
1079
|
-
});
|
|
1080
|
-
}
|
|
1081
|
-
sendRequestCloseControl(to, streamId) {
|
|
1082
|
-
return this.send(to, {
|
|
1083
|
-
streamId,
|
|
1084
|
-
controlFlags: 16 /* StreamCloseRequestBit */,
|
|
1085
|
-
payload: {
|
|
1086
|
-
type: "CLOSE"
|
|
1087
|
-
}
|
|
1088
|
-
});
|
|
1089
|
-
}
|
|
1090
|
-
sendAbort(to, streamId, payload) {
|
|
1091
|
-
return this.send(to, {
|
|
1092
|
-
streamId,
|
|
1093
|
-
controlFlags: 4 /* StreamAbortBit */,
|
|
1094
|
-
payload
|
|
1095
|
-
});
|
|
1096
|
-
}
|
|
1097
1059
|
protocolError(type, message) {
|
|
1098
1060
|
this.eventDispatcher.dispatchEvent("protocolError", { type, message });
|
|
1099
1061
|
}
|
|
@@ -1105,7 +1067,7 @@ var Transport = class {
|
|
|
1105
1067
|
close() {
|
|
1106
1068
|
this.status = "closed";
|
|
1107
1069
|
for (const session of this.sessions.values()) {
|
|
1108
|
-
this.deleteSession(
|
|
1070
|
+
this.deleteSession(session);
|
|
1109
1071
|
}
|
|
1110
1072
|
this.eventDispatcher.dispatchEvent("transportStatus", {
|
|
1111
1073
|
status: this.status
|
|
@@ -1116,10 +1078,72 @@ var Transport = class {
|
|
|
1116
1078
|
getStatus() {
|
|
1117
1079
|
return this.status;
|
|
1118
1080
|
}
|
|
1081
|
+
updateSession(session) {
|
|
1082
|
+
const activeSession = this.sessions.get(session.to);
|
|
1083
|
+
if (activeSession && activeSession.id !== session.id) {
|
|
1084
|
+
const msg = `attempt to transition active session for ${session.to} but active session (${activeSession.id}) is different from handle (${session.id})`;
|
|
1085
|
+
throw new Error(msg);
|
|
1086
|
+
}
|
|
1087
|
+
this.sessions.set(session.to, session);
|
|
1088
|
+
if (!activeSession) {
|
|
1089
|
+
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
1090
|
+
status: "connect",
|
|
1091
|
+
session
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
this.eventDispatcher.dispatchEvent("sessionTransition", {
|
|
1095
|
+
state: session.state,
|
|
1096
|
+
session
|
|
1097
|
+
});
|
|
1098
|
+
return session;
|
|
1099
|
+
}
|
|
1100
|
+
// state transitions
|
|
1101
|
+
deleteSession(session) {
|
|
1102
|
+
session.log?.info(`closing session ${session.id}`, session.loggingMetadata);
|
|
1103
|
+
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
1104
|
+
status: "disconnect",
|
|
1105
|
+
session
|
|
1106
|
+
});
|
|
1107
|
+
session.close();
|
|
1108
|
+
this.sessions.delete(session.to);
|
|
1109
|
+
}
|
|
1110
|
+
// common listeners
|
|
1111
|
+
onSessionGracePeriodElapsed(session) {
|
|
1112
|
+
this.log?.warn(
|
|
1113
|
+
`session to ${session.to} grace period elapsed, closing`,
|
|
1114
|
+
session.loggingMetadata
|
|
1115
|
+
);
|
|
1116
|
+
this.deleteSession(session);
|
|
1117
|
+
}
|
|
1118
|
+
onConnectingFailed(session) {
|
|
1119
|
+
const noConnectionSession = SessionStateGraph.transition.ConnectingToNoConnection(session, {
|
|
1120
|
+
onSessionGracePeriodElapsed: () => {
|
|
1121
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
return this.updateSession(noConnectionSession);
|
|
1125
|
+
}
|
|
1126
|
+
onConnClosed(session) {
|
|
1127
|
+
let noConnectionSession;
|
|
1128
|
+
if (session.state === "Handshaking" /* Handshaking */) {
|
|
1129
|
+
noConnectionSession = SessionStateGraph.transition.HandshakingToNoConnection(session, {
|
|
1130
|
+
onSessionGracePeriodElapsed: () => {
|
|
1131
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
} else {
|
|
1135
|
+
noConnectionSession = SessionStateGraph.transition.ConnectedToNoConnection(session, {
|
|
1136
|
+
onSessionGracePeriodElapsed: () => {
|
|
1137
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
return this.updateSession(noConnectionSession);
|
|
1142
|
+
}
|
|
1119
1143
|
};
|
|
1120
1144
|
|
|
1121
1145
|
// transport/client.ts
|
|
1122
|
-
var
|
|
1146
|
+
var import_api3 = require("@opentelemetry/api");
|
|
1123
1147
|
|
|
1124
1148
|
// transport/rateLimit.ts
|
|
1125
1149
|
var LeakyBucketRateLimit = class {
|
|
@@ -1207,10 +1231,6 @@ var ClientTransport = class extends Transport {
|
|
|
1207
1231
|
* The options for this transport.
|
|
1208
1232
|
*/
|
|
1209
1233
|
options;
|
|
1210
|
-
/**
|
|
1211
|
-
* The map of reconnect promises for each client ID.
|
|
1212
|
-
*/
|
|
1213
|
-
inflightConnectionPromises;
|
|
1214
1234
|
retryBudget;
|
|
1215
1235
|
/**
|
|
1216
1236
|
* A flag indicating whether the transport should automatically reconnect
|
|
@@ -1229,352 +1249,279 @@ var ClientTransport = class extends Transport {
|
|
|
1229
1249
|
...defaultClientTransportOptions,
|
|
1230
1250
|
...providedOptions
|
|
1231
1251
|
};
|
|
1232
|
-
this.inflightConnectionPromises = /* @__PURE__ */ new Map();
|
|
1233
1252
|
this.retryBudget = new LeakyBucketRateLimit(this.options);
|
|
1234
1253
|
}
|
|
1235
1254
|
extendHandshake(options) {
|
|
1236
1255
|
this.handshakeExtensions = options;
|
|
1237
1256
|
}
|
|
1238
|
-
|
|
1239
|
-
if (this.getStatus()
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
}, this.options.sessionDisconnectGraceMs);
|
|
1251
|
-
const handshakeHandler = (data) => {
|
|
1252
|
-
const maybeSession = this.receiveHandshakeResponseMessage(data, conn);
|
|
1253
|
-
clearTimeout(handshakeTimeout);
|
|
1254
|
-
if (!maybeSession) {
|
|
1255
|
-
conn.close();
|
|
1256
|
-
return;
|
|
1257
|
-
} else {
|
|
1258
|
-
session = maybeSession;
|
|
1259
|
-
}
|
|
1260
|
-
conn.removeDataListener(handshakeHandler);
|
|
1261
|
-
conn.addDataListener((data2) => {
|
|
1262
|
-
const parsed = this.parseMsg(data2, conn);
|
|
1263
|
-
if (!parsed) {
|
|
1264
|
-
conn.telemetry?.span.setStatus({
|
|
1265
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1266
|
-
message: "message parse failure"
|
|
1267
|
-
});
|
|
1268
|
-
conn.close();
|
|
1269
|
-
return;
|
|
1270
|
-
}
|
|
1271
|
-
this.handleMsg(parsed, conn);
|
|
1257
|
+
tryReconnecting(to) {
|
|
1258
|
+
if (this.reconnectOnConnectionDrop && this.getStatus() === "open") {
|
|
1259
|
+
this.connect(to);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
send(to, msg) {
|
|
1263
|
+
if (this.getStatus() === "closed") {
|
|
1264
|
+
const err = "transport is closed, cant send";
|
|
1265
|
+
this.log?.error(err, {
|
|
1266
|
+
clientId: this.clientId,
|
|
1267
|
+
transportMessage: msg,
|
|
1268
|
+
tags: ["invariant-violation"]
|
|
1272
1269
|
});
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1270
|
+
throw new Error(err);
|
|
1271
|
+
}
|
|
1272
|
+
let session = this.sessions.get(to);
|
|
1273
|
+
if (!session) {
|
|
1274
|
+
session = this.createUnconnectedSession(to);
|
|
1275
|
+
}
|
|
1276
|
+
return session.send(msg);
|
|
1277
|
+
}
|
|
1278
|
+
createUnconnectedSession(to) {
|
|
1279
|
+
const session = SessionStateGraph.entrypoints.NoConnection(
|
|
1280
|
+
to,
|
|
1281
|
+
this.clientId,
|
|
1282
|
+
{
|
|
1283
|
+
onSessionGracePeriodElapsed: () => {
|
|
1284
|
+
this.onSessionGracePeriodElapsed(session);
|
|
1287
1285
|
}
|
|
1288
|
-
|
|
1289
|
-
this.
|
|
1290
|
-
|
|
1291
|
-
|
|
1286
|
+
},
|
|
1287
|
+
this.options,
|
|
1288
|
+
currentProtocolVersion,
|
|
1289
|
+
this.log
|
|
1290
|
+
);
|
|
1291
|
+
this.updateSession(session);
|
|
1292
|
+
return session;
|
|
1293
|
+
}
|
|
1294
|
+
// listeners
|
|
1295
|
+
onConnectingFailed(session) {
|
|
1296
|
+
const noConnectionSession = super.onConnectingFailed(session);
|
|
1297
|
+
this.tryReconnecting(noConnectionSession.to);
|
|
1298
|
+
return noConnectionSession;
|
|
1299
|
+
}
|
|
1300
|
+
onConnClosed(session) {
|
|
1301
|
+
const noConnectionSession = super.onConnClosed(session);
|
|
1302
|
+
this.tryReconnecting(noConnectionSession.to);
|
|
1303
|
+
return noConnectionSession;
|
|
1304
|
+
}
|
|
1305
|
+
onConnectionEstablished(session, conn) {
|
|
1306
|
+
const handshakingSession = SessionStateGraph.transition.ConnectingToHandshaking(session, conn, {
|
|
1307
|
+
onConnectionErrored: (err) => {
|
|
1308
|
+
const errStr = coerceErrorString(err);
|
|
1309
|
+
this.log?.error(
|
|
1310
|
+
`connection to ${handshakingSession.to} errored during handshake: ${errStr}`,
|
|
1311
|
+
handshakingSession.loggingMetadata
|
|
1312
|
+
);
|
|
1313
|
+
},
|
|
1314
|
+
onConnectionClosed: () => {
|
|
1315
|
+
this.log?.warn(
|
|
1316
|
+
`connection to ${handshakingSession.to} closed during handshake`,
|
|
1317
|
+
handshakingSession.loggingMetadata
|
|
1318
|
+
);
|
|
1319
|
+
this.onConnClosed(handshakingSession);
|
|
1320
|
+
},
|
|
1321
|
+
onHandshake: (msg) => {
|
|
1322
|
+
this.onHandshakeResponse(handshakingSession, msg);
|
|
1323
|
+
},
|
|
1324
|
+
onInvalidHandshake: (reason) => {
|
|
1325
|
+
this.log?.error(
|
|
1326
|
+
`invalid handshake: ${reason}`,
|
|
1327
|
+
handshakingSession.loggingMetadata
|
|
1328
|
+
);
|
|
1329
|
+
this.deleteSession(session);
|
|
1330
|
+
this.protocolError(ProtocolError.HandshakeFailed, reason);
|
|
1331
|
+
},
|
|
1332
|
+
onHandshakeTimeout: () => {
|
|
1333
|
+
this.log?.error(
|
|
1334
|
+
`connection to ${handshakingSession.to} timed out during handshake`,
|
|
1335
|
+
handshakingSession.loggingMetadata
|
|
1336
|
+
);
|
|
1337
|
+
this.onConnClosed(handshakingSession);
|
|
1292
1338
|
}
|
|
1293
1339
|
});
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
...conn.loggingMetadata,
|
|
1303
|
-
...session?.loggingMetadata,
|
|
1304
|
-
clientId: this.clientId,
|
|
1305
|
-
connectedTo: to
|
|
1306
|
-
}
|
|
1307
|
-
);
|
|
1340
|
+
this.updateSession(handshakingSession);
|
|
1341
|
+
void this.sendHandshake(handshakingSession);
|
|
1342
|
+
return handshakingSession;
|
|
1343
|
+
}
|
|
1344
|
+
rejectHandshakeResponse(session, reason, metadata) {
|
|
1345
|
+
session.conn.telemetry?.span.setStatus({
|
|
1346
|
+
code: import_api3.SpanStatusCode.ERROR,
|
|
1347
|
+
message: reason
|
|
1308
1348
|
});
|
|
1349
|
+
this.log?.warn(reason, metadata);
|
|
1350
|
+
this.deleteSession(session);
|
|
1309
1351
|
}
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
});
|
|
1317
|
-
this.protocolError(
|
|
1318
|
-
ProtocolError.HandshakeFailed,
|
|
1319
|
-
"received non-transport message"
|
|
1320
|
-
);
|
|
1321
|
-
return false;
|
|
1322
|
-
}
|
|
1323
|
-
if (!import_value2.Value.Check(ControlMessageHandshakeResponseSchema, parsed.payload)) {
|
|
1324
|
-
conn.telemetry?.span.setStatus({
|
|
1325
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1326
|
-
message: "invalid handshake response"
|
|
1327
|
-
});
|
|
1328
|
-
this.log?.warn(`received invalid handshake resp`, {
|
|
1329
|
-
...conn.loggingMetadata,
|
|
1330
|
-
clientId: this.clientId,
|
|
1331
|
-
connectedTo: parsed.from,
|
|
1332
|
-
transportMessage: parsed,
|
|
1352
|
+
onHandshakeResponse(session, msg) {
|
|
1353
|
+
if (!import_value2.Value.Check(ControlMessageHandshakeResponseSchema, msg.payload)) {
|
|
1354
|
+
const reason = `received invalid handshake response`;
|
|
1355
|
+
this.rejectHandshakeResponse(session, reason, {
|
|
1356
|
+
...session.loggingMetadata,
|
|
1357
|
+
transportMessage: msg,
|
|
1333
1358
|
validationErrors: [
|
|
1334
|
-
...import_value2.Value.Errors(
|
|
1335
|
-
ControlMessageHandshakeResponseSchema,
|
|
1336
|
-
parsed.payload
|
|
1337
|
-
)
|
|
1359
|
+
...import_value2.Value.Errors(ControlMessageHandshakeResponseSchema, msg.payload)
|
|
1338
1360
|
]
|
|
1339
1361
|
});
|
|
1340
|
-
|
|
1341
|
-
ProtocolError.HandshakeFailed,
|
|
1342
|
-
"invalid handshake resp"
|
|
1343
|
-
);
|
|
1344
|
-
return false;
|
|
1362
|
+
return;
|
|
1345
1363
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
});
|
|
1364
|
+
if (!msg.payload.status.ok) {
|
|
1365
|
+
const retriable = msg.payload.status.code ? import_value2.Value.Check(
|
|
1366
|
+
HandshakeErrorRetriableResponseCodes,
|
|
1367
|
+
msg.payload.status.code
|
|
1368
|
+
) : false;
|
|
1369
|
+
const reason = `handshake failed: ${msg.payload.status.reason}`;
|
|
1370
|
+
this.rejectHandshakeResponse(session, reason, {
|
|
1371
|
+
...session.loggingMetadata,
|
|
1372
|
+
transportMessage: msg
|
|
1373
|
+
});
|
|
1374
|
+
if (retriable) {
|
|
1375
|
+
this.tryReconnecting(session.to);
|
|
1359
1376
|
} else {
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
message: "handshake rejected"
|
|
1363
|
-
});
|
|
1377
|
+
this.deleteSession(session);
|
|
1378
|
+
this.protocolError(ProtocolError.HandshakeFailed, reason);
|
|
1364
1379
|
}
|
|
1365
|
-
|
|
1366
|
-
`received handshake rejection: ${parsed.payload.status.reason}`,
|
|
1367
|
-
{
|
|
1368
|
-
...conn.loggingMetadata,
|
|
1369
|
-
clientId: this.clientId,
|
|
1370
|
-
connectedTo: parsed.from,
|
|
1371
|
-
transportMessage: parsed
|
|
1372
|
-
}
|
|
1373
|
-
);
|
|
1374
|
-
this.protocolError(
|
|
1375
|
-
ProtocolError.HandshakeFailed,
|
|
1376
|
-
parsed.payload.status.reason
|
|
1377
|
-
);
|
|
1378
|
-
return false;
|
|
1380
|
+
return;
|
|
1379
1381
|
}
|
|
1380
|
-
if (
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
conn.telemetry?.span.setStatus({
|
|
1386
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1387
|
-
message: "session id mismatch"
|
|
1388
|
-
});
|
|
1389
|
-
this.log?.warn(`handshake from ${parsed.from} session id mismatch`, {
|
|
1390
|
-
...conn.loggingMetadata,
|
|
1391
|
-
clientId: this.clientId,
|
|
1392
|
-
connectedTo: parsed.from,
|
|
1393
|
-
transportMessage: parsed
|
|
1382
|
+
if (msg.payload.status.sessionId !== session.id) {
|
|
1383
|
+
const reason = `session id mismatch: expected ${session.id}, got ${msg.payload.status.sessionId}`;
|
|
1384
|
+
this.rejectHandshakeResponse(session, reason, {
|
|
1385
|
+
...session.loggingMetadata,
|
|
1386
|
+
transportMessage: msg
|
|
1394
1387
|
});
|
|
1395
|
-
|
|
1396
|
-
return false;
|
|
1388
|
+
return;
|
|
1397
1389
|
}
|
|
1398
|
-
this.log?.
|
|
1399
|
-
...
|
|
1400
|
-
|
|
1401
|
-
connectedTo: parsed.from,
|
|
1402
|
-
transportMessage: parsed
|
|
1390
|
+
this.log?.info(`handshake from ${msg.from} ok`, {
|
|
1391
|
+
...session.loggingMetadata,
|
|
1392
|
+
transportMessage: msg
|
|
1403
1393
|
});
|
|
1404
|
-
const
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1394
|
+
const connectedSession = SessionStateGraph.transition.HandshakingToConnected(session, {
|
|
1395
|
+
onConnectionErrored: (err) => {
|
|
1396
|
+
const errStr = coerceErrorString(err);
|
|
1397
|
+
this.log?.warn(
|
|
1398
|
+
`connection to ${connectedSession.to} errored: ${errStr}`,
|
|
1399
|
+
connectedSession.loggingMetadata
|
|
1400
|
+
);
|
|
1401
|
+
},
|
|
1402
|
+
onConnectionClosed: () => {
|
|
1403
|
+
this.log?.info(
|
|
1404
|
+
`connection to ${connectedSession.to} closed`,
|
|
1405
|
+
connectedSession.loggingMetadata
|
|
1406
|
+
);
|
|
1407
|
+
this.onConnClosed(connectedSession);
|
|
1408
|
+
},
|
|
1409
|
+
onMessage: (msg2) => this.handleMsg(msg2),
|
|
1410
|
+
onInvalidMessage: (reason) => {
|
|
1411
|
+
this.deleteSession(connectedSession);
|
|
1412
|
+
this.protocolError(ProtocolError.MessageOrderingViolated, reason);
|
|
1413
|
+
}
|
|
1408
1414
|
});
|
|
1409
|
-
this.
|
|
1410
|
-
this.retryBudget.startRestoringBudget(
|
|
1411
|
-
return session;
|
|
1415
|
+
this.updateSession(connectedSession);
|
|
1416
|
+
this.retryBudget.startRestoringBudget(connectedSession.to);
|
|
1412
1417
|
}
|
|
1413
1418
|
/**
|
|
1414
1419
|
* Manually attempts to connect to a client.
|
|
1415
1420
|
* @param to The client ID of the node to connect to.
|
|
1416
1421
|
*/
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1422
|
+
connect(to) {
|
|
1423
|
+
let session = this.sessions.get(to);
|
|
1424
|
+
session ??= this.createUnconnectedSession(to);
|
|
1425
|
+
if (session.state !== "NoConnection" /* NoConnection */) {
|
|
1426
|
+
this.log?.debug(
|
|
1427
|
+
`session to ${to} has state ${session.state}, skipping connect attempt`,
|
|
1428
|
+
session.loggingMetadata
|
|
1429
|
+
);
|
|
1423
1430
|
return;
|
|
1424
1431
|
}
|
|
1425
|
-
|
|
1426
|
-
if (!canProceedWithConnection()) {
|
|
1432
|
+
if (this.getStatus() !== "open") {
|
|
1427
1433
|
this.log?.info(
|
|
1428
1434
|
`transport state is no longer open, cancelling attempt to connect to ${to}`,
|
|
1429
|
-
|
|
1435
|
+
session.loggingMetadata
|
|
1430
1436
|
);
|
|
1431
1437
|
return;
|
|
1432
1438
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
this.protocolError(ProtocolError.RetriesExceeded, errMsg);
|
|
1440
|
-
return;
|
|
1441
|
-
}
|
|
1442
|
-
let sleep = Promise.resolve();
|
|
1443
|
-
const backoffMs = this.retryBudget.getBackoffMs(to);
|
|
1444
|
-
if (backoffMs > 0) {
|
|
1445
|
-
sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
1446
|
-
}
|
|
1447
|
-
this.log?.info(
|
|
1448
|
-
`attempting connection to ${to} (${backoffMs}ms backoff)`,
|
|
1449
|
-
{
|
|
1450
|
-
clientId: this.clientId,
|
|
1451
|
-
connectedTo: to
|
|
1452
|
-
}
|
|
1453
|
-
);
|
|
1454
|
-
this.retryBudget.consumeBudget(to);
|
|
1455
|
-
reconnectPromise = tracing_default.startActiveSpan("connect", async (span) => {
|
|
1456
|
-
try {
|
|
1457
|
-
span.addEvent("backoff", { backoffMs });
|
|
1458
|
-
await sleep;
|
|
1459
|
-
if (!canProceedWithConnection()) {
|
|
1460
|
-
throw new Error("transport state is no longer open");
|
|
1461
|
-
}
|
|
1462
|
-
span.addEvent("connecting");
|
|
1463
|
-
const conn = await this.createNewOutgoingConnection(to);
|
|
1464
|
-
if (!canProceedWithConnection()) {
|
|
1465
|
-
this.log?.info(
|
|
1466
|
-
`transport state is no longer open, closing pre-handshake connection to ${to}`,
|
|
1467
|
-
{
|
|
1468
|
-
...conn.loggingMetadata,
|
|
1469
|
-
clientId: this.clientId,
|
|
1470
|
-
connectedTo: to
|
|
1471
|
-
}
|
|
1472
|
-
);
|
|
1473
|
-
conn.close();
|
|
1474
|
-
throw new Error("transport state is no longer open");
|
|
1475
|
-
}
|
|
1476
|
-
span.addEvent("sending handshake");
|
|
1477
|
-
const ok = await this.sendHandshake(to, conn);
|
|
1478
|
-
if (!ok) {
|
|
1479
|
-
conn.close();
|
|
1480
|
-
throw new Error("failed to send handshake");
|
|
1481
|
-
}
|
|
1482
|
-
return conn;
|
|
1483
|
-
} catch (err) {
|
|
1484
|
-
const errStr = coerceErrorString(err);
|
|
1485
|
-
span.recordException(errStr);
|
|
1486
|
-
span.setStatus({ code: import_api4.SpanStatusCode.ERROR });
|
|
1487
|
-
throw err;
|
|
1488
|
-
} finally {
|
|
1489
|
-
span.end();
|
|
1490
|
-
}
|
|
1491
|
-
});
|
|
1492
|
-
this.inflightConnectionPromises.set(to, reconnectPromise);
|
|
1493
|
-
} else {
|
|
1494
|
-
this.log?.info(
|
|
1495
|
-
`attempting connection to ${to} (reusing previous attempt)`,
|
|
1496
|
-
{
|
|
1497
|
-
clientId: this.clientId,
|
|
1498
|
-
connectedTo: to
|
|
1499
|
-
}
|
|
1500
|
-
);
|
|
1439
|
+
if (!this.retryBudget.hasBudget(to)) {
|
|
1440
|
+
const budgetConsumed = this.retryBudget.getBudgetConsumed(to);
|
|
1441
|
+
const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
|
|
1442
|
+
this.log?.error(errMsg, session.loggingMetadata);
|
|
1443
|
+
this.protocolError(ProtocolError.RetriesExceeded, errMsg);
|
|
1444
|
+
return;
|
|
1501
1445
|
}
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
const errStr = coerceErrorString(error);
|
|
1507
|
-
if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {
|
|
1508
|
-
this.log?.warn(`connection to ${to} failed (${errStr})`, {
|
|
1509
|
-
clientId: this.clientId,
|
|
1510
|
-
connectedTo: to
|
|
1511
|
-
});
|
|
1512
|
-
} else {
|
|
1513
|
-
this.log?.warn(`connection to ${to} failed (${errStr}), retrying`, {
|
|
1514
|
-
clientId: this.clientId,
|
|
1515
|
-
connectedTo: to
|
|
1516
|
-
});
|
|
1517
|
-
await this.connect(to);
|
|
1518
|
-
}
|
|
1446
|
+
let sleep = Promise.resolve();
|
|
1447
|
+
const backoffMs = this.retryBudget.getBackoffMs(to);
|
|
1448
|
+
if (backoffMs > 0) {
|
|
1449
|
+
sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
1519
1450
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1451
|
+
this.log?.info(
|
|
1452
|
+
`attempting connection to ${to} (${backoffMs}ms backoff)`,
|
|
1453
|
+
session.loggingMetadata
|
|
1454
|
+
);
|
|
1455
|
+
this.retryBudget.consumeBudget(to);
|
|
1456
|
+
const reconnectPromise = tracing_default.startActiveSpan("connect", async (span) => {
|
|
1457
|
+
try {
|
|
1458
|
+
span.addEvent("backoff", { backoffMs });
|
|
1459
|
+
await sleep;
|
|
1460
|
+
if (this.getStatus() !== "open") {
|
|
1461
|
+
throw new Error("transport state is no longer open");
|
|
1462
|
+
}
|
|
1463
|
+
span.addEvent("connecting");
|
|
1464
|
+
return await this.createNewOutgoingConnection(to);
|
|
1465
|
+
} catch (err) {
|
|
1466
|
+
const errStr = coerceErrorString(err);
|
|
1467
|
+
span.recordException(errStr);
|
|
1468
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR });
|
|
1469
|
+
throw err;
|
|
1470
|
+
} finally {
|
|
1471
|
+
span.end();
|
|
1472
|
+
}
|
|
1531
1473
|
});
|
|
1474
|
+
const connectingSession = SessionStateGraph.transition.NoConnectionToConnecting(
|
|
1475
|
+
session,
|
|
1476
|
+
reconnectPromise,
|
|
1477
|
+
{
|
|
1478
|
+
onConnectionEstablished: (conn) => {
|
|
1479
|
+
this.log?.debug(
|
|
1480
|
+
`connection to ${connectingSession.to} established`,
|
|
1481
|
+
connectingSession.loggingMetadata
|
|
1482
|
+
);
|
|
1483
|
+
this.onConnectionEstablished(connectingSession, conn);
|
|
1484
|
+
},
|
|
1485
|
+
onConnectionFailed: (error) => {
|
|
1486
|
+
const errStr = coerceErrorString(error);
|
|
1487
|
+
this.log?.error(
|
|
1488
|
+
`error connecting to ${connectingSession.to}: ${errStr}`,
|
|
1489
|
+
connectingSession.loggingMetadata
|
|
1490
|
+
);
|
|
1491
|
+
this.onConnectingFailed(connectingSession);
|
|
1492
|
+
},
|
|
1493
|
+
onConnectionTimeout: () => {
|
|
1494
|
+
this.log?.error(
|
|
1495
|
+
`connection to ${connectingSession.to} timed out`,
|
|
1496
|
+
connectingSession.loggingMetadata
|
|
1497
|
+
);
|
|
1498
|
+
this.onConnectingFailed(connectingSession);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
);
|
|
1502
|
+
this.updateSession(connectingSession);
|
|
1532
1503
|
}
|
|
1533
|
-
async sendHandshake(
|
|
1504
|
+
async sendHandshake(session) {
|
|
1534
1505
|
let metadata = void 0;
|
|
1535
1506
|
if (this.handshakeExtensions) {
|
|
1536
1507
|
metadata = await this.handshakeExtensions.construct();
|
|
1537
|
-
if (!import_value2.Value.Check(this.handshakeExtensions.schema, metadata)) {
|
|
1538
|
-
this.log?.error(`constructed handshake metadata did not match schema`, {
|
|
1539
|
-
...conn.loggingMetadata,
|
|
1540
|
-
clientId: this.clientId,
|
|
1541
|
-
connectedTo: to,
|
|
1542
|
-
validationErrors: [
|
|
1543
|
-
...import_value2.Value.Errors(this.handshakeExtensions.schema, metadata)
|
|
1544
|
-
],
|
|
1545
|
-
tags: ["invariant-violation"]
|
|
1546
|
-
});
|
|
1547
|
-
this.protocolError(
|
|
1548
|
-
ProtocolError.HandshakeFailed,
|
|
1549
|
-
"handshake metadata did not match schema"
|
|
1550
|
-
);
|
|
1551
|
-
conn.telemetry?.span.setStatus({
|
|
1552
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1553
|
-
message: "handshake meta mismatch"
|
|
1554
|
-
});
|
|
1555
|
-
return false;
|
|
1556
|
-
}
|
|
1557
1508
|
}
|
|
1558
|
-
const { session } = this.getOrCreateSession({ to, handshakingConn: conn });
|
|
1559
1509
|
const requestMsg = handshakeRequestMessage({
|
|
1560
1510
|
from: this.clientId,
|
|
1561
|
-
to,
|
|
1511
|
+
to: session.to,
|
|
1562
1512
|
sessionId: session.id,
|
|
1563
1513
|
expectedSessionState: {
|
|
1564
|
-
|
|
1565
|
-
|
|
1514
|
+
nextExpectedSeq: session.ack,
|
|
1515
|
+
nextSentSeq: session.nextSeq()
|
|
1566
1516
|
},
|
|
1567
1517
|
metadata,
|
|
1568
1518
|
tracing: getPropagationContext(session.telemetry.ctx)
|
|
1569
1519
|
});
|
|
1570
|
-
this.log?.debug(`sending handshake request to ${to}`, {
|
|
1571
|
-
...
|
|
1572
|
-
clientId: this.clientId,
|
|
1573
|
-
connectedTo: to,
|
|
1520
|
+
this.log?.debug(`sending handshake request to ${session.to}`, {
|
|
1521
|
+
...session.loggingMetadata,
|
|
1574
1522
|
transportMessage: requestMsg
|
|
1575
1523
|
});
|
|
1576
|
-
|
|
1577
|
-
return true;
|
|
1524
|
+
session.sendHandshake(requestMsg);
|
|
1578
1525
|
}
|
|
1579
1526
|
close() {
|
|
1580
1527
|
this.retryBudget.close();
|
|
@@ -1583,7 +1530,7 @@ var ClientTransport = class extends Transport {
|
|
|
1583
1530
|
};
|
|
1584
1531
|
|
|
1585
1532
|
// transport/server.ts
|
|
1586
|
-
var
|
|
1533
|
+
var import_api4 = require("@opentelemetry/api");
|
|
1587
1534
|
var import_value3 = require("@sinclair/typebox/value");
|
|
1588
1535
|
var ServerTransport = class extends Transport {
|
|
1589
1536
|
/**
|
|
@@ -1597,22 +1544,52 @@ var ServerTransport = class extends Transport {
|
|
|
1597
1544
|
/**
|
|
1598
1545
|
* A map of session handshake data for each session.
|
|
1599
1546
|
*/
|
|
1600
|
-
sessionHandshakeMetadata;
|
|
1547
|
+
sessionHandshakeMetadata = /* @__PURE__ */ new Map();
|
|
1548
|
+
pendingSessions = /* @__PURE__ */ new Set();
|
|
1601
1549
|
constructor(clientId, providedOptions) {
|
|
1602
1550
|
super(clientId, providedOptions);
|
|
1603
1551
|
this.options = {
|
|
1604
1552
|
...defaultServerTransportOptions,
|
|
1605
1553
|
...providedOptions
|
|
1606
1554
|
};
|
|
1607
|
-
this.sessionHandshakeMetadata = /* @__PURE__ */ new WeakMap();
|
|
1608
1555
|
this.log?.info(`initiated server transport`, {
|
|
1609
1556
|
clientId: this.clientId,
|
|
1610
|
-
protocolVersion:
|
|
1557
|
+
protocolVersion: currentProtocolVersion
|
|
1611
1558
|
});
|
|
1612
1559
|
}
|
|
1613
1560
|
extendHandshake(options) {
|
|
1614
1561
|
this.handshakeExtensions = options;
|
|
1615
1562
|
}
|
|
1563
|
+
send(to, msg) {
|
|
1564
|
+
if (this.getStatus() === "closed") {
|
|
1565
|
+
const err = "transport is closed, cant send";
|
|
1566
|
+
this.log?.error(err, {
|
|
1567
|
+
clientId: this.clientId,
|
|
1568
|
+
transportMessage: msg,
|
|
1569
|
+
tags: ["invariant-violation"]
|
|
1570
|
+
});
|
|
1571
|
+
throw new Error(err);
|
|
1572
|
+
}
|
|
1573
|
+
const session = this.sessions.get(to);
|
|
1574
|
+
if (!session) {
|
|
1575
|
+
const err = `session to ${to} does not exist`;
|
|
1576
|
+
this.log?.error(err, {
|
|
1577
|
+
clientId: this.clientId,
|
|
1578
|
+
transportMessage: msg,
|
|
1579
|
+
tags: ["invariant-violation"]
|
|
1580
|
+
});
|
|
1581
|
+
throw new Error(err);
|
|
1582
|
+
}
|
|
1583
|
+
return session.send(msg);
|
|
1584
|
+
}
|
|
1585
|
+
deletePendingSession(pendingSession) {
|
|
1586
|
+
pendingSession.close();
|
|
1587
|
+
this.pendingSessions.delete(pendingSession);
|
|
1588
|
+
}
|
|
1589
|
+
deleteSession(session) {
|
|
1590
|
+
this.sessionHandshakeMetadata.delete(session.to);
|
|
1591
|
+
super.deleteSession(session);
|
|
1592
|
+
}
|
|
1616
1593
|
handleConnection(conn) {
|
|
1617
1594
|
if (this.getStatus() !== "open")
|
|
1618
1595
|
return;
|
|
@@ -1620,281 +1597,370 @@ var ServerTransport = class extends Transport {
|
|
|
1620
1597
|
...conn.loggingMetadata,
|
|
1621
1598
|
clientId: this.clientId
|
|
1622
1599
|
});
|
|
1623
|
-
let
|
|
1624
|
-
const
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1600
|
+
let receivedHandshake = false;
|
|
1601
|
+
const pendingSession = SessionStateGraph.entrypoints.WaitingForHandshake(
|
|
1602
|
+
this.clientId,
|
|
1603
|
+
conn,
|
|
1604
|
+
{
|
|
1605
|
+
onConnectionClosed: () => {
|
|
1606
|
+
this.log?.warn(
|
|
1607
|
+
`connection from unknown closed before handshake finished`,
|
|
1608
|
+
pendingSession.loggingMetadata
|
|
1609
|
+
);
|
|
1610
|
+
this.deletePendingSession(pendingSession);
|
|
1611
|
+
},
|
|
1612
|
+
onConnectionErrored: (err) => {
|
|
1613
|
+
const errorString = coerceErrorString(err);
|
|
1614
|
+
this.log?.warn(
|
|
1615
|
+
`connection from unknown errored before handshake finished: ${errorString}`,
|
|
1616
|
+
pendingSession.loggingMetadata
|
|
1617
|
+
);
|
|
1618
|
+
this.deletePendingSession(pendingSession);
|
|
1619
|
+
},
|
|
1620
|
+
onHandshakeTimeout: () => {
|
|
1621
|
+
this.log?.warn(
|
|
1622
|
+
`connection from unknown timed out before handshake finished`,
|
|
1623
|
+
pendingSession.loggingMetadata
|
|
1624
|
+
);
|
|
1625
|
+
this.deletePendingSession(pendingSession);
|
|
1626
|
+
},
|
|
1627
|
+
onHandshake: (msg) => {
|
|
1628
|
+
if (receivedHandshake) {
|
|
1629
|
+
this.log?.error(
|
|
1630
|
+
`received multiple handshake messages from pending session`,
|
|
1631
|
+
{
|
|
1632
|
+
...pendingSession.loggingMetadata,
|
|
1633
|
+
connectedTo: msg.from,
|
|
1634
|
+
transportMessage: msg
|
|
1635
|
+
}
|
|
1636
|
+
);
|
|
1637
|
+
this.deletePendingSession(pendingSession);
|
|
1655
1638
|
return;
|
|
1656
1639
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
dataHandler(data2);
|
|
1668
|
-
}
|
|
1669
|
-
conn.removeDataListener(handshakeHandler);
|
|
1670
|
-
conn.addDataListener(dataHandler);
|
|
1671
|
-
buffer.length = 0;
|
|
1640
|
+
receivedHandshake = true;
|
|
1641
|
+
void this.onHandshakeRequest(pendingSession, msg);
|
|
1642
|
+
},
|
|
1643
|
+
onInvalidHandshake: (reason) => {
|
|
1644
|
+
this.log?.error(
|
|
1645
|
+
`invalid handshake: ${reason}`,
|
|
1646
|
+
pendingSession.loggingMetadata
|
|
1647
|
+
);
|
|
1648
|
+
this.deletePendingSession(pendingSession);
|
|
1649
|
+
this.protocolError(ProtocolError.HandshakeFailed, reason);
|
|
1672
1650
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
return;
|
|
1679
|
-
this.log?.info(`connection to ${client()} disconnected`, {
|
|
1680
|
-
...conn.loggingMetadata,
|
|
1681
|
-
clientId: this.clientId
|
|
1682
|
-
});
|
|
1683
|
-
this.onDisconnect(conn, session);
|
|
1684
|
-
});
|
|
1685
|
-
conn.addErrorListener((err) => {
|
|
1686
|
-
conn.telemetry?.span.setStatus({
|
|
1687
|
-
code: import_api5.SpanStatusCode.ERROR,
|
|
1688
|
-
message: "connection error"
|
|
1689
|
-
});
|
|
1690
|
-
if (!session)
|
|
1691
|
-
return;
|
|
1692
|
-
this.log?.warn(
|
|
1693
|
-
`connection to ${client()} got an error: ${coerceErrorString(err)}`,
|
|
1694
|
-
{ ...conn.loggingMetadata, clientId: this.clientId }
|
|
1695
|
-
);
|
|
1696
|
-
});
|
|
1697
|
-
}
|
|
1698
|
-
async validateHandshakeMetadata(conn, session, rawMetadata, from) {
|
|
1699
|
-
let parsedMetadata = {};
|
|
1700
|
-
if (this.handshakeExtensions) {
|
|
1701
|
-
if (!import_value3.Value.Check(this.handshakeExtensions.schema, rawMetadata)) {
|
|
1702
|
-
conn.telemetry?.span.setStatus({
|
|
1703
|
-
code: import_api5.SpanStatusCode.ERROR,
|
|
1704
|
-
message: "malformed handshake meta"
|
|
1705
|
-
});
|
|
1706
|
-
const reason = "received malformed handshake metadata";
|
|
1707
|
-
const responseMsg = handshakeResponseMessage({
|
|
1708
|
-
from: this.clientId,
|
|
1709
|
-
to: from,
|
|
1710
|
-
status: {
|
|
1711
|
-
ok: false,
|
|
1712
|
-
reason
|
|
1713
|
-
}
|
|
1714
|
-
});
|
|
1715
|
-
conn.send(this.codec.toBuffer(responseMsg));
|
|
1716
|
-
this.log?.warn(`received malformed handshake metadata from ${from}`, {
|
|
1717
|
-
...conn.loggingMetadata,
|
|
1718
|
-
clientId: this.clientId,
|
|
1719
|
-
validationErrors: [
|
|
1720
|
-
...import_value3.Value.Errors(this.handshakeExtensions.schema, rawMetadata)
|
|
1721
|
-
]
|
|
1722
|
-
});
|
|
1723
|
-
this.protocolError(ProtocolError.HandshakeFailed, reason);
|
|
1724
|
-
return false;
|
|
1725
|
-
}
|
|
1726
|
-
const previousParsedMetadata = session ? this.sessionHandshakeMetadata.get(session) : void 0;
|
|
1727
|
-
parsedMetadata = await this.handshakeExtensions.validate(
|
|
1728
|
-
rawMetadata,
|
|
1729
|
-
previousParsedMetadata
|
|
1730
|
-
);
|
|
1731
|
-
if (parsedMetadata === false) {
|
|
1732
|
-
const reason = "rejected by handshake handler";
|
|
1733
|
-
conn.telemetry?.span.setStatus({
|
|
1734
|
-
code: import_api5.SpanStatusCode.ERROR,
|
|
1735
|
-
message: reason
|
|
1736
|
-
});
|
|
1737
|
-
const responseMsg = handshakeResponseMessage({
|
|
1738
|
-
from: this.clientId,
|
|
1739
|
-
to: from,
|
|
1740
|
-
status: {
|
|
1741
|
-
ok: false,
|
|
1742
|
-
reason
|
|
1743
|
-
}
|
|
1744
|
-
});
|
|
1745
|
-
conn.send(this.codec.toBuffer(responseMsg));
|
|
1746
|
-
this.log?.warn(`rejected handshake from ${from}`, {
|
|
1747
|
-
...conn.loggingMetadata,
|
|
1748
|
-
clientId: this.clientId
|
|
1749
|
-
});
|
|
1750
|
-
this.protocolError(ProtocolError.HandshakeFailed, reason);
|
|
1751
|
-
return false;
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
return parsedMetadata;
|
|
1651
|
+
},
|
|
1652
|
+
this.options,
|
|
1653
|
+
this.log
|
|
1654
|
+
);
|
|
1655
|
+
this.pendingSessions.add(pendingSession);
|
|
1755
1656
|
}
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
ProtocolError.HandshakeFailed,
|
|
1765
|
-
"received non-transport message"
|
|
1766
|
-
);
|
|
1767
|
-
return false;
|
|
1768
|
-
}
|
|
1769
|
-
if (!import_value3.Value.Check(ControlMessageHandshakeRequestSchema, parsed.payload)) {
|
|
1770
|
-
conn.telemetry?.span.setStatus({
|
|
1771
|
-
code: import_api5.SpanStatusCode.ERROR,
|
|
1772
|
-
message: "invalid handshake request"
|
|
1773
|
-
});
|
|
1774
|
-
const reason = "received invalid handshake msg";
|
|
1775
|
-
const responseMsg2 = handshakeResponseMessage({
|
|
1657
|
+
rejectHandshakeRequest(session, to, reason, code, metadata) {
|
|
1658
|
+
session.conn.telemetry?.span.setStatus({
|
|
1659
|
+
code: import_api4.SpanStatusCode.ERROR,
|
|
1660
|
+
message: reason
|
|
1661
|
+
});
|
|
1662
|
+
this.log?.warn(reason, metadata);
|
|
1663
|
+
session.sendHandshake(
|
|
1664
|
+
handshakeResponseMessage({
|
|
1776
1665
|
from: this.clientId,
|
|
1777
|
-
to
|
|
1666
|
+
to,
|
|
1778
1667
|
status: {
|
|
1779
1668
|
ok: false,
|
|
1669
|
+
code,
|
|
1780
1670
|
reason
|
|
1781
1671
|
}
|
|
1782
|
-
})
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1672
|
+
})
|
|
1673
|
+
);
|
|
1674
|
+
this.protocolError(ProtocolError.HandshakeFailed, reason);
|
|
1675
|
+
this.deletePendingSession(session);
|
|
1676
|
+
}
|
|
1677
|
+
async onHandshakeRequest(session, msg) {
|
|
1678
|
+
if (!import_value3.Value.Check(ControlMessageHandshakeRequestSchema, msg.payload)) {
|
|
1679
|
+
this.rejectHandshakeRequest(
|
|
1680
|
+
session,
|
|
1681
|
+
msg.from,
|
|
1682
|
+
"received invalid handshake request",
|
|
1683
|
+
"MALFORMED_HANDSHAKE",
|
|
1684
|
+
{
|
|
1685
|
+
...session.loggingMetadata,
|
|
1686
|
+
transportMessage: msg,
|
|
1687
|
+
connectedTo: msg.from,
|
|
1688
|
+
validationErrors: [
|
|
1689
|
+
...import_value3.Value.Errors(ControlMessageHandshakeRequestSchema, msg.payload)
|
|
1690
|
+
]
|
|
1691
|
+
}
|
|
1797
1692
|
);
|
|
1798
|
-
return
|
|
1693
|
+
return;
|
|
1799
1694
|
}
|
|
1800
|
-
const gotVersion =
|
|
1801
|
-
if (gotVersion
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
ok: false,
|
|
1812
|
-
reason
|
|
1695
|
+
const gotVersion = msg.payload.protocolVersion;
|
|
1696
|
+
if (!acceptedProtocolVersions.includes(gotVersion)) {
|
|
1697
|
+
this.rejectHandshakeRequest(
|
|
1698
|
+
session,
|
|
1699
|
+
msg.from,
|
|
1700
|
+
`expected protocol version oneof [${acceptedProtocolVersions.toString()}], got ${gotVersion}`,
|
|
1701
|
+
"PROTOCOL_VERSION_MISMATCH",
|
|
1702
|
+
{
|
|
1703
|
+
...session.loggingMetadata,
|
|
1704
|
+
connectedTo: msg.from,
|
|
1705
|
+
transportMessage: msg
|
|
1813
1706
|
}
|
|
1814
|
-
});
|
|
1815
|
-
conn.send(this.codec.toBuffer(responseMsg2));
|
|
1816
|
-
this.log?.warn(
|
|
1817
|
-
`received handshake msg with incompatible protocol version (got: ${gotVersion}, expected: ${PROTOCOL_VERSION})`,
|
|
1818
|
-
{ ...conn.loggingMetadata, clientId: this.clientId }
|
|
1819
1707
|
);
|
|
1820
|
-
|
|
1821
|
-
return false;
|
|
1708
|
+
return;
|
|
1822
1709
|
}
|
|
1823
|
-
|
|
1710
|
+
let oldSession = this.sessions.get(msg.from);
|
|
1824
1711
|
const parsedMetadata = await this.validateHandshakeMetadata(
|
|
1825
|
-
|
|
1712
|
+
session,
|
|
1826
1713
|
oldSession,
|
|
1827
|
-
|
|
1828
|
-
|
|
1714
|
+
msg.payload.metadata,
|
|
1715
|
+
msg.from
|
|
1829
1716
|
);
|
|
1830
1717
|
if (parsedMetadata === false) {
|
|
1831
|
-
return
|
|
1718
|
+
return;
|
|
1832
1719
|
}
|
|
1833
|
-
let session;
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1720
|
+
let connectCase = "new session";
|
|
1721
|
+
if (oldSession && oldSession.id === msg.payload.sessionId) {
|
|
1722
|
+
connectCase = "transparent reconnection";
|
|
1723
|
+
const clientNextExpectedSeq = msg.payload.expectedSessionState.nextExpectedSeq;
|
|
1724
|
+
const clientNextSentSeq = msg.payload.expectedSessionState.nextSentSeq ?? 0;
|
|
1725
|
+
const ourNextSeq = oldSession.nextSeq();
|
|
1726
|
+
const ourAck = oldSession.ack;
|
|
1727
|
+
if (clientNextSentSeq > ourAck) {
|
|
1728
|
+
this.rejectHandshakeRequest(
|
|
1729
|
+
session,
|
|
1730
|
+
msg.from,
|
|
1731
|
+
`client is in the future: server wanted next message to be ${ourAck} but client would have sent ${clientNextSentSeq}`,
|
|
1732
|
+
"SESSION_STATE_MISMATCH",
|
|
1733
|
+
{
|
|
1734
|
+
...session.loggingMetadata,
|
|
1735
|
+
connectedTo: msg.from,
|
|
1736
|
+
transportMessage: msg
|
|
1737
|
+
}
|
|
1738
|
+
);
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
if (ourNextSeq > clientNextExpectedSeq) {
|
|
1742
|
+
this.rejectHandshakeRequest(
|
|
1743
|
+
session,
|
|
1744
|
+
msg.from,
|
|
1745
|
+
`server is in the future: client wanted next message to be ${clientNextExpectedSeq} but server would have sent ${ourNextSeq}`,
|
|
1746
|
+
"SESSION_STATE_MISMATCH",
|
|
1747
|
+
{
|
|
1748
|
+
...session.loggingMetadata,
|
|
1749
|
+
connectedTo: msg.from,
|
|
1750
|
+
transportMessage: msg
|
|
1751
|
+
}
|
|
1752
|
+
);
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
if (oldSession.state === "Connected" /* Connected */) {
|
|
1756
|
+
const noConnectionSession = SessionStateGraph.transition.ConnectedToNoConnection(oldSession, {
|
|
1757
|
+
onSessionGracePeriodElapsed: () => {
|
|
1758
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1759
|
+
}
|
|
1852
1760
|
});
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
ok: false,
|
|
1859
|
-
reason
|
|
1761
|
+
oldSession = noConnectionSession;
|
|
1762
|
+
} else if (oldSession.state === "Handshaking" /* Handshaking */) {
|
|
1763
|
+
const noConnectionSession = SessionStateGraph.transition.HandshakingToNoConnection(oldSession, {
|
|
1764
|
+
onSessionGracePeriodElapsed: () => {
|
|
1765
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1860
1766
|
}
|
|
1861
1767
|
});
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1768
|
+
oldSession = noConnectionSession;
|
|
1769
|
+
} else if (oldSession.state === "Connecting" /* Connecting */) {
|
|
1770
|
+
const noConnectionSession = SessionStateGraph.transition.ConnectingToNoConnection(oldSession, {
|
|
1771
|
+
onSessionGracePeriodElapsed: () => {
|
|
1772
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1773
|
+
}
|
|
1774
|
+
});
|
|
1775
|
+
oldSession = noConnectionSession;
|
|
1869
1776
|
}
|
|
1870
|
-
|
|
1871
|
-
|
|
1777
|
+
this.updateSession(oldSession);
|
|
1778
|
+
} else if (oldSession) {
|
|
1779
|
+
connectCase = "hard reconnection";
|
|
1780
|
+
this.deleteSession(oldSession);
|
|
1781
|
+
oldSession = void 0;
|
|
1872
1782
|
} else {
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1783
|
+
connectCase = "unknown session";
|
|
1784
|
+
const clientNextExpectedSeq = msg.payload.expectedSessionState.nextExpectedSeq;
|
|
1785
|
+
const clientNextSentSeq = msg.payload.expectedSessionState.nextSentSeq ?? 0;
|
|
1786
|
+
if (clientNextSentSeq > 0 || clientNextExpectedSeq > 0) {
|
|
1787
|
+
this.rejectHandshakeRequest(
|
|
1788
|
+
session,
|
|
1789
|
+
msg.from,
|
|
1790
|
+
`client is trying to reconnect to a session the server don't know about: ${msg.payload.sessionId}`,
|
|
1791
|
+
"SESSION_STATE_MISMATCH",
|
|
1792
|
+
{
|
|
1793
|
+
...session.loggingMetadata,
|
|
1794
|
+
connectedTo: msg.from,
|
|
1795
|
+
transportMessage: msg
|
|
1796
|
+
}
|
|
1797
|
+
);
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1881
1800
|
}
|
|
1882
|
-
|
|
1883
|
-
this.log?.
|
|
1884
|
-
`handshake from ${
|
|
1885
|
-
|
|
1801
|
+
const sessionId = msg.payload.sessionId;
|
|
1802
|
+
this.log?.info(
|
|
1803
|
+
`handshake from ${msg.from} ok (${connectCase}), responding with handshake success`,
|
|
1804
|
+
{
|
|
1805
|
+
...session.loggingMetadata,
|
|
1806
|
+
connectedTo: msg.from
|
|
1807
|
+
}
|
|
1886
1808
|
);
|
|
1887
1809
|
const responseMsg = handshakeResponseMessage({
|
|
1888
1810
|
from: this.clientId,
|
|
1889
|
-
to:
|
|
1811
|
+
to: msg.from,
|
|
1890
1812
|
status: {
|
|
1891
1813
|
ok: true,
|
|
1892
|
-
sessionId
|
|
1814
|
+
sessionId
|
|
1893
1815
|
}
|
|
1894
1816
|
});
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1817
|
+
session.sendHandshake(responseMsg);
|
|
1818
|
+
const connectedSession = SessionStateGraph.transition.WaitingForHandshakeToConnected(
|
|
1819
|
+
session,
|
|
1820
|
+
// by this point oldSession is either no connection or we dont have an old session
|
|
1821
|
+
oldSession,
|
|
1822
|
+
sessionId,
|
|
1823
|
+
msg.from,
|
|
1824
|
+
msg.tracing,
|
|
1825
|
+
{
|
|
1826
|
+
onConnectionErrored: (err) => {
|
|
1827
|
+
const errStr = coerceErrorString(err);
|
|
1828
|
+
this.log?.warn(
|
|
1829
|
+
`connection to ${connectedSession.to} errored: ${errStr}`,
|
|
1830
|
+
connectedSession.loggingMetadata
|
|
1831
|
+
);
|
|
1832
|
+
},
|
|
1833
|
+
onConnectionClosed: () => {
|
|
1834
|
+
this.log?.info(
|
|
1835
|
+
`connection to ${connectedSession.to} closed`,
|
|
1836
|
+
connectedSession.loggingMetadata
|
|
1837
|
+
);
|
|
1838
|
+
this.onConnClosed(connectedSession);
|
|
1839
|
+
},
|
|
1840
|
+
onMessage: (msg2) => this.handleMsg(msg2),
|
|
1841
|
+
onInvalidMessage: (reason) => {
|
|
1842
|
+
this.protocolError(ProtocolError.MessageOrderingViolated, reason);
|
|
1843
|
+
this.deleteSession(connectedSession);
|
|
1844
|
+
}
|
|
1845
|
+
},
|
|
1846
|
+
gotVersion
|
|
1847
|
+
);
|
|
1848
|
+
this.sessionHandshakeMetadata.set(connectedSession.to, parsedMetadata);
|
|
1849
|
+
this.updateSession(connectedSession);
|
|
1850
|
+
this.pendingSessions.delete(session);
|
|
1851
|
+
connectedSession.startActiveHeartbeat();
|
|
1852
|
+
}
|
|
1853
|
+
async validateHandshakeMetadata(handshakingSession, existingSession, rawMetadata, from) {
|
|
1854
|
+
let parsedMetadata = {};
|
|
1855
|
+
if (this.handshakeExtensions) {
|
|
1856
|
+
if (!import_value3.Value.Check(this.handshakeExtensions.schema, rawMetadata)) {
|
|
1857
|
+
this.rejectHandshakeRequest(
|
|
1858
|
+
handshakingSession,
|
|
1859
|
+
from,
|
|
1860
|
+
"received malformed handshake metadata",
|
|
1861
|
+
"MALFORMED_HANDSHAKE_META",
|
|
1862
|
+
{
|
|
1863
|
+
...handshakingSession.loggingMetadata,
|
|
1864
|
+
connectedTo: from,
|
|
1865
|
+
validationErrors: [
|
|
1866
|
+
...import_value3.Value.Errors(this.handshakeExtensions.schema, rawMetadata)
|
|
1867
|
+
]
|
|
1868
|
+
}
|
|
1869
|
+
);
|
|
1870
|
+
return false;
|
|
1871
|
+
}
|
|
1872
|
+
const previousParsedMetadata = existingSession ? this.sessionHandshakeMetadata.get(existingSession.to) : void 0;
|
|
1873
|
+
parsedMetadata = await this.handshakeExtensions.validate(
|
|
1874
|
+
rawMetadata,
|
|
1875
|
+
previousParsedMetadata
|
|
1876
|
+
);
|
|
1877
|
+
if (parsedMetadata === false) {
|
|
1878
|
+
this.rejectHandshakeRequest(
|
|
1879
|
+
handshakingSession,
|
|
1880
|
+
from,
|
|
1881
|
+
"rejected by handshake handler",
|
|
1882
|
+
"REJECTED_BY_CUSTOM_HANDLER",
|
|
1883
|
+
{
|
|
1884
|
+
...handshakingSession.loggingMetadata,
|
|
1885
|
+
connectedTo: from,
|
|
1886
|
+
clientId: this.clientId
|
|
1887
|
+
}
|
|
1888
|
+
);
|
|
1889
|
+
return false;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
return parsedMetadata;
|
|
1893
|
+
}
|
|
1894
|
+
};
|
|
1895
|
+
|
|
1896
|
+
// transport/connection.ts
|
|
1897
|
+
var Connection = class {
|
|
1898
|
+
id;
|
|
1899
|
+
telemetry;
|
|
1900
|
+
constructor() {
|
|
1901
|
+
this.id = `conn-${generateId()}`;
|
|
1902
|
+
}
|
|
1903
|
+
get loggingMetadata() {
|
|
1904
|
+
const metadata = { connId: this.id };
|
|
1905
|
+
const spanContext = this.telemetry?.span.spanContext();
|
|
1906
|
+
if (this.telemetry?.span.isRecording() && spanContext) {
|
|
1907
|
+
metadata.telemetry = {
|
|
1908
|
+
traceId: spanContext.traceId,
|
|
1909
|
+
spanId: spanContext.spanId
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
return metadata;
|
|
1913
|
+
}
|
|
1914
|
+
// can't use event emitter because we need this to work in both node + browser
|
|
1915
|
+
_dataListeners = /* @__PURE__ */ new Set();
|
|
1916
|
+
_closeListeners = /* @__PURE__ */ new Set();
|
|
1917
|
+
_errorListeners = /* @__PURE__ */ new Set();
|
|
1918
|
+
get dataListeners() {
|
|
1919
|
+
return [...this._dataListeners];
|
|
1920
|
+
}
|
|
1921
|
+
get closeListeners() {
|
|
1922
|
+
return [...this._closeListeners];
|
|
1923
|
+
}
|
|
1924
|
+
get errorListeners() {
|
|
1925
|
+
return [...this._errorListeners];
|
|
1926
|
+
}
|
|
1927
|
+
/**
|
|
1928
|
+
* Handle adding a callback for when a message is received.
|
|
1929
|
+
* @param msg The message that was received.
|
|
1930
|
+
*/
|
|
1931
|
+
addDataListener(cb) {
|
|
1932
|
+
this._dataListeners.add(cb);
|
|
1933
|
+
}
|
|
1934
|
+
removeDataListener(cb) {
|
|
1935
|
+
this._dataListeners.delete(cb);
|
|
1936
|
+
}
|
|
1937
|
+
/**
|
|
1938
|
+
* Handle adding a callback for when the connection is closed.
|
|
1939
|
+
* This should also be called if an error happens and after notifying all the error listeners.
|
|
1940
|
+
* @param cb The callback to call when the connection is closed.
|
|
1941
|
+
*/
|
|
1942
|
+
addCloseListener(cb) {
|
|
1943
|
+
this._closeListeners.add(cb);
|
|
1944
|
+
}
|
|
1945
|
+
removeCloseListener(cb) {
|
|
1946
|
+
this._closeListeners.delete(cb);
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Handle adding a callback for when an error is received.
|
|
1950
|
+
* This should only be used for this.logging errors, all cleanup
|
|
1951
|
+
* should be delegated to addCloseListener.
|
|
1952
|
+
*
|
|
1953
|
+
* The implementer should take care such that the implemented
|
|
1954
|
+
* connection will call both the close and error callbacks
|
|
1955
|
+
* on an error.
|
|
1956
|
+
*
|
|
1957
|
+
* @param cb The callback to call when an error is received.
|
|
1958
|
+
*/
|
|
1959
|
+
addErrorListener(cb) {
|
|
1960
|
+
this._errorListeners.add(cb);
|
|
1961
|
+
}
|
|
1962
|
+
removeErrorListener(cb) {
|
|
1963
|
+
this._errorListeners.delete(cb);
|
|
1898
1964
|
}
|
|
1899
1965
|
};
|
|
1900
1966
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1904,7 +1970,7 @@ var ServerTransport = class extends Transport {
|
|
|
1904
1970
|
OpaqueTransportMessageSchema,
|
|
1905
1971
|
ProtocolError,
|
|
1906
1972
|
ServerTransport,
|
|
1907
|
-
|
|
1973
|
+
SessionState,
|
|
1908
1974
|
Transport,
|
|
1909
1975
|
TransportMessageSchema
|
|
1910
1976
|
});
|