@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
|
@@ -24,12 +24,193 @@ __export(server_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(server_exports);
|
|
26
26
|
|
|
27
|
-
// transport/
|
|
28
|
-
var
|
|
27
|
+
// transport/transforms/messageFraming.ts
|
|
28
|
+
var import_node_stream = require("stream");
|
|
29
|
+
var Uint32LengthPrefixFraming = class extends import_node_stream.Transform {
|
|
30
|
+
receivedBuffer;
|
|
31
|
+
maxBufferSizeBytes;
|
|
32
|
+
constructor({ maxBufferSizeBytes, ...options }) {
|
|
33
|
+
super(options);
|
|
34
|
+
this.maxBufferSizeBytes = maxBufferSizeBytes;
|
|
35
|
+
this.receivedBuffer = Buffer.alloc(0);
|
|
36
|
+
}
|
|
37
|
+
_transform(chunk, _encoding, cb) {
|
|
38
|
+
if (this.receivedBuffer.byteLength + chunk.byteLength > this.maxBufferSizeBytes) {
|
|
39
|
+
const err = new Error(
|
|
40
|
+
`buffer overflow: ${this.receivedBuffer.byteLength}B > ${this.maxBufferSizeBytes}B`
|
|
41
|
+
);
|
|
42
|
+
this.emit("error", err);
|
|
43
|
+
cb(err);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
this.receivedBuffer = Buffer.concat([this.receivedBuffer, chunk]);
|
|
47
|
+
while (this.receivedBuffer.length > 4) {
|
|
48
|
+
const claimedMessageLength = this.receivedBuffer.readUInt32BE(0) + 4;
|
|
49
|
+
if (this.receivedBuffer.length >= claimedMessageLength) {
|
|
50
|
+
const message = this.receivedBuffer.subarray(4, claimedMessageLength);
|
|
51
|
+
this.push(message);
|
|
52
|
+
this.receivedBuffer = this.receivedBuffer.subarray(claimedMessageLength);
|
|
53
|
+
} else {
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
cb();
|
|
58
|
+
}
|
|
59
|
+
_flush(cb) {
|
|
60
|
+
if (this.receivedBuffer.length) {
|
|
61
|
+
this.emit("error", new Error("got incomplete message while flushing"));
|
|
62
|
+
}
|
|
63
|
+
this.receivedBuffer = Buffer.alloc(0);
|
|
64
|
+
cb();
|
|
65
|
+
}
|
|
66
|
+
_destroy(error, callback) {
|
|
67
|
+
this.receivedBuffer = Buffer.alloc(0);
|
|
68
|
+
super._destroy(error, callback);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
function createLengthEncodedStream(options) {
|
|
72
|
+
return new Uint32LengthPrefixFraming({
|
|
73
|
+
maxBufferSizeBytes: options?.maxBufferSizeBytes ?? 16 * 1024 * 1024
|
|
74
|
+
// 16MB
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
var MessageFramer = {
|
|
78
|
+
createFramedStream: createLengthEncodedStream,
|
|
79
|
+
write: (buf) => {
|
|
80
|
+
const lengthPrefix = Buffer.alloc(4);
|
|
81
|
+
lengthPrefix.writeUInt32BE(buf.length, 0);
|
|
82
|
+
return Buffer.concat([lengthPrefix, buf]);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// transport/id.ts
|
|
87
|
+
var import_nanoid = require("nanoid");
|
|
88
|
+
var alphabet = (0, import_nanoid.customAlphabet)(
|
|
89
|
+
"1234567890abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ"
|
|
90
|
+
);
|
|
91
|
+
var generateId = () => alphabet(12);
|
|
92
|
+
|
|
93
|
+
// transport/connection.ts
|
|
94
|
+
var Connection = class {
|
|
95
|
+
id;
|
|
96
|
+
telemetry;
|
|
97
|
+
constructor() {
|
|
98
|
+
this.id = `conn-${generateId()}`;
|
|
99
|
+
}
|
|
100
|
+
get loggingMetadata() {
|
|
101
|
+
const metadata = { connId: this.id };
|
|
102
|
+
const spanContext = this.telemetry?.span.spanContext();
|
|
103
|
+
if (this.telemetry?.span.isRecording() && spanContext) {
|
|
104
|
+
metadata.telemetry = {
|
|
105
|
+
traceId: spanContext.traceId,
|
|
106
|
+
spanId: spanContext.spanId
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return metadata;
|
|
110
|
+
}
|
|
111
|
+
// can't use event emitter because we need this to work in both node + browser
|
|
112
|
+
_dataListeners = /* @__PURE__ */ new Set();
|
|
113
|
+
_closeListeners = /* @__PURE__ */ new Set();
|
|
114
|
+
_errorListeners = /* @__PURE__ */ new Set();
|
|
115
|
+
get dataListeners() {
|
|
116
|
+
return [...this._dataListeners];
|
|
117
|
+
}
|
|
118
|
+
get closeListeners() {
|
|
119
|
+
return [...this._closeListeners];
|
|
120
|
+
}
|
|
121
|
+
get errorListeners() {
|
|
122
|
+
return [...this._errorListeners];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Handle adding a callback for when a message is received.
|
|
126
|
+
* @param msg The message that was received.
|
|
127
|
+
*/
|
|
128
|
+
addDataListener(cb) {
|
|
129
|
+
this._dataListeners.add(cb);
|
|
130
|
+
}
|
|
131
|
+
removeDataListener(cb) {
|
|
132
|
+
this._dataListeners.delete(cb);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Handle adding a callback for when the connection is closed.
|
|
136
|
+
* This should also be called if an error happens and after notifying all the error listeners.
|
|
137
|
+
* @param cb The callback to call when the connection is closed.
|
|
138
|
+
*/
|
|
139
|
+
addCloseListener(cb) {
|
|
140
|
+
this._closeListeners.add(cb);
|
|
141
|
+
}
|
|
142
|
+
removeCloseListener(cb) {
|
|
143
|
+
this._closeListeners.delete(cb);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Handle adding a callback for when an error is received.
|
|
147
|
+
* This should only be used for this.logging errors, all cleanup
|
|
148
|
+
* should be delegated to addCloseListener.
|
|
149
|
+
*
|
|
150
|
+
* The implementer should take care such that the implemented
|
|
151
|
+
* connection will call both the close and error callbacks
|
|
152
|
+
* on an error.
|
|
153
|
+
*
|
|
154
|
+
* @param cb The callback to call when an error is received.
|
|
155
|
+
*/
|
|
156
|
+
addErrorListener(cb) {
|
|
157
|
+
this._errorListeners.add(cb);
|
|
158
|
+
}
|
|
159
|
+
removeErrorListener(cb) {
|
|
160
|
+
this._errorListeners.delete(cb);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// transport/impls/uds/connection.ts
|
|
165
|
+
var UdsConnection = class extends Connection {
|
|
166
|
+
sock;
|
|
167
|
+
input;
|
|
168
|
+
framer;
|
|
169
|
+
constructor(sock) {
|
|
170
|
+
super();
|
|
171
|
+
this.framer = MessageFramer.createFramedStream();
|
|
172
|
+
this.sock = sock;
|
|
173
|
+
this.input = sock.pipe(this.framer);
|
|
174
|
+
this.sock.on("close", () => {
|
|
175
|
+
for (const cb of this.closeListeners) {
|
|
176
|
+
cb();
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
this.sock.on("error", (err) => {
|
|
180
|
+
if (err instanceof Error && "code" in err && err.code === "EPIPE") {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
for (const cb of this.errorListeners) {
|
|
184
|
+
cb(err);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
this.input.on("data", (msg) => {
|
|
188
|
+
for (const cb of this.dataListeners) {
|
|
189
|
+
cb(msg);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
this.sock.on("end", () => {
|
|
193
|
+
this.sock.destroy();
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
send(payload) {
|
|
197
|
+
if (this.framer.destroyed || !this.sock.writable || this.sock.closed) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
this.sock.write(MessageFramer.write(payload));
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
close() {
|
|
204
|
+
this.sock.end();
|
|
205
|
+
this.framer.end();
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// transport/server.ts
|
|
210
|
+
var import_api3 = require("@opentelemetry/api");
|
|
29
211
|
|
|
30
212
|
// transport/message.ts
|
|
31
213
|
var import_typebox = require("@sinclair/typebox");
|
|
32
|
-
var import_nanoid = require("nanoid");
|
|
33
214
|
var TransportMessageSchema = (t) => import_typebox.Type.Object({
|
|
34
215
|
id: import_typebox.Type.String(),
|
|
35
216
|
from: import_typebox.Type.String(),
|
|
@@ -54,7 +235,8 @@ var ControlMessageAckSchema = import_typebox.Type.Object({
|
|
|
54
235
|
var ControlMessageCloseSchema = import_typebox.Type.Object({
|
|
55
236
|
type: import_typebox.Type.Literal("CLOSE")
|
|
56
237
|
});
|
|
57
|
-
var
|
|
238
|
+
var currentProtocolVersion = "v2.0";
|
|
239
|
+
var acceptedProtocolVersions = ["v1.1", currentProtocolVersion];
|
|
58
240
|
var ControlMessageHandshakeRequestSchema = import_typebox.Type.Object({
|
|
59
241
|
type: import_typebox.Type.Literal("HANDSHAKE_REQ"),
|
|
60
242
|
protocolVersion: import_typebox.Type.String(),
|
|
@@ -64,18 +246,29 @@ var ControlMessageHandshakeRequestSchema = import_typebox.Type.Object({
|
|
|
64
246
|
* used by the server to know whether this is a new or a reestablished connection, and whether it
|
|
65
247
|
* is compatible with what it already has.
|
|
66
248
|
*/
|
|
67
|
-
expectedSessionState: import_typebox.Type.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
})
|
|
76
|
-
),
|
|
249
|
+
expectedSessionState: import_typebox.Type.Object({
|
|
250
|
+
// what the client expects the server to send next
|
|
251
|
+
nextExpectedSeq: import_typebox.Type.Integer(),
|
|
252
|
+
// TODO: remove optional once we know all servers
|
|
253
|
+
// are nextSentSeq here
|
|
254
|
+
// what the server expects the client to send next
|
|
255
|
+
nextSentSeq: import_typebox.Type.Optional(import_typebox.Type.Integer())
|
|
256
|
+
}),
|
|
77
257
|
metadata: import_typebox.Type.Optional(import_typebox.Type.Unknown())
|
|
78
258
|
});
|
|
259
|
+
var HandshakeErrorRetriableResponseCodes = import_typebox.Type.Union([
|
|
260
|
+
import_typebox.Type.Literal("SESSION_STATE_MISMATCH")
|
|
261
|
+
]);
|
|
262
|
+
var HandshakeErrorFatalResponseCodes = import_typebox.Type.Union([
|
|
263
|
+
import_typebox.Type.Literal("MALFORMED_HANDSHAKE_META"),
|
|
264
|
+
import_typebox.Type.Literal("MALFORMED_HANDSHAKE"),
|
|
265
|
+
import_typebox.Type.Literal("PROTOCOL_VERSION_MISMATCH"),
|
|
266
|
+
import_typebox.Type.Literal("REJECTED_BY_CUSTOM_HANDLER")
|
|
267
|
+
]);
|
|
268
|
+
var HandshakeErrorResponseCodes = import_typebox.Type.Union([
|
|
269
|
+
HandshakeErrorRetriableResponseCodes,
|
|
270
|
+
HandshakeErrorFatalResponseCodes
|
|
271
|
+
]);
|
|
79
272
|
var ControlMessageHandshakeResponseSchema = import_typebox.Type.Object({
|
|
80
273
|
type: import_typebox.Type.Literal("HANDSHAKE_RESP"),
|
|
81
274
|
status: import_typebox.Type.Union([
|
|
@@ -85,7 +278,10 @@ var ControlMessageHandshakeResponseSchema = import_typebox.Type.Object({
|
|
|
85
278
|
}),
|
|
86
279
|
import_typebox.Type.Object({
|
|
87
280
|
ok: import_typebox.Type.Literal(false),
|
|
88
|
-
reason: import_typebox.Type.String()
|
|
281
|
+
reason: import_typebox.Type.String(),
|
|
282
|
+
// TODO: remove optional once we know all servers
|
|
283
|
+
// are sending code here
|
|
284
|
+
code: import_typebox.Type.Optional(HandshakeErrorResponseCodes)
|
|
89
285
|
})
|
|
90
286
|
])
|
|
91
287
|
});
|
|
@@ -98,19 +294,18 @@ var ControlMessagePayloadSchema = import_typebox.Type.Union([
|
|
|
98
294
|
var OpaqueTransportMessageSchema = TransportMessageSchema(
|
|
99
295
|
import_typebox.Type.Unknown()
|
|
100
296
|
);
|
|
101
|
-
var SESSION_STATE_MISMATCH = "session state mismatch";
|
|
102
297
|
function handshakeResponseMessage({
|
|
103
298
|
from,
|
|
104
299
|
to,
|
|
105
300
|
status
|
|
106
301
|
}) {
|
|
107
302
|
return {
|
|
108
|
-
id: (
|
|
303
|
+
id: generateId(),
|
|
109
304
|
from,
|
|
110
305
|
to,
|
|
111
306
|
seq: 0,
|
|
112
307
|
ack: 0,
|
|
113
|
-
streamId: (
|
|
308
|
+
streamId: generateId(),
|
|
114
309
|
controlFlags: 0,
|
|
115
310
|
payload: {
|
|
116
311
|
type: "HANDSHAKE_RESP",
|
|
@@ -122,974 +317,854 @@ function isAck(controlFlag) {
|
|
|
122
317
|
return (controlFlag & 1 /* AckBit */) === 1 /* AckBit */;
|
|
123
318
|
}
|
|
124
319
|
|
|
125
|
-
//
|
|
126
|
-
var
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const span = tracer.startSpan(
|
|
135
|
-
`session ${session.id}`,
|
|
136
|
-
{
|
|
137
|
-
attributes: {
|
|
138
|
-
component: "river",
|
|
139
|
-
"river.session.id": session.id,
|
|
140
|
-
"river.session.to": session.to,
|
|
141
|
-
"river.session.from": session.from
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
|
-
parentCtx
|
|
145
|
-
);
|
|
146
|
-
const ctx = import_api.trace.setSpan(parentCtx, span);
|
|
147
|
-
return { span, ctx };
|
|
148
|
-
}
|
|
149
|
-
function createConnectionTelemetryInfo(connection, info) {
|
|
150
|
-
const span = tracer.startSpan(
|
|
151
|
-
`connection ${connection.id}`,
|
|
152
|
-
{
|
|
153
|
-
attributes: {
|
|
154
|
-
component: "river",
|
|
155
|
-
"river.connection.id": connection.id
|
|
156
|
-
},
|
|
157
|
-
links: [{ context: info.span.spanContext() }]
|
|
158
|
-
},
|
|
159
|
-
info.ctx
|
|
160
|
-
);
|
|
161
|
-
const ctx = import_api.trace.setSpan(info.ctx, span);
|
|
162
|
-
return { span, ctx };
|
|
320
|
+
// codec/json.ts
|
|
321
|
+
var encoder = new TextEncoder();
|
|
322
|
+
var decoder = new TextDecoder();
|
|
323
|
+
function uint8ArrayToBase64(uint8Array) {
|
|
324
|
+
let binary = "";
|
|
325
|
+
uint8Array.forEach((byte) => {
|
|
326
|
+
binary += String.fromCharCode(byte);
|
|
327
|
+
});
|
|
328
|
+
return btoa(binary);
|
|
163
329
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
var unsafeId = () => nanoid2();
|
|
170
|
-
var Connection = class {
|
|
171
|
-
id;
|
|
172
|
-
telemetry;
|
|
173
|
-
constructor() {
|
|
174
|
-
this.id = `conn-${nanoid2(12)}`;
|
|
330
|
+
function base64ToUint8Array(base64) {
|
|
331
|
+
const binaryString = atob(base64);
|
|
332
|
+
const uint8Array = new Uint8Array(binaryString.length);
|
|
333
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
334
|
+
uint8Array[i] = binaryString.charCodeAt(i);
|
|
175
335
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
336
|
+
return uint8Array;
|
|
337
|
+
}
|
|
338
|
+
var NaiveJsonCodec = {
|
|
339
|
+
toBuffer: (obj) => {
|
|
340
|
+
return encoder.encode(
|
|
341
|
+
JSON.stringify(obj, function replacer(key) {
|
|
342
|
+
const val = this[key];
|
|
343
|
+
if (val instanceof Uint8Array) {
|
|
344
|
+
return { $t: uint8ArrayToBase64(val) };
|
|
345
|
+
} else {
|
|
346
|
+
return val;
|
|
347
|
+
}
|
|
348
|
+
})
|
|
349
|
+
);
|
|
350
|
+
},
|
|
351
|
+
fromBuffer: (buff) => {
|
|
352
|
+
try {
|
|
353
|
+
const parsed = JSON.parse(
|
|
354
|
+
decoder.decode(buff),
|
|
355
|
+
function reviver(_key, val) {
|
|
356
|
+
if (val?.$t) {
|
|
357
|
+
return base64ToUint8Array(val.$t);
|
|
358
|
+
} else {
|
|
359
|
+
return val;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
);
|
|
363
|
+
if (typeof parsed === "object")
|
|
364
|
+
return parsed;
|
|
365
|
+
return null;
|
|
366
|
+
} catch {
|
|
367
|
+
return null;
|
|
184
368
|
}
|
|
185
|
-
return metadata;
|
|
186
369
|
}
|
|
187
370
|
};
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* The grace period between when the inner connection is disconnected
|
|
226
|
-
* and when we should consider the entire session disconnected.
|
|
227
|
-
*/
|
|
228
|
-
disconnectionGrace;
|
|
229
|
-
/**
|
|
230
|
-
* Number of heartbeats we've sent without a response.
|
|
231
|
-
*/
|
|
232
|
-
heartbeatMisses;
|
|
233
|
-
/**
|
|
234
|
-
* The interval for sending heartbeats.
|
|
235
|
-
*/
|
|
236
|
-
heartbeat;
|
|
237
|
-
log;
|
|
238
|
-
constructor(conn, from, to, options, propagationCtx) {
|
|
239
|
-
this.id = `session-${nanoid2(12)}`;
|
|
240
|
-
this.options = options;
|
|
241
|
-
this.from = from;
|
|
242
|
-
this.to = to;
|
|
243
|
-
this.connection = conn;
|
|
244
|
-
this.codec = options.codec;
|
|
245
|
-
this.heartbeatMisses = 0;
|
|
246
|
-
this.heartbeat = setInterval(
|
|
247
|
-
() => this.sendHeartbeat(),
|
|
248
|
-
options.heartbeatIntervalMs
|
|
249
|
-
);
|
|
250
|
-
this.telemetry = createSessionTelemetryInfo(this, propagationCtx);
|
|
251
|
-
}
|
|
252
|
-
bindLogger(log) {
|
|
253
|
-
this.log = log;
|
|
254
|
-
}
|
|
255
|
-
get loggingMetadata() {
|
|
256
|
-
const spanContext = this.telemetry.span.spanContext();
|
|
257
|
-
return {
|
|
258
|
-
clientId: this.from,
|
|
259
|
-
connectedTo: this.to,
|
|
260
|
-
sessionId: this.id,
|
|
261
|
-
connId: this.connection?.id,
|
|
262
|
-
telemetry: {
|
|
263
|
-
traceId: spanContext.traceId,
|
|
264
|
-
spanId: spanContext.spanId
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* Sends a message over the session's connection.
|
|
270
|
-
* If the connection is not ready or the message fails to send, the message can be buffered for retry unless skipped.
|
|
271
|
-
*
|
|
272
|
-
* @param msg The partial message to be sent, which will be constructed into a full message.
|
|
273
|
-
* @param addToSendBuff Whether to add the message to the send buffer for retry.
|
|
274
|
-
* @returns The full transport ID of the message that was attempted to be sent.
|
|
275
|
-
*/
|
|
276
|
-
send(msg) {
|
|
277
|
-
const fullMsg = this.constructMsg(msg);
|
|
278
|
-
this.log?.debug(`sending msg`, {
|
|
279
|
-
...this.loggingMetadata,
|
|
280
|
-
transportMessage: fullMsg
|
|
281
|
-
});
|
|
282
|
-
if (this.connection) {
|
|
283
|
-
const ok = this.connection.send(this.codec.toBuffer(fullMsg));
|
|
284
|
-
if (ok)
|
|
285
|
-
return fullMsg.id;
|
|
286
|
-
this.log?.info(
|
|
287
|
-
`failed to send msg to ${fullMsg.to}, connection is probably dead`,
|
|
288
|
-
{
|
|
289
|
-
...this.loggingMetadata,
|
|
290
|
-
transportMessage: fullMsg
|
|
291
|
-
}
|
|
292
|
-
);
|
|
293
|
-
} else {
|
|
294
|
-
this.log?.debug(
|
|
295
|
-
`buffering msg to ${fullMsg.to}, connection not ready yet`,
|
|
296
|
-
{ ...this.loggingMetadata, transportMessage: fullMsg }
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
return fullMsg.id;
|
|
300
|
-
}
|
|
301
|
-
sendHeartbeat() {
|
|
302
|
-
const misses = this.heartbeatMisses;
|
|
303
|
-
const missDuration = misses * this.options.heartbeatIntervalMs;
|
|
304
|
-
if (misses > this.options.heartbeatsUntilDead) {
|
|
305
|
-
if (this.connection) {
|
|
306
|
-
this.log?.info(
|
|
307
|
-
`closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
|
|
308
|
-
this.loggingMetadata
|
|
309
|
-
);
|
|
310
|
-
this.telemetry.span.addEvent("closing connection due to inactivity");
|
|
311
|
-
this.closeStaleConnection();
|
|
312
|
-
}
|
|
371
|
+
|
|
372
|
+
// transport/options.ts
|
|
373
|
+
var defaultTransportOptions = {
|
|
374
|
+
heartbeatIntervalMs: 1e3,
|
|
375
|
+
heartbeatsUntilDead: 2,
|
|
376
|
+
sessionDisconnectGraceMs: 5e3,
|
|
377
|
+
connectionTimeoutMs: 2e3,
|
|
378
|
+
handshakeTimeoutMs: 1e3,
|
|
379
|
+
codec: NaiveJsonCodec
|
|
380
|
+
};
|
|
381
|
+
var defaultConnectionRetryOptions = {
|
|
382
|
+
baseIntervalMs: 250,
|
|
383
|
+
maxJitterMs: 200,
|
|
384
|
+
maxBackoffMs: 32e3,
|
|
385
|
+
attemptBudgetCapacity: 5,
|
|
386
|
+
budgetRestoreIntervalMs: 200
|
|
387
|
+
};
|
|
388
|
+
var defaultClientTransportOptions = {
|
|
389
|
+
...defaultTransportOptions,
|
|
390
|
+
...defaultConnectionRetryOptions
|
|
391
|
+
};
|
|
392
|
+
var defaultServerTransportOptions = {
|
|
393
|
+
...defaultTransportOptions
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// logging/log.ts
|
|
397
|
+
var LoggingLevels = {
|
|
398
|
+
debug: -1,
|
|
399
|
+
info: 0,
|
|
400
|
+
warn: 1,
|
|
401
|
+
error: 2
|
|
402
|
+
};
|
|
403
|
+
var cleanedLogFn = (log) => {
|
|
404
|
+
return (msg, metadata) => {
|
|
405
|
+
if (!metadata?.transportMessage) {
|
|
406
|
+
log(msg, metadata);
|
|
313
407
|
return;
|
|
314
408
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
409
|
+
const { payload, ...rest } = metadata.transportMessage;
|
|
410
|
+
metadata.transportMessage = rest;
|
|
411
|
+
log(msg, metadata);
|
|
412
|
+
};
|
|
413
|
+
};
|
|
414
|
+
var BaseLogger = class {
|
|
415
|
+
minLevel;
|
|
416
|
+
output;
|
|
417
|
+
constructor(output, minLevel = "info") {
|
|
418
|
+
this.minLevel = minLevel;
|
|
419
|
+
this.output = output;
|
|
323
420
|
}
|
|
324
|
-
|
|
325
|
-
this.
|
|
326
|
-
|
|
327
|
-
|
|
421
|
+
debug(msg, metadata) {
|
|
422
|
+
if (LoggingLevels[this.minLevel] <= LoggingLevels.debug) {
|
|
423
|
+
this.output(msg, metadata ?? {}, "debug");
|
|
424
|
+
}
|
|
328
425
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
connId: conn.id
|
|
333
|
-
});
|
|
334
|
-
for (const msg of this.sendBuffer) {
|
|
335
|
-
this.log?.debug(`resending msg`, {
|
|
336
|
-
...this.loggingMetadata,
|
|
337
|
-
transportMessage: msg,
|
|
338
|
-
connId: conn.id
|
|
339
|
-
});
|
|
340
|
-
const ok = conn.send(this.codec.toBuffer(msg));
|
|
341
|
-
if (!ok) {
|
|
342
|
-
const errMsg = `failed to send buffered message to ${this.to} (sus, this is a fresh connection)`;
|
|
343
|
-
conn.telemetry?.span.setStatus({
|
|
344
|
-
code: import_api2.SpanStatusCode.ERROR,
|
|
345
|
-
message: errMsg
|
|
346
|
-
});
|
|
347
|
-
this.log?.error(errMsg, {
|
|
348
|
-
...this.loggingMetadata,
|
|
349
|
-
transportMessage: msg,
|
|
350
|
-
connId: conn.id,
|
|
351
|
-
tags: ["invariant-violation"]
|
|
352
|
-
});
|
|
353
|
-
conn.close();
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
426
|
+
info(msg, metadata) {
|
|
427
|
+
if (LoggingLevels[this.minLevel] <= LoggingLevels.info) {
|
|
428
|
+
this.output(msg, metadata ?? {}, "info");
|
|
356
429
|
}
|
|
357
430
|
}
|
|
358
|
-
|
|
359
|
-
if (
|
|
360
|
-
this.
|
|
361
|
-
...this.loggingMetadata,
|
|
362
|
-
tags: ["invariant-violation"]
|
|
363
|
-
});
|
|
364
|
-
return;
|
|
431
|
+
warn(msg, metadata) {
|
|
432
|
+
if (LoggingLevels[this.minLevel] <= LoggingLevels.warn) {
|
|
433
|
+
this.output(msg, metadata ?? {}, "warn");
|
|
365
434
|
}
|
|
366
|
-
this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
|
|
367
|
-
this.ack = seq + 1;
|
|
368
435
|
}
|
|
369
|
-
|
|
370
|
-
if (this.
|
|
371
|
-
|
|
372
|
-
this.log?.info(
|
|
373
|
-
`closing old inner connection from session to ${this.to}`,
|
|
374
|
-
this.loggingMetadata
|
|
375
|
-
);
|
|
376
|
-
this.connection.close();
|
|
377
|
-
this.connection = void 0;
|
|
378
|
-
}
|
|
379
|
-
replaceWithNewConnection(newConn, isTransparentReconnect) {
|
|
380
|
-
this.closeStaleConnection(newConn);
|
|
381
|
-
this.cancelGrace();
|
|
382
|
-
if (isTransparentReconnect) {
|
|
383
|
-
this.sendBufferedMessages(newConn);
|
|
436
|
+
error(msg, metadata) {
|
|
437
|
+
if (LoggingLevels[this.minLevel] <= LoggingLevels.error) {
|
|
438
|
+
this.output(msg, metadata ?? {}, "error");
|
|
384
439
|
}
|
|
385
|
-
this.connection = newConn;
|
|
386
|
-
this.handshakingConnection = void 0;
|
|
387
440
|
}
|
|
388
|
-
|
|
389
|
-
|
|
441
|
+
};
|
|
442
|
+
var createLogProxy = (log) => ({
|
|
443
|
+
debug: cleanedLogFn(log.debug.bind(log)),
|
|
444
|
+
info: cleanedLogFn(log.info.bind(log)),
|
|
445
|
+
warn: cleanedLogFn(log.warn.bind(log)),
|
|
446
|
+
error: cleanedLogFn(log.error.bind(log))
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// transport/events.ts
|
|
450
|
+
var ProtocolError = {
|
|
451
|
+
RetriesExceeded: "conn_retry_exceeded",
|
|
452
|
+
HandshakeFailed: "handshake_failed",
|
|
453
|
+
MessageOrderingViolated: "message_ordering_violated"
|
|
454
|
+
};
|
|
455
|
+
var EventDispatcher = class {
|
|
456
|
+
eventListeners = {};
|
|
457
|
+
removeAllListeners() {
|
|
458
|
+
this.eventListeners = {};
|
|
390
459
|
}
|
|
391
|
-
|
|
392
|
-
this.
|
|
393
|
-
`starting ${this.options.sessionDisconnectGraceMs}ms grace period until session to ${this.to} is closed`,
|
|
394
|
-
this.loggingMetadata
|
|
395
|
-
);
|
|
396
|
-
this.cancelGrace();
|
|
397
|
-
this.disconnectionGrace = setTimeout(() => {
|
|
398
|
-
this.log?.info(
|
|
399
|
-
`grace period for ${this.to} elapsed`,
|
|
400
|
-
this.loggingMetadata
|
|
401
|
-
);
|
|
402
|
-
cb();
|
|
403
|
-
}, this.options.sessionDisconnectGraceMs);
|
|
460
|
+
numberOfListeners(eventType) {
|
|
461
|
+
return this.eventListeners[eventType]?.size ?? 0;
|
|
404
462
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
this.
|
|
463
|
+
addEventListener(eventType, handler) {
|
|
464
|
+
if (!this.eventListeners[eventType]) {
|
|
465
|
+
this.eventListeners[eventType] = /* @__PURE__ */ new Set();
|
|
466
|
+
}
|
|
467
|
+
this.eventListeners[eventType]?.add(handler);
|
|
410
468
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
469
|
+
removeEventListener(eventType, handler) {
|
|
470
|
+
const handlers = this.eventListeners[eventType];
|
|
471
|
+
if (handlers) {
|
|
472
|
+
this.eventListeners[eventType]?.delete(handler);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
dispatchEvent(eventType, event) {
|
|
476
|
+
const handlers = this.eventListeners[eventType];
|
|
477
|
+
if (handlers) {
|
|
478
|
+
const copy = [...handlers];
|
|
479
|
+
for (const handler of copy) {
|
|
480
|
+
handler(event);
|
|
481
|
+
}
|
|
419
482
|
}
|
|
420
|
-
this.handshakingConnection.close();
|
|
421
|
-
this.handshakingConnection = void 0;
|
|
422
483
|
}
|
|
423
|
-
|
|
424
|
-
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
// transport/sessionStateMachine/common.ts
|
|
487
|
+
var import_value = require("@sinclair/typebox/value");
|
|
488
|
+
var ERR_CONSUMED = `session state has been consumed and is no longer valid`;
|
|
489
|
+
var StateMachineState = class {
|
|
490
|
+
/*
|
|
491
|
+
* Whether this state has been consumed
|
|
492
|
+
* and we've moved on to another state
|
|
493
|
+
*/
|
|
494
|
+
_isConsumed;
|
|
425
495
|
close() {
|
|
426
|
-
this.
|
|
427
|
-
this.cancelGrace();
|
|
428
|
-
this.resetBufferedMessages();
|
|
429
|
-
clearInterval(this.heartbeat);
|
|
496
|
+
this._handleClose();
|
|
430
497
|
}
|
|
431
|
-
|
|
432
|
-
|
|
498
|
+
constructor() {
|
|
499
|
+
this._isConsumed = false;
|
|
500
|
+
return new Proxy(this, {
|
|
501
|
+
get(target, prop) {
|
|
502
|
+
if (prop === "_isConsumed" || prop === "id" || prop === "state") {
|
|
503
|
+
return Reflect.get(target, prop);
|
|
504
|
+
}
|
|
505
|
+
if (prop === "_handleStateExit") {
|
|
506
|
+
return () => {
|
|
507
|
+
target._isConsumed = true;
|
|
508
|
+
target._handleStateExit();
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
if (prop === "_handleClose") {
|
|
512
|
+
return () => {
|
|
513
|
+
target._handleStateExit();
|
|
514
|
+
target._handleClose();
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
if (target._isConsumed) {
|
|
518
|
+
throw new Error(
|
|
519
|
+
`${ERR_CONSUMED}: getting ${prop.toString()} on consumed state`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
return Reflect.get(target, prop);
|
|
523
|
+
},
|
|
524
|
+
set(target, prop, value) {
|
|
525
|
+
if (target._isConsumed) {
|
|
526
|
+
throw new Error(
|
|
527
|
+
`${ERR_CONSUMED}: setting ${prop.toString()} on consumed state`
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
return Reflect.set(target, prop, value);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
433
533
|
}
|
|
434
|
-
|
|
435
|
-
|
|
534
|
+
};
|
|
535
|
+
var CommonSession = class extends StateMachineState {
|
|
536
|
+
from;
|
|
537
|
+
options;
|
|
538
|
+
log;
|
|
539
|
+
constructor(from, options, log) {
|
|
540
|
+
super();
|
|
541
|
+
this.from = from;
|
|
542
|
+
this.options = options;
|
|
543
|
+
this.log = log;
|
|
436
544
|
}
|
|
437
|
-
|
|
438
|
-
|
|
545
|
+
parseMsg(msg) {
|
|
546
|
+
const parsedMsg = this.options.codec.fromBuffer(msg);
|
|
547
|
+
if (parsedMsg === null) {
|
|
548
|
+
const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));
|
|
549
|
+
this.log?.error(
|
|
550
|
+
`received malformed msg: ${decodedBuffer}`,
|
|
551
|
+
this.loggingMetadata
|
|
552
|
+
);
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
|
|
556
|
+
this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
|
|
557
|
+
...this.loggingMetadata,
|
|
558
|
+
validationErrors: [
|
|
559
|
+
...import_value.Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
|
|
560
|
+
]
|
|
561
|
+
});
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
return parsedMsg;
|
|
439
565
|
}
|
|
566
|
+
};
|
|
567
|
+
var IdentifiedSession = class extends CommonSession {
|
|
568
|
+
id;
|
|
569
|
+
telemetry;
|
|
570
|
+
to;
|
|
571
|
+
protocolVersion;
|
|
440
572
|
/**
|
|
441
|
-
*
|
|
442
|
-
* _or_ matches our actual next seq.
|
|
573
|
+
* Index of the message we will send next (excluding handshake)
|
|
443
574
|
*/
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
575
|
+
seq;
|
|
576
|
+
/**
|
|
577
|
+
* Number of unique messages we've received this session (excluding handshake)
|
|
578
|
+
*/
|
|
579
|
+
ack;
|
|
580
|
+
sendBuffer;
|
|
581
|
+
constructor(id, from, to, seq, ack, sendBuffer, telemetry, options, protocolVersion, log) {
|
|
582
|
+
super(from, options, log);
|
|
583
|
+
this.id = id;
|
|
584
|
+
this.to = to;
|
|
585
|
+
this.seq = seq;
|
|
586
|
+
this.ack = ack;
|
|
587
|
+
this.sendBuffer = sendBuffer;
|
|
588
|
+
this.telemetry = telemetry;
|
|
589
|
+
this.log = log;
|
|
590
|
+
this.protocolVersion = protocolVersion;
|
|
451
591
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
592
|
+
get loggingMetadata() {
|
|
593
|
+
const spanContext = this.telemetry.span.spanContext();
|
|
594
|
+
return {
|
|
595
|
+
clientId: this.from,
|
|
596
|
+
connectedTo: this.to,
|
|
597
|
+
sessionId: this.id,
|
|
598
|
+
telemetry: {
|
|
599
|
+
traceId: spanContext.traceId,
|
|
600
|
+
spanId: spanContext.spanId
|
|
601
|
+
}
|
|
602
|
+
};
|
|
456
603
|
}
|
|
457
604
|
constructMsg(partialMsg) {
|
|
458
605
|
const msg = {
|
|
459
606
|
...partialMsg,
|
|
460
|
-
id:
|
|
607
|
+
id: generateId(),
|
|
461
608
|
to: this.to,
|
|
462
609
|
from: this.from,
|
|
463
610
|
seq: this.seq,
|
|
464
611
|
ack: this.ack
|
|
465
612
|
};
|
|
466
613
|
this.seq++;
|
|
467
|
-
this.sendBuffer.push(msg);
|
|
468
614
|
return msg;
|
|
469
615
|
}
|
|
470
|
-
|
|
471
|
-
return this.sendBuffer;
|
|
616
|
+
nextSeq() {
|
|
617
|
+
return this.sendBuffer.length > 0 ? this.sendBuffer[0].seq : this.seq;
|
|
618
|
+
}
|
|
619
|
+
send(msg) {
|
|
620
|
+
const constructedMsg = this.constructMsg(msg);
|
|
621
|
+
this.sendBuffer.push(constructedMsg);
|
|
622
|
+
return constructedMsg.id;
|
|
623
|
+
}
|
|
624
|
+
_handleStateExit() {
|
|
625
|
+
}
|
|
626
|
+
_handleClose() {
|
|
627
|
+
this.sendBuffer.length = 0;
|
|
628
|
+
this.telemetry.span.end();
|
|
472
629
|
}
|
|
473
630
|
};
|
|
474
631
|
|
|
475
|
-
// transport/
|
|
476
|
-
var
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
this.
|
|
632
|
+
// transport/sessionStateMachine/SessionConnecting.ts
|
|
633
|
+
var SessionConnecting = class extends IdentifiedSession {
|
|
634
|
+
state = "Connecting" /* Connecting */;
|
|
635
|
+
connPromise;
|
|
636
|
+
listeners;
|
|
637
|
+
connectionTimeout;
|
|
638
|
+
constructor(connPromise, listeners, ...args) {
|
|
639
|
+
super(...args);
|
|
640
|
+
this.connPromise = connPromise;
|
|
641
|
+
this.listeners = listeners;
|
|
642
|
+
this.connectionTimeout = setTimeout(() => {
|
|
643
|
+
listeners.onConnectionTimeout();
|
|
644
|
+
}, this.options.connectionTimeoutMs);
|
|
645
|
+
connPromise.then(
|
|
646
|
+
(conn) => {
|
|
647
|
+
if (this._isConsumed)
|
|
648
|
+
return;
|
|
649
|
+
listeners.onConnectionEstablished(conn);
|
|
650
|
+
},
|
|
651
|
+
(err) => {
|
|
652
|
+
if (this._isConsumed)
|
|
653
|
+
return;
|
|
654
|
+
listeners.onConnectionFailed(err);
|
|
655
|
+
}
|
|
656
|
+
);
|
|
484
657
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
);
|
|
490
|
-
this.emit("error", err);
|
|
491
|
-
cb(err);
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
this.receivedBuffer = Buffer.concat([this.receivedBuffer, chunk]);
|
|
495
|
-
while (this.receivedBuffer.length > 4) {
|
|
496
|
-
const claimedMessageLength = this.receivedBuffer.readUInt32BE(0) + 4;
|
|
497
|
-
if (this.receivedBuffer.length >= claimedMessageLength) {
|
|
498
|
-
const message = this.receivedBuffer.subarray(4, claimedMessageLength);
|
|
499
|
-
this.push(message);
|
|
500
|
-
this.receivedBuffer = this.receivedBuffer.subarray(claimedMessageLength);
|
|
501
|
-
} else {
|
|
502
|
-
break;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
cb();
|
|
506
|
-
}
|
|
507
|
-
_flush(cb) {
|
|
508
|
-
if (this.receivedBuffer.length) {
|
|
509
|
-
this.emit("error", new Error("got incomplete message while flushing"));
|
|
510
|
-
}
|
|
511
|
-
this.receivedBuffer = Buffer.alloc(0);
|
|
512
|
-
cb();
|
|
513
|
-
}
|
|
514
|
-
_destroy(error, callback) {
|
|
515
|
-
this.receivedBuffer = Buffer.alloc(0);
|
|
516
|
-
super._destroy(error, callback);
|
|
517
|
-
}
|
|
518
|
-
};
|
|
519
|
-
function createLengthEncodedStream(options) {
|
|
520
|
-
return new Uint32LengthPrefixFraming({
|
|
521
|
-
maxBufferSizeBytes: options?.maxBufferSizeBytes ?? 16 * 1024 * 1024
|
|
522
|
-
// 16MB
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
var MessageFramer = {
|
|
526
|
-
createFramedStream: createLengthEncodedStream,
|
|
527
|
-
write: (buf) => {
|
|
528
|
-
const lengthPrefix = Buffer.alloc(4);
|
|
529
|
-
lengthPrefix.writeUInt32BE(buf.length, 0);
|
|
530
|
-
return Buffer.concat([lengthPrefix, buf]);
|
|
531
|
-
}
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
// transport/impls/uds/connection.ts
|
|
535
|
-
var UdsConnection = class extends Connection {
|
|
536
|
-
sock;
|
|
537
|
-
input;
|
|
538
|
-
framer;
|
|
539
|
-
constructor(sock) {
|
|
540
|
-
super();
|
|
541
|
-
this.framer = MessageFramer.createFramedStream();
|
|
542
|
-
this.sock = sock;
|
|
543
|
-
this.input = sock.pipe(this.framer);
|
|
544
|
-
}
|
|
545
|
-
addDataListener(cb) {
|
|
546
|
-
this.input.on("data", cb);
|
|
547
|
-
}
|
|
548
|
-
removeDataListener(cb) {
|
|
549
|
-
this.input.off("data", cb);
|
|
550
|
-
}
|
|
551
|
-
addCloseListener(cb) {
|
|
552
|
-
this.sock.on("close", cb);
|
|
553
|
-
}
|
|
554
|
-
addErrorListener(cb) {
|
|
555
|
-
this.sock.on("error", (err) => {
|
|
556
|
-
if (err instanceof Error && "code" in err && err.code === "EPIPE") {
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
cb(err);
|
|
658
|
+
// close a pending connection if it resolves, ignore errors if the promise
|
|
659
|
+
// ends up rejected anyways
|
|
660
|
+
bestEffortClose() {
|
|
661
|
+
void this.connPromise.then((conn) => conn.close()).catch(() => {
|
|
560
662
|
});
|
|
561
663
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
664
|
+
_handleStateExit() {
|
|
665
|
+
super._handleStateExit();
|
|
666
|
+
clearTimeout(this.connectionTimeout);
|
|
667
|
+
this.connectionTimeout = void 0;
|
|
566
668
|
}
|
|
567
|
-
|
|
568
|
-
this.
|
|
569
|
-
|
|
669
|
+
_handleClose() {
|
|
670
|
+
this.bestEffortClose();
|
|
671
|
+
super._handleClose();
|
|
570
672
|
}
|
|
571
673
|
};
|
|
572
674
|
|
|
573
|
-
// transport/
|
|
574
|
-
var
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
return btoa(binary);
|
|
585
|
-
}
|
|
586
|
-
function base64ToUint8Array(base64) {
|
|
587
|
-
const binaryString = atob(base64);
|
|
588
|
-
const uint8Array = new Uint8Array(binaryString.length);
|
|
589
|
-
for (let i = 0; i < binaryString.length; i++) {
|
|
590
|
-
uint8Array[i] = binaryString.charCodeAt(i);
|
|
675
|
+
// transport/sessionStateMachine/SessionNoConnection.ts
|
|
676
|
+
var SessionNoConnection = class extends IdentifiedSession {
|
|
677
|
+
state = "NoConnection" /* NoConnection */;
|
|
678
|
+
listeners;
|
|
679
|
+
gracePeriodTimeout;
|
|
680
|
+
constructor(listeners, ...args) {
|
|
681
|
+
super(...args);
|
|
682
|
+
this.listeners = listeners;
|
|
683
|
+
this.gracePeriodTimeout = setTimeout(() => {
|
|
684
|
+
this.listeners.onSessionGracePeriodElapsed();
|
|
685
|
+
}, this.options.sessionDisconnectGraceMs);
|
|
591
686
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
return { $t: uint8ArrayToBase64(val) };
|
|
601
|
-
} else {
|
|
602
|
-
return val;
|
|
603
|
-
}
|
|
604
|
-
})
|
|
605
|
-
);
|
|
606
|
-
},
|
|
607
|
-
fromBuffer: (buff) => {
|
|
608
|
-
try {
|
|
609
|
-
const parsed = JSON.parse(
|
|
610
|
-
decoder.decode(buff),
|
|
611
|
-
function reviver(_key, val) {
|
|
612
|
-
if (val?.$t) {
|
|
613
|
-
return base64ToUint8Array(val.$t);
|
|
614
|
-
} else {
|
|
615
|
-
return val;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
);
|
|
619
|
-
if (typeof parsed === "object")
|
|
620
|
-
return parsed;
|
|
621
|
-
return null;
|
|
622
|
-
} catch {
|
|
623
|
-
return null;
|
|
687
|
+
_handleClose() {
|
|
688
|
+
super._handleClose();
|
|
689
|
+
}
|
|
690
|
+
_handleStateExit() {
|
|
691
|
+
super._handleStateExit();
|
|
692
|
+
if (this.gracePeriodTimeout) {
|
|
693
|
+
clearTimeout(this.gracePeriodTimeout);
|
|
694
|
+
this.gracePeriodTimeout = void 0;
|
|
624
695
|
}
|
|
625
696
|
}
|
|
626
697
|
};
|
|
627
698
|
|
|
628
|
-
//
|
|
629
|
-
var
|
|
630
|
-
heartbeatIntervalMs: 1e3,
|
|
631
|
-
heartbeatsUntilDead: 2,
|
|
632
|
-
sessionDisconnectGraceMs: 5e3,
|
|
633
|
-
codec: NaiveJsonCodec
|
|
634
|
-
};
|
|
635
|
-
var defaultConnectionRetryOptions = {
|
|
636
|
-
baseIntervalMs: 250,
|
|
637
|
-
maxJitterMs: 200,
|
|
638
|
-
maxBackoffMs: 32e3,
|
|
639
|
-
attemptBudgetCapacity: 5,
|
|
640
|
-
budgetRestoreIntervalMs: 200
|
|
641
|
-
};
|
|
642
|
-
var defaultClientTransportOptions = {
|
|
643
|
-
...defaultTransportOptions,
|
|
644
|
-
...defaultConnectionRetryOptions
|
|
645
|
-
};
|
|
646
|
-
var defaultServerTransportOptions = {
|
|
647
|
-
...defaultTransportOptions
|
|
648
|
-
};
|
|
699
|
+
// tracing/index.ts
|
|
700
|
+
var import_api = require("@opentelemetry/api");
|
|
649
701
|
|
|
650
|
-
//
|
|
651
|
-
var
|
|
702
|
+
// package.json
|
|
703
|
+
var version = "0.200.0-rc.4";
|
|
652
704
|
|
|
653
|
-
//
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
705
|
+
// tracing/index.ts
|
|
706
|
+
function createSessionTelemetryInfo(sessionId, to, from, propagationCtx) {
|
|
707
|
+
const parentCtx = propagationCtx ? import_api.propagation.extract(import_api.context.active(), propagationCtx) : import_api.context.active();
|
|
708
|
+
const span = tracer.startSpan(
|
|
709
|
+
`session ${sessionId}`,
|
|
710
|
+
{
|
|
711
|
+
attributes: {
|
|
712
|
+
component: "river",
|
|
713
|
+
"river.session.id": sessionId,
|
|
714
|
+
"river.session.to": to,
|
|
715
|
+
"river.session.from": from
|
|
716
|
+
}
|
|
717
|
+
},
|
|
718
|
+
parentCtx
|
|
719
|
+
);
|
|
720
|
+
const ctx = import_api.trace.setSpan(parentCtx, span);
|
|
721
|
+
return { span, ctx };
|
|
722
|
+
}
|
|
723
|
+
var tracer = import_api.trace.getTracer("river", version);
|
|
724
|
+
|
|
725
|
+
// transport/sessionStateMachine/SessionWaitingForHandshake.ts
|
|
726
|
+
var SessionWaitingForHandshake = class extends CommonSession {
|
|
727
|
+
state = "WaitingForHandshake" /* WaitingForHandshake */;
|
|
728
|
+
conn;
|
|
729
|
+
listeners;
|
|
730
|
+
handshakeTimeout;
|
|
731
|
+
constructor(conn, listeners, ...args) {
|
|
732
|
+
super(...args);
|
|
733
|
+
this.conn = conn;
|
|
734
|
+
this.listeners = listeners;
|
|
735
|
+
this.handshakeTimeout = setTimeout(() => {
|
|
736
|
+
listeners.onHandshakeTimeout();
|
|
737
|
+
}, this.options.handshakeTimeoutMs);
|
|
738
|
+
this.conn.addDataListener(this.onHandshakeData);
|
|
739
|
+
this.conn.addErrorListener(listeners.onConnectionErrored);
|
|
740
|
+
this.conn.addCloseListener(listeners.onConnectionClosed);
|
|
741
|
+
}
|
|
742
|
+
onHandshakeData = (msg) => {
|
|
743
|
+
const parsedMsg = this.parseMsg(msg);
|
|
744
|
+
if (parsedMsg === null) {
|
|
745
|
+
this.listeners.onInvalidHandshake("could not parse message");
|
|
664
746
|
return;
|
|
665
747
|
}
|
|
666
|
-
|
|
667
|
-
metadata.transportMessage = rest;
|
|
668
|
-
log(msg, metadata);
|
|
748
|
+
this.listeners.onHandshake(parsedMsg);
|
|
669
749
|
};
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
this.minLevel = minLevel;
|
|
676
|
-
this.output = output;
|
|
677
|
-
}
|
|
678
|
-
debug(msg, metadata) {
|
|
679
|
-
if (LoggingLevels[this.minLevel] <= LoggingLevels.debug) {
|
|
680
|
-
this.output(msg, metadata ?? {}, "debug");
|
|
681
|
-
}
|
|
750
|
+
get loggingMetadata() {
|
|
751
|
+
return {
|
|
752
|
+
clientId: this.from,
|
|
753
|
+
connId: this.conn.id
|
|
754
|
+
};
|
|
682
755
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
this.output(msg, metadata ?? {}, "info");
|
|
686
|
-
}
|
|
756
|
+
sendHandshake(msg) {
|
|
757
|
+
return this.conn.send(this.options.codec.toBuffer(msg));
|
|
687
758
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
759
|
+
_handleStateExit() {
|
|
760
|
+
this.conn.removeDataListener(this.onHandshakeData);
|
|
761
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
762
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
763
|
+
clearTimeout(this.handshakeTimeout);
|
|
764
|
+
this.handshakeTimeout = void 0;
|
|
692
765
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
this.output(msg, metadata ?? {}, "error");
|
|
696
|
-
}
|
|
766
|
+
_handleClose() {
|
|
767
|
+
this.conn.close();
|
|
697
768
|
}
|
|
698
769
|
};
|
|
699
|
-
var createLogProxy = (log) => ({
|
|
700
|
-
debug: cleanedLogFn(log.debug.bind(log)),
|
|
701
|
-
info: cleanedLogFn(log.info.bind(log)),
|
|
702
|
-
warn: cleanedLogFn(log.warn.bind(log)),
|
|
703
|
-
error: cleanedLogFn(log.error.bind(log))
|
|
704
|
-
});
|
|
705
770
|
|
|
706
|
-
// transport/
|
|
707
|
-
var
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
this.
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
771
|
+
// transport/sessionStateMachine/SessionHandshaking.ts
|
|
772
|
+
var SessionHandshaking = class extends IdentifiedSession {
|
|
773
|
+
state = "Handshaking" /* Handshaking */;
|
|
774
|
+
conn;
|
|
775
|
+
listeners;
|
|
776
|
+
handshakeTimeout;
|
|
777
|
+
constructor(conn, listeners, ...args) {
|
|
778
|
+
super(...args);
|
|
779
|
+
this.conn = conn;
|
|
780
|
+
this.listeners = listeners;
|
|
781
|
+
this.handshakeTimeout = setTimeout(() => {
|
|
782
|
+
listeners.onHandshakeTimeout();
|
|
783
|
+
}, this.options.handshakeTimeoutMs);
|
|
784
|
+
this.conn.addDataListener(this.onHandshakeData);
|
|
785
|
+
this.conn.addErrorListener(listeners.onConnectionErrored);
|
|
786
|
+
this.conn.addCloseListener(listeners.onConnectionClosed);
|
|
787
|
+
}
|
|
788
|
+
onHandshakeData = (msg) => {
|
|
789
|
+
const parsedMsg = this.parseMsg(msg);
|
|
790
|
+
if (parsedMsg === null) {
|
|
791
|
+
this.listeners.onInvalidHandshake("could not parse message");
|
|
792
|
+
return;
|
|
723
793
|
}
|
|
724
|
-
this.
|
|
794
|
+
this.listeners.onHandshake(parsedMsg);
|
|
795
|
+
};
|
|
796
|
+
sendHandshake(msg) {
|
|
797
|
+
return this.conn.send(this.options.codec.toBuffer(msg));
|
|
725
798
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
799
|
+
_handleStateExit() {
|
|
800
|
+
super._handleStateExit();
|
|
801
|
+
this.conn.removeDataListener(this.onHandshakeData);
|
|
802
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
803
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
804
|
+
clearTimeout(this.handshakeTimeout);
|
|
731
805
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const copy = [...handlers];
|
|
736
|
-
for (const handler of copy) {
|
|
737
|
-
handler(event);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
806
|
+
_handleClose() {
|
|
807
|
+
super._handleClose();
|
|
808
|
+
this.conn.close();
|
|
740
809
|
}
|
|
741
810
|
};
|
|
742
811
|
|
|
743
|
-
// transport/
|
|
744
|
-
var
|
|
745
|
-
var
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
codec;
|
|
754
|
-
/**
|
|
755
|
-
* The client ID of this transport.
|
|
756
|
-
*/
|
|
757
|
-
clientId;
|
|
758
|
-
/**
|
|
759
|
-
* The map of {@link Session}s managed by this transport.
|
|
760
|
-
*/
|
|
761
|
-
sessions;
|
|
762
|
-
/**
|
|
763
|
-
* The map of {@link Connection}s managed by this transport.
|
|
764
|
-
*/
|
|
765
|
-
get connections() {
|
|
766
|
-
return new Map(
|
|
767
|
-
[...this.sessions].map(([client, session]) => [client, session.connection]).filter((entry) => entry[1] !== void 0)
|
|
768
|
-
);
|
|
812
|
+
// transport/sessionStateMachine/SessionConnected.ts
|
|
813
|
+
var import_api2 = require("@opentelemetry/api");
|
|
814
|
+
var SessionConnected = class extends IdentifiedSession {
|
|
815
|
+
state = "Connected" /* Connected */;
|
|
816
|
+
conn;
|
|
817
|
+
listeners;
|
|
818
|
+
heartbeatHandle;
|
|
819
|
+
heartbeatMisses = 0;
|
|
820
|
+
get isActivelyHeartbeating() {
|
|
821
|
+
return this.heartbeatHandle !== void 0;
|
|
769
822
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
/**
|
|
775
|
-
* The options for this transport.
|
|
776
|
-
*/
|
|
777
|
-
options;
|
|
778
|
-
log;
|
|
779
|
-
/**
|
|
780
|
-
* Creates a new Transport instance.
|
|
781
|
-
* This should also set up {@link onConnect}, and {@link onDisconnect} listeners.
|
|
782
|
-
* @param codec The codec used to encode and decode messages.
|
|
783
|
-
* @param clientId The client ID of this transport.
|
|
784
|
-
*/
|
|
785
|
-
constructor(clientId, providedOptions) {
|
|
786
|
-
this.options = { ...defaultTransportOptions, ...providedOptions };
|
|
787
|
-
this.eventDispatcher = new EventDispatcher();
|
|
788
|
-
this.sessions = /* @__PURE__ */ new Map();
|
|
789
|
-
this.codec = this.options.codec;
|
|
790
|
-
this.clientId = clientId;
|
|
791
|
-
this.status = "open";
|
|
823
|
+
updateBookkeeping(ack, seq) {
|
|
824
|
+
this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
|
|
825
|
+
this.ack = seq + 1;
|
|
826
|
+
this.heartbeatMisses = 0;
|
|
792
827
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
828
|
+
send(msg) {
|
|
829
|
+
const constructedMsg = this.constructMsg(msg);
|
|
830
|
+
this.sendBuffer.push(constructedMsg);
|
|
831
|
+
this.conn.send(this.options.codec.toBuffer(constructedMsg));
|
|
832
|
+
return constructedMsg.id;
|
|
833
|
+
}
|
|
834
|
+
constructor(conn, listeners, ...args) {
|
|
835
|
+
super(...args);
|
|
836
|
+
this.conn = conn;
|
|
837
|
+
this.listeners = listeners;
|
|
838
|
+
this.conn.addDataListener(this.onMessageData);
|
|
839
|
+
this.conn.addCloseListener(listeners.onConnectionClosed);
|
|
840
|
+
this.conn.addErrorListener(listeners.onConnectionErrored);
|
|
841
|
+
if (this.sendBuffer.length > 0) {
|
|
842
|
+
this.log?.debug(
|
|
843
|
+
`sending ${this.sendBuffer.length} buffered messages`,
|
|
844
|
+
this.loggingMetadata
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
for (const msg of this.sendBuffer) {
|
|
848
|
+
conn.send(this.options.codec.toBuffer(msg));
|
|
797
849
|
}
|
|
798
|
-
this.log = createLogProxy(fn);
|
|
799
850
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
851
|
+
startActiveHeartbeat() {
|
|
852
|
+
this.heartbeatHandle = setInterval(() => {
|
|
853
|
+
const misses = this.heartbeatMisses;
|
|
854
|
+
const missDuration = misses * this.options.heartbeatIntervalMs;
|
|
855
|
+
if (misses >= this.options.heartbeatsUntilDead) {
|
|
856
|
+
this.log?.info(
|
|
857
|
+
`closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
|
|
858
|
+
this.loggingMetadata
|
|
859
|
+
);
|
|
860
|
+
this.telemetry.span.addEvent("closing connection due to inactivity");
|
|
861
|
+
this.conn.close();
|
|
862
|
+
clearInterval(this.heartbeatHandle);
|
|
863
|
+
this.heartbeatHandle = void 0;
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
this.sendHeartbeat();
|
|
867
|
+
this.heartbeatMisses++;
|
|
868
|
+
}, this.options.heartbeatIntervalMs);
|
|
869
|
+
}
|
|
870
|
+
sendHeartbeat() {
|
|
871
|
+
this.send({
|
|
872
|
+
streamId: "heartbeat",
|
|
873
|
+
controlFlags: 1 /* AckBit */,
|
|
874
|
+
payload: {
|
|
875
|
+
type: "ACK"
|
|
876
|
+
}
|
|
815
877
|
});
|
|
816
878
|
}
|
|
817
|
-
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
this.
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
`
|
|
832
|
-
{
|
|
833
|
-
...
|
|
879
|
+
onMessageData = (msg) => {
|
|
880
|
+
const parsedMsg = this.parseMsg(msg);
|
|
881
|
+
if (parsedMsg === null)
|
|
882
|
+
return;
|
|
883
|
+
if (parsedMsg.seq !== this.ack) {
|
|
884
|
+
if (parsedMsg.seq < this.ack) {
|
|
885
|
+
this.log?.debug(
|
|
886
|
+
`received duplicate msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack}), discarding`,
|
|
887
|
+
{
|
|
888
|
+
...this.loggingMetadata,
|
|
889
|
+
transportMessage: parsedMsg
|
|
890
|
+
}
|
|
891
|
+
);
|
|
892
|
+
} else {
|
|
893
|
+
const reason = `received out-of-order msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack})`;
|
|
894
|
+
this.log?.error(reason, {
|
|
895
|
+
...this.loggingMetadata,
|
|
896
|
+
transportMessage: parsedMsg,
|
|
834
897
|
tags: ["invariant-violation"]
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
898
|
+
});
|
|
899
|
+
this.telemetry.span.setStatus({
|
|
900
|
+
code: import_api2.SpanStatusCode.ERROR,
|
|
901
|
+
message: reason
|
|
902
|
+
});
|
|
903
|
+
this.listeners.onInvalidMessage(reason);
|
|
904
|
+
}
|
|
905
|
+
return;
|
|
841
906
|
}
|
|
842
|
-
this.
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
session
|
|
907
|
+
this.log?.debug(`received msg`, {
|
|
908
|
+
...this.loggingMetadata,
|
|
909
|
+
transportMessage: parsedMsg
|
|
846
910
|
});
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
conn,
|
|
852
|
-
sessionId,
|
|
853
|
-
propagationCtx
|
|
854
|
-
}) {
|
|
855
|
-
let session = this.sessions.get(to);
|
|
856
|
-
if (session !== void 0) {
|
|
857
|
-
this.log?.info(
|
|
858
|
-
`session for ${to} already exists, replacing it with a new session as requested`,
|
|
859
|
-
session.loggingMetadata
|
|
860
|
-
);
|
|
861
|
-
this.deleteSession({
|
|
862
|
-
session,
|
|
863
|
-
closeHandshakingConnection: false
|
|
864
|
-
});
|
|
865
|
-
session = void 0;
|
|
911
|
+
this.updateBookkeeping(parsedMsg.ack, parsedMsg.seq);
|
|
912
|
+
if (!isAck(parsedMsg.controlFlags)) {
|
|
913
|
+
this.listeners.onMessage(parsedMsg);
|
|
914
|
+
return;
|
|
866
915
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
to,
|
|
874
|
-
sessionId,
|
|
875
|
-
nextExpectedSeq
|
|
876
|
-
}) {
|
|
877
|
-
const session = this.sessions.get(to);
|
|
878
|
-
if (
|
|
879
|
-
// reject this request if there was no previous session to replace
|
|
880
|
-
session === void 0 || // or if both parties do not agree about the next expected sequence number
|
|
881
|
-
!session.nextExpectedSeqInRange(nextExpectedSeq) || // or if both parties do not agree on the advertised session id
|
|
882
|
-
session.advertisedSessionId !== sessionId
|
|
883
|
-
) {
|
|
884
|
-
return false;
|
|
916
|
+
this.log?.debug(`discarding msg (ack bit set)`, {
|
|
917
|
+
...this.loggingMetadata,
|
|
918
|
+
transportMessage: parsedMsg
|
|
919
|
+
});
|
|
920
|
+
if (!this.isActivelyHeartbeating) {
|
|
921
|
+
this.sendHeartbeat();
|
|
885
922
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
);
|
|
890
|
-
|
|
923
|
+
};
|
|
924
|
+
_handleStateExit() {
|
|
925
|
+
super._handleStateExit();
|
|
926
|
+
this.conn.removeDataListener(this.onMessageData);
|
|
927
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
928
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
929
|
+
clearInterval(this.heartbeatHandle);
|
|
930
|
+
this.heartbeatHandle = void 0;
|
|
931
|
+
}
|
|
932
|
+
_handleClose() {
|
|
933
|
+
super._handleClose();
|
|
934
|
+
this.conn.close();
|
|
891
935
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
// transport/sessionStateMachine/transitions.ts
|
|
939
|
+
function inheritSharedSession(session) {
|
|
940
|
+
return [
|
|
941
|
+
session.id,
|
|
942
|
+
session.from,
|
|
943
|
+
session.to,
|
|
944
|
+
session.seq,
|
|
945
|
+
session.ack,
|
|
946
|
+
session.sendBuffer,
|
|
947
|
+
session.telemetry,
|
|
948
|
+
session.options,
|
|
949
|
+
session.protocolVersion,
|
|
950
|
+
session.log
|
|
951
|
+
];
|
|
952
|
+
}
|
|
953
|
+
var SessionStateGraph = {
|
|
954
|
+
entrypoints: {
|
|
955
|
+
NoConnection(to, from, listeners, options, protocolVersion, log) {
|
|
956
|
+
const id = `session-${generateId()}`;
|
|
957
|
+
const telemetry = createSessionTelemetryInfo(id, to, from);
|
|
958
|
+
const sendBuffer = [];
|
|
959
|
+
const session = new SessionNoConnection(
|
|
960
|
+
listeners,
|
|
961
|
+
id,
|
|
962
|
+
from,
|
|
963
|
+
to,
|
|
964
|
+
0,
|
|
965
|
+
0,
|
|
966
|
+
sendBuffer,
|
|
967
|
+
telemetry,
|
|
968
|
+
options,
|
|
969
|
+
protocolVersion,
|
|
970
|
+
log
|
|
906
971
|
);
|
|
907
|
-
|
|
908
|
-
session,
|
|
909
|
-
|
|
910
|
-
handshakingConn
|
|
972
|
+
session.log?.info(`session ${session.id} created in NoConnection state`, {
|
|
973
|
+
...session.loggingMetadata,
|
|
974
|
+
tags: ["state-transition"]
|
|
911
975
|
});
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
976
|
+
return session;
|
|
977
|
+
},
|
|
978
|
+
WaitingForHandshake(from, conn, listeners, options, log) {
|
|
979
|
+
const session = new SessionWaitingForHandshake(
|
|
980
|
+
conn,
|
|
981
|
+
listeners,
|
|
982
|
+
from,
|
|
983
|
+
options,
|
|
984
|
+
log
|
|
920
985
|
);
|
|
986
|
+
session.log?.info(`session created in WaitingForHandshake state`, {
|
|
987
|
+
...session.loggingMetadata,
|
|
988
|
+
tags: ["state-transition"]
|
|
989
|
+
});
|
|
990
|
+
return session;
|
|
921
991
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
}
|
|
938
|
-
session.close();
|
|
939
|
-
session.telemetry.span.end();
|
|
940
|
-
const currentSession = this.sessions.get(session.to);
|
|
941
|
-
if (currentSession && currentSession.id !== session.id) {
|
|
942
|
-
this.log?.warn(
|
|
943
|
-
`session ${session.id} disconnect from ${session.to}, mismatch with ${currentSession.id}`,
|
|
992
|
+
},
|
|
993
|
+
// All of the transitions 'move'/'consume' the old session and return a new one.
|
|
994
|
+
// After a session is transitioned, any usage of the old session will throw.
|
|
995
|
+
transition: {
|
|
996
|
+
// happy path transitions
|
|
997
|
+
NoConnectionToConnecting(oldSession, connPromise, listeners) {
|
|
998
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
999
|
+
oldSession._handleStateExit();
|
|
1000
|
+
const session = new SessionConnecting(
|
|
1001
|
+
connPromise,
|
|
1002
|
+
listeners,
|
|
1003
|
+
...carriedState
|
|
1004
|
+
);
|
|
1005
|
+
session.log?.info(
|
|
1006
|
+
`session ${session.id} transition from NoConnection to Connecting`,
|
|
944
1007
|
{
|
|
945
1008
|
...session.loggingMetadata,
|
|
946
|
-
tags: ["
|
|
1009
|
+
tags: ["state-transition"]
|
|
947
1010
|
}
|
|
948
1011
|
);
|
|
949
|
-
return;
|
|
1012
|
+
return session;
|
|
1013
|
+
},
|
|
1014
|
+
ConnectingToHandshaking(oldSession, conn, listeners) {
|
|
1015
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1016
|
+
oldSession._handleStateExit();
|
|
1017
|
+
const session = new SessionHandshaking(conn, listeners, ...carriedState);
|
|
1018
|
+
session.log?.info(
|
|
1019
|
+
`session ${session.id} transition from Connecting to Handshaking`,
|
|
1020
|
+
{
|
|
1021
|
+
...session.loggingMetadata,
|
|
1022
|
+
tags: ["state-transition"]
|
|
1023
|
+
}
|
|
1024
|
+
);
|
|
1025
|
+
return session;
|
|
1026
|
+
},
|
|
1027
|
+
HandshakingToConnected(oldSession, listeners) {
|
|
1028
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1029
|
+
const conn = oldSession.conn;
|
|
1030
|
+
oldSession._handleStateExit();
|
|
1031
|
+
const session = new SessionConnected(conn, listeners, ...carriedState);
|
|
1032
|
+
session.log?.info(
|
|
1033
|
+
`session ${session.id} transition from Handshaking to Connected`,
|
|
1034
|
+
{
|
|
1035
|
+
...session.loggingMetadata,
|
|
1036
|
+
tags: ["state-transition"]
|
|
1037
|
+
}
|
|
1038
|
+
);
|
|
1039
|
+
return session;
|
|
1040
|
+
},
|
|
1041
|
+
WaitingForHandshakeToConnected(pendingSession, oldSession, sessionId, to, propagationCtx, listeners, protocolVersion) {
|
|
1042
|
+
const conn = pendingSession.conn;
|
|
1043
|
+
const { from, options } = pendingSession;
|
|
1044
|
+
const carriedState = oldSession ? (
|
|
1045
|
+
// old session exists, inherit state
|
|
1046
|
+
inheritSharedSession(oldSession)
|
|
1047
|
+
) : (
|
|
1048
|
+
// old session does not exist, create new state
|
|
1049
|
+
[
|
|
1050
|
+
sessionId,
|
|
1051
|
+
from,
|
|
1052
|
+
to,
|
|
1053
|
+
0,
|
|
1054
|
+
0,
|
|
1055
|
+
[],
|
|
1056
|
+
createSessionTelemetryInfo(sessionId, to, from, propagationCtx),
|
|
1057
|
+
options,
|
|
1058
|
+
protocolVersion,
|
|
1059
|
+
pendingSession.log
|
|
1060
|
+
]
|
|
1061
|
+
);
|
|
1062
|
+
pendingSession._handleStateExit();
|
|
1063
|
+
oldSession?._handleStateExit();
|
|
1064
|
+
const session = new SessionConnected(conn, listeners, ...carriedState);
|
|
1065
|
+
session.log?.info(
|
|
1066
|
+
`session ${session.id} transition from WaitingForHandshake to Connected`,
|
|
1067
|
+
{
|
|
1068
|
+
...session.loggingMetadata,
|
|
1069
|
+
tags: ["state-transition"]
|
|
1070
|
+
}
|
|
1071
|
+
);
|
|
1072
|
+
return session;
|
|
1073
|
+
},
|
|
1074
|
+
// disconnect paths
|
|
1075
|
+
ConnectingToNoConnection(oldSession, listeners) {
|
|
1076
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1077
|
+
oldSession.bestEffortClose();
|
|
1078
|
+
oldSession._handleStateExit();
|
|
1079
|
+
const session = new SessionNoConnection(listeners, ...carriedState);
|
|
1080
|
+
session.log?.info(
|
|
1081
|
+
`session ${session.id} transition from Connecting to NoConnection`,
|
|
1082
|
+
{
|
|
1083
|
+
...session.loggingMetadata,
|
|
1084
|
+
tags: ["state-transition"]
|
|
1085
|
+
}
|
|
1086
|
+
);
|
|
1087
|
+
return session;
|
|
1088
|
+
},
|
|
1089
|
+
HandshakingToNoConnection(oldSession, listeners) {
|
|
1090
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1091
|
+
oldSession.conn.close();
|
|
1092
|
+
oldSession._handleStateExit();
|
|
1093
|
+
const session = new SessionNoConnection(listeners, ...carriedState);
|
|
1094
|
+
session.log?.info(
|
|
1095
|
+
`session ${session.id} transition from Handshaking to NoConnection`,
|
|
1096
|
+
{
|
|
1097
|
+
...session.loggingMetadata,
|
|
1098
|
+
tags: ["state-transition"]
|
|
1099
|
+
}
|
|
1100
|
+
);
|
|
1101
|
+
return session;
|
|
1102
|
+
},
|
|
1103
|
+
ConnectedToNoConnection(oldSession, listeners) {
|
|
1104
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1105
|
+
oldSession.conn.close();
|
|
1106
|
+
oldSession._handleStateExit();
|
|
1107
|
+
const session = new SessionNoConnection(listeners, ...carriedState);
|
|
1108
|
+
session.log?.info(
|
|
1109
|
+
`session ${session.id} transition from Connected to NoConnection`,
|
|
1110
|
+
{
|
|
1111
|
+
...session.loggingMetadata,
|
|
1112
|
+
tags: ["state-transition"]
|
|
1113
|
+
}
|
|
1114
|
+
);
|
|
1115
|
+
return session;
|
|
950
1116
|
}
|
|
951
|
-
this.sessions.delete(session.to);
|
|
952
|
-
this.log?.info(
|
|
953
|
-
`session ${session.id} disconnect from ${session.to}`,
|
|
954
|
-
session.loggingMetadata
|
|
955
|
-
);
|
|
956
|
-
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
957
|
-
status: "disconnect",
|
|
958
|
-
session
|
|
959
|
-
});
|
|
960
1117
|
}
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
// transport/transport.ts
|
|
1121
|
+
var Transport = class {
|
|
961
1122
|
/**
|
|
962
|
-
* The
|
|
963
|
-
* @param conn The connection object.
|
|
964
|
-
* @param connectedTo The peer we are connected to.
|
|
1123
|
+
* The status of the transport.
|
|
965
1124
|
*/
|
|
966
|
-
|
|
967
|
-
if (session.connection !== void 0 && session.connection.id !== conn.id) {
|
|
968
|
-
session.telemetry.span.addEvent("onDisconnect race");
|
|
969
|
-
this.log?.warn("onDisconnect race", {
|
|
970
|
-
clientId: this.clientId,
|
|
971
|
-
...session.loggingMetadata,
|
|
972
|
-
...conn.loggingMetadata,
|
|
973
|
-
tags: ["invariant-violation"]
|
|
974
|
-
});
|
|
975
|
-
return;
|
|
976
|
-
}
|
|
977
|
-
conn.telemetry?.span.end();
|
|
978
|
-
this.eventDispatcher.dispatchEvent("connectionStatus", {
|
|
979
|
-
status: "disconnect",
|
|
980
|
-
conn
|
|
981
|
-
});
|
|
982
|
-
session.connection = void 0;
|
|
983
|
-
session.beginGrace(() => {
|
|
984
|
-
if (session.connection !== void 0) {
|
|
985
|
-
session.telemetry.span.addEvent("session grace period race");
|
|
986
|
-
this.log?.warn("session grace period race", {
|
|
987
|
-
clientId: this.clientId,
|
|
988
|
-
...session.loggingMetadata,
|
|
989
|
-
...conn.loggingMetadata,
|
|
990
|
-
tags: ["invariant-violation"]
|
|
991
|
-
});
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
session.telemetry.span.addEvent("session grace period expired");
|
|
995
|
-
this.deleteSession({
|
|
996
|
-
session,
|
|
997
|
-
closeHandshakingConnection: true,
|
|
998
|
-
handshakingConn: conn
|
|
999
|
-
});
|
|
1000
|
-
});
|
|
1001
|
-
}
|
|
1125
|
+
status;
|
|
1002
1126
|
/**
|
|
1003
|
-
*
|
|
1004
|
-
* @param msg The message to parse.
|
|
1005
|
-
* @returns The parsed message, or null if the message is malformed or invalid.
|
|
1127
|
+
* The client ID of this transport.
|
|
1006
1128
|
*/
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1129
|
+
clientId;
|
|
1130
|
+
/**
|
|
1131
|
+
* The event dispatcher for handling events of type EventTypes.
|
|
1132
|
+
*/
|
|
1133
|
+
eventDispatcher;
|
|
1134
|
+
/**
|
|
1135
|
+
* The options for this transport.
|
|
1136
|
+
*/
|
|
1137
|
+
options;
|
|
1138
|
+
log;
|
|
1139
|
+
sessions;
|
|
1140
|
+
/**
|
|
1141
|
+
* Creates a new Transport instance.
|
|
1142
|
+
* @param codec The codec used to encode and decode messages.
|
|
1143
|
+
* @param clientId The client ID of this transport.
|
|
1144
|
+
*/
|
|
1145
|
+
constructor(clientId, providedOptions) {
|
|
1146
|
+
this.options = { ...defaultTransportOptions, ...providedOptions };
|
|
1147
|
+
this.eventDispatcher = new EventDispatcher();
|
|
1148
|
+
this.clientId = clientId;
|
|
1149
|
+
this.status = "open";
|
|
1150
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
1151
|
+
}
|
|
1152
|
+
bindLogger(fn, level) {
|
|
1153
|
+
if (typeof fn === "function") {
|
|
1154
|
+
this.log = createLogProxy(new BaseLogger(fn, level));
|
|
1155
|
+
return;
|
|
1029
1156
|
}
|
|
1030
|
-
|
|
1157
|
+
this.log = createLogProxy(fn);
|
|
1031
1158
|
}
|
|
1032
1159
|
/**
|
|
1033
1160
|
* Called when a message is received by this transport.
|
|
1034
1161
|
* You generally shouldn't need to override this in downstream transport implementations.
|
|
1035
1162
|
* @param msg The received message.
|
|
1036
1163
|
*/
|
|
1037
|
-
handleMsg(msg
|
|
1164
|
+
handleMsg(msg) {
|
|
1038
1165
|
if (this.getStatus() !== "open")
|
|
1039
1166
|
return;
|
|
1040
|
-
|
|
1041
|
-
if (!session) {
|
|
1042
|
-
this.log?.error(`received message for unknown session from ${msg.from}`, {
|
|
1043
|
-
clientId: this.clientId,
|
|
1044
|
-
transportMessage: msg,
|
|
1045
|
-
...conn.loggingMetadata,
|
|
1046
|
-
tags: ["invariant-violation"]
|
|
1047
|
-
});
|
|
1048
|
-
return;
|
|
1049
|
-
}
|
|
1050
|
-
session.cancelGrace();
|
|
1051
|
-
this.log?.debug(`received msg`, {
|
|
1052
|
-
clientId: this.clientId,
|
|
1053
|
-
transportMessage: msg,
|
|
1054
|
-
...conn.loggingMetadata
|
|
1055
|
-
});
|
|
1056
|
-
if (msg.seq !== session.nextExpectedSeq) {
|
|
1057
|
-
if (msg.seq < session.nextExpectedSeq) {
|
|
1058
|
-
this.log?.debug(
|
|
1059
|
-
`received duplicate msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq}), discarding`,
|
|
1060
|
-
{
|
|
1061
|
-
clientId: this.clientId,
|
|
1062
|
-
transportMessage: msg,
|
|
1063
|
-
...conn.loggingMetadata
|
|
1064
|
-
}
|
|
1065
|
-
);
|
|
1066
|
-
} else {
|
|
1067
|
-
const errMsg = `received out-of-order msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq})`;
|
|
1068
|
-
this.log?.error(`${errMsg}, marking connection as dead`, {
|
|
1069
|
-
clientId: this.clientId,
|
|
1070
|
-
transportMessage: msg,
|
|
1071
|
-
...conn.loggingMetadata,
|
|
1072
|
-
tags: ["invariant-violation"]
|
|
1073
|
-
});
|
|
1074
|
-
this.protocolError(ProtocolError.MessageOrderingViolated, errMsg);
|
|
1075
|
-
session.telemetry.span.setStatus({
|
|
1076
|
-
code: import_api3.SpanStatusCode.ERROR,
|
|
1077
|
-
message: "message order violated"
|
|
1078
|
-
});
|
|
1079
|
-
this.deleteSession({ session, closeHandshakingConnection: true });
|
|
1080
|
-
}
|
|
1081
|
-
return;
|
|
1082
|
-
}
|
|
1083
|
-
session.updateBookkeeping(msg.ack, msg.seq);
|
|
1084
|
-
if (!isAck(msg.controlFlags)) {
|
|
1085
|
-
this.eventDispatcher.dispatchEvent("message", msg);
|
|
1086
|
-
} else {
|
|
1087
|
-
this.log?.debug(`discarding msg (ack bit set)`, {
|
|
1088
|
-
clientId: this.clientId,
|
|
1089
|
-
transportMessage: msg,
|
|
1090
|
-
...conn.loggingMetadata
|
|
1091
|
-
});
|
|
1092
|
-
}
|
|
1167
|
+
this.eventDispatcher.dispatchEvent("message", msg);
|
|
1093
1168
|
}
|
|
1094
1169
|
/**
|
|
1095
1170
|
* Adds a listener to this transport.
|
|
@@ -1107,50 +1182,6 @@ var Transport = class {
|
|
|
1107
1182
|
removeEventListener(type, handler) {
|
|
1108
1183
|
this.eventDispatcher.removeEventListener(type, handler);
|
|
1109
1184
|
}
|
|
1110
|
-
/**
|
|
1111
|
-
* Sends a message over this transport, delegating to the appropriate connection to actually
|
|
1112
|
-
* send the message.
|
|
1113
|
-
* @param msg The message to send.
|
|
1114
|
-
* @returns The ID of the sent message or undefined if it wasn't sent
|
|
1115
|
-
*/
|
|
1116
|
-
send(to, msg) {
|
|
1117
|
-
if (this.getStatus() === "closed") {
|
|
1118
|
-
const err = "transport is closed, cant send";
|
|
1119
|
-
this.log?.error(err, {
|
|
1120
|
-
clientId: this.clientId,
|
|
1121
|
-
transportMessage: msg,
|
|
1122
|
-
tags: ["invariant-violation"]
|
|
1123
|
-
});
|
|
1124
|
-
throw new Error(err);
|
|
1125
|
-
}
|
|
1126
|
-
return this.getOrCreateSession({ to }).session.send(msg);
|
|
1127
|
-
}
|
|
1128
|
-
// control helpers
|
|
1129
|
-
sendCloseControl(to, streamId) {
|
|
1130
|
-
return this.send(to, {
|
|
1131
|
-
streamId,
|
|
1132
|
-
controlFlags: 8 /* StreamClosedBit */,
|
|
1133
|
-
payload: {
|
|
1134
|
-
type: "CLOSE"
|
|
1135
|
-
}
|
|
1136
|
-
});
|
|
1137
|
-
}
|
|
1138
|
-
sendRequestCloseControl(to, streamId) {
|
|
1139
|
-
return this.send(to, {
|
|
1140
|
-
streamId,
|
|
1141
|
-
controlFlags: 16 /* StreamCloseRequestBit */,
|
|
1142
|
-
payload: {
|
|
1143
|
-
type: "CLOSE"
|
|
1144
|
-
}
|
|
1145
|
-
});
|
|
1146
|
-
}
|
|
1147
|
-
sendAbort(to, streamId, payload) {
|
|
1148
|
-
return this.send(to, {
|
|
1149
|
-
streamId,
|
|
1150
|
-
controlFlags: 4 /* StreamAbortBit */,
|
|
1151
|
-
payload
|
|
1152
|
-
});
|
|
1153
|
-
}
|
|
1154
1185
|
protocolError(type, message) {
|
|
1155
1186
|
this.eventDispatcher.dispatchEvent("protocolError", { type, message });
|
|
1156
1187
|
}
|
|
@@ -1162,7 +1193,7 @@ var Transport = class {
|
|
|
1162
1193
|
close() {
|
|
1163
1194
|
this.status = "closed";
|
|
1164
1195
|
for (const session of this.sessions.values()) {
|
|
1165
|
-
this.deleteSession(
|
|
1196
|
+
this.deleteSession(session);
|
|
1166
1197
|
}
|
|
1167
1198
|
this.eventDispatcher.dispatchEvent("transportStatus", {
|
|
1168
1199
|
status: this.status
|
|
@@ -1173,6 +1204,68 @@ var Transport = class {
|
|
|
1173
1204
|
getStatus() {
|
|
1174
1205
|
return this.status;
|
|
1175
1206
|
}
|
|
1207
|
+
updateSession(session) {
|
|
1208
|
+
const activeSession = this.sessions.get(session.to);
|
|
1209
|
+
if (activeSession && activeSession.id !== session.id) {
|
|
1210
|
+
const msg = `attempt to transition active session for ${session.to} but active session (${activeSession.id}) is different from handle (${session.id})`;
|
|
1211
|
+
throw new Error(msg);
|
|
1212
|
+
}
|
|
1213
|
+
this.sessions.set(session.to, session);
|
|
1214
|
+
if (!activeSession) {
|
|
1215
|
+
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
1216
|
+
status: "connect",
|
|
1217
|
+
session
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
this.eventDispatcher.dispatchEvent("sessionTransition", {
|
|
1221
|
+
state: session.state,
|
|
1222
|
+
session
|
|
1223
|
+
});
|
|
1224
|
+
return session;
|
|
1225
|
+
}
|
|
1226
|
+
// state transitions
|
|
1227
|
+
deleteSession(session) {
|
|
1228
|
+
session.log?.info(`closing session ${session.id}`, session.loggingMetadata);
|
|
1229
|
+
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
1230
|
+
status: "disconnect",
|
|
1231
|
+
session
|
|
1232
|
+
});
|
|
1233
|
+
session.close();
|
|
1234
|
+
this.sessions.delete(session.to);
|
|
1235
|
+
}
|
|
1236
|
+
// common listeners
|
|
1237
|
+
onSessionGracePeriodElapsed(session) {
|
|
1238
|
+
this.log?.warn(
|
|
1239
|
+
`session to ${session.to} grace period elapsed, closing`,
|
|
1240
|
+
session.loggingMetadata
|
|
1241
|
+
);
|
|
1242
|
+
this.deleteSession(session);
|
|
1243
|
+
}
|
|
1244
|
+
onConnectingFailed(session) {
|
|
1245
|
+
const noConnectionSession = SessionStateGraph.transition.ConnectingToNoConnection(session, {
|
|
1246
|
+
onSessionGracePeriodElapsed: () => {
|
|
1247
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
return this.updateSession(noConnectionSession);
|
|
1251
|
+
}
|
|
1252
|
+
onConnClosed(session) {
|
|
1253
|
+
let noConnectionSession;
|
|
1254
|
+
if (session.state === "Handshaking" /* Handshaking */) {
|
|
1255
|
+
noConnectionSession = SessionStateGraph.transition.HandshakingToNoConnection(session, {
|
|
1256
|
+
onSessionGracePeriodElapsed: () => {
|
|
1257
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
1260
|
+
} else {
|
|
1261
|
+
noConnectionSession = SessionStateGraph.transition.ConnectedToNoConnection(session, {
|
|
1262
|
+
onSessionGracePeriodElapsed: () => {
|
|
1263
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
return this.updateSession(noConnectionSession);
|
|
1268
|
+
}
|
|
1176
1269
|
};
|
|
1177
1270
|
|
|
1178
1271
|
// util/stringify.ts
|
|
@@ -1197,22 +1290,52 @@ var ServerTransport = class extends Transport {
|
|
|
1197
1290
|
/**
|
|
1198
1291
|
* A map of session handshake data for each session.
|
|
1199
1292
|
*/
|
|
1200
|
-
sessionHandshakeMetadata;
|
|
1293
|
+
sessionHandshakeMetadata = /* @__PURE__ */ new Map();
|
|
1294
|
+
pendingSessions = /* @__PURE__ */ new Set();
|
|
1201
1295
|
constructor(clientId, providedOptions) {
|
|
1202
1296
|
super(clientId, providedOptions);
|
|
1203
1297
|
this.options = {
|
|
1204
1298
|
...defaultServerTransportOptions,
|
|
1205
1299
|
...providedOptions
|
|
1206
1300
|
};
|
|
1207
|
-
this.sessionHandshakeMetadata = /* @__PURE__ */ new WeakMap();
|
|
1208
1301
|
this.log?.info(`initiated server transport`, {
|
|
1209
1302
|
clientId: this.clientId,
|
|
1210
|
-
protocolVersion:
|
|
1303
|
+
protocolVersion: currentProtocolVersion
|
|
1211
1304
|
});
|
|
1212
1305
|
}
|
|
1213
1306
|
extendHandshake(options) {
|
|
1214
1307
|
this.handshakeExtensions = options;
|
|
1215
1308
|
}
|
|
1309
|
+
send(to, msg) {
|
|
1310
|
+
if (this.getStatus() === "closed") {
|
|
1311
|
+
const err = "transport is closed, cant send";
|
|
1312
|
+
this.log?.error(err, {
|
|
1313
|
+
clientId: this.clientId,
|
|
1314
|
+
transportMessage: msg,
|
|
1315
|
+
tags: ["invariant-violation"]
|
|
1316
|
+
});
|
|
1317
|
+
throw new Error(err);
|
|
1318
|
+
}
|
|
1319
|
+
const session = this.sessions.get(to);
|
|
1320
|
+
if (!session) {
|
|
1321
|
+
const err = `session to ${to} does not exist`;
|
|
1322
|
+
this.log?.error(err, {
|
|
1323
|
+
clientId: this.clientId,
|
|
1324
|
+
transportMessage: msg,
|
|
1325
|
+
tags: ["invariant-violation"]
|
|
1326
|
+
});
|
|
1327
|
+
throw new Error(err);
|
|
1328
|
+
}
|
|
1329
|
+
return session.send(msg);
|
|
1330
|
+
}
|
|
1331
|
+
deletePendingSession(pendingSession) {
|
|
1332
|
+
pendingSession.close();
|
|
1333
|
+
this.pendingSessions.delete(pendingSession);
|
|
1334
|
+
}
|
|
1335
|
+
deleteSession(session) {
|
|
1336
|
+
this.sessionHandshakeMetadata.delete(session.to);
|
|
1337
|
+
super.deleteSession(session);
|
|
1338
|
+
}
|
|
1216
1339
|
handleConnection(conn) {
|
|
1217
1340
|
if (this.getStatus() !== "open")
|
|
1218
1341
|
return;
|
|
@@ -1220,281 +1343,299 @@ var ServerTransport = class extends Transport {
|
|
|
1220
1343
|
...conn.loggingMetadata,
|
|
1221
1344
|
clientId: this.clientId
|
|
1222
1345
|
});
|
|
1223
|
-
let
|
|
1224
|
-
const
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1346
|
+
let receivedHandshake = false;
|
|
1347
|
+
const pendingSession = SessionStateGraph.entrypoints.WaitingForHandshake(
|
|
1348
|
+
this.clientId,
|
|
1349
|
+
conn,
|
|
1350
|
+
{
|
|
1351
|
+
onConnectionClosed: () => {
|
|
1352
|
+
this.log?.warn(
|
|
1353
|
+
`connection from unknown closed before handshake finished`,
|
|
1354
|
+
pendingSession.loggingMetadata
|
|
1355
|
+
);
|
|
1356
|
+
this.deletePendingSession(pendingSession);
|
|
1357
|
+
},
|
|
1358
|
+
onConnectionErrored: (err) => {
|
|
1359
|
+
const errorString = coerceErrorString(err);
|
|
1360
|
+
this.log?.warn(
|
|
1361
|
+
`connection from unknown errored before handshake finished: ${errorString}`,
|
|
1362
|
+
pendingSession.loggingMetadata
|
|
1363
|
+
);
|
|
1364
|
+
this.deletePendingSession(pendingSession);
|
|
1365
|
+
},
|
|
1366
|
+
onHandshakeTimeout: () => {
|
|
1367
|
+
this.log?.warn(
|
|
1368
|
+
`connection from unknown timed out before handshake finished`,
|
|
1369
|
+
pendingSession.loggingMetadata
|
|
1370
|
+
);
|
|
1371
|
+
this.deletePendingSession(pendingSession);
|
|
1372
|
+
},
|
|
1373
|
+
onHandshake: (msg) => {
|
|
1374
|
+
if (receivedHandshake) {
|
|
1375
|
+
this.log?.error(
|
|
1376
|
+
`received multiple handshake messages from pending session`,
|
|
1377
|
+
{
|
|
1378
|
+
...pendingSession.loggingMetadata,
|
|
1379
|
+
connectedTo: msg.from,
|
|
1380
|
+
transportMessage: msg
|
|
1381
|
+
}
|
|
1382
|
+
);
|
|
1383
|
+
this.deletePendingSession(pendingSession);
|
|
1255
1384
|
return;
|
|
1256
1385
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
dataHandler(data2);
|
|
1268
|
-
}
|
|
1269
|
-
conn.removeDataListener(handshakeHandler);
|
|
1270
|
-
conn.addDataListener(dataHandler);
|
|
1271
|
-
buffer.length = 0;
|
|
1386
|
+
receivedHandshake = true;
|
|
1387
|
+
void this.onHandshakeRequest(pendingSession, msg);
|
|
1388
|
+
},
|
|
1389
|
+
onInvalidHandshake: (reason) => {
|
|
1390
|
+
this.log?.error(
|
|
1391
|
+
`invalid handshake: ${reason}`,
|
|
1392
|
+
pendingSession.loggingMetadata
|
|
1393
|
+
);
|
|
1394
|
+
this.deletePendingSession(pendingSession);
|
|
1395
|
+
this.protocolError(ProtocolError.HandshakeFailed, reason);
|
|
1272
1396
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
return;
|
|
1279
|
-
this.log?.info(`connection to ${client()} disconnected`, {
|
|
1280
|
-
...conn.loggingMetadata,
|
|
1281
|
-
clientId: this.clientId
|
|
1282
|
-
});
|
|
1283
|
-
this.onDisconnect(conn, session);
|
|
1284
|
-
});
|
|
1285
|
-
conn.addErrorListener((err) => {
|
|
1286
|
-
conn.telemetry?.span.setStatus({
|
|
1287
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1288
|
-
message: "connection error"
|
|
1289
|
-
});
|
|
1290
|
-
if (!session)
|
|
1291
|
-
return;
|
|
1292
|
-
this.log?.warn(
|
|
1293
|
-
`connection to ${client()} got an error: ${coerceErrorString(err)}`,
|
|
1294
|
-
{ ...conn.loggingMetadata, clientId: this.clientId }
|
|
1295
|
-
);
|
|
1296
|
-
});
|
|
1297
|
-
}
|
|
1298
|
-
async validateHandshakeMetadata(conn, session, rawMetadata, from) {
|
|
1299
|
-
let parsedMetadata = {};
|
|
1300
|
-
if (this.handshakeExtensions) {
|
|
1301
|
-
if (!import_value2.Value.Check(this.handshakeExtensions.schema, rawMetadata)) {
|
|
1302
|
-
conn.telemetry?.span.setStatus({
|
|
1303
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1304
|
-
message: "malformed handshake meta"
|
|
1305
|
-
});
|
|
1306
|
-
const reason = "received malformed handshake metadata";
|
|
1307
|
-
const responseMsg = handshakeResponseMessage({
|
|
1308
|
-
from: this.clientId,
|
|
1309
|
-
to: from,
|
|
1310
|
-
status: {
|
|
1311
|
-
ok: false,
|
|
1312
|
-
reason
|
|
1313
|
-
}
|
|
1314
|
-
});
|
|
1315
|
-
conn.send(this.codec.toBuffer(responseMsg));
|
|
1316
|
-
this.log?.warn(`received malformed handshake metadata from ${from}`, {
|
|
1317
|
-
...conn.loggingMetadata,
|
|
1318
|
-
clientId: this.clientId,
|
|
1319
|
-
validationErrors: [
|
|
1320
|
-
...import_value2.Value.Errors(this.handshakeExtensions.schema, rawMetadata)
|
|
1321
|
-
]
|
|
1322
|
-
});
|
|
1323
|
-
this.protocolError(ProtocolError.HandshakeFailed, reason);
|
|
1324
|
-
return false;
|
|
1325
|
-
}
|
|
1326
|
-
const previousParsedMetadata = session ? this.sessionHandshakeMetadata.get(session) : void 0;
|
|
1327
|
-
parsedMetadata = await this.handshakeExtensions.validate(
|
|
1328
|
-
rawMetadata,
|
|
1329
|
-
previousParsedMetadata
|
|
1330
|
-
);
|
|
1331
|
-
if (parsedMetadata === false) {
|
|
1332
|
-
const reason = "rejected by handshake handler";
|
|
1333
|
-
conn.telemetry?.span.setStatus({
|
|
1334
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1335
|
-
message: reason
|
|
1336
|
-
});
|
|
1337
|
-
const responseMsg = handshakeResponseMessage({
|
|
1338
|
-
from: this.clientId,
|
|
1339
|
-
to: from,
|
|
1340
|
-
status: {
|
|
1341
|
-
ok: false,
|
|
1342
|
-
reason
|
|
1343
|
-
}
|
|
1344
|
-
});
|
|
1345
|
-
conn.send(this.codec.toBuffer(responseMsg));
|
|
1346
|
-
this.log?.warn(`rejected handshake from ${from}`, {
|
|
1347
|
-
...conn.loggingMetadata,
|
|
1348
|
-
clientId: this.clientId
|
|
1349
|
-
});
|
|
1350
|
-
this.protocolError(ProtocolError.HandshakeFailed, reason);
|
|
1351
|
-
return false;
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
return parsedMetadata;
|
|
1397
|
+
},
|
|
1398
|
+
this.options,
|
|
1399
|
+
this.log
|
|
1400
|
+
);
|
|
1401
|
+
this.pendingSessions.add(pendingSession);
|
|
1355
1402
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
ProtocolError.HandshakeFailed,
|
|
1365
|
-
"received non-transport message"
|
|
1366
|
-
);
|
|
1367
|
-
return false;
|
|
1368
|
-
}
|
|
1369
|
-
if (!import_value2.Value.Check(ControlMessageHandshakeRequestSchema, parsed.payload)) {
|
|
1370
|
-
conn.telemetry?.span.setStatus({
|
|
1371
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1372
|
-
message: "invalid handshake request"
|
|
1373
|
-
});
|
|
1374
|
-
const reason = "received invalid handshake msg";
|
|
1375
|
-
const responseMsg2 = handshakeResponseMessage({
|
|
1403
|
+
rejectHandshakeRequest(session, to, reason, code, metadata) {
|
|
1404
|
+
session.conn.telemetry?.span.setStatus({
|
|
1405
|
+
code: import_api3.SpanStatusCode.ERROR,
|
|
1406
|
+
message: reason
|
|
1407
|
+
});
|
|
1408
|
+
this.log?.warn(reason, metadata);
|
|
1409
|
+
session.sendHandshake(
|
|
1410
|
+
handshakeResponseMessage({
|
|
1376
1411
|
from: this.clientId,
|
|
1377
|
-
to
|
|
1412
|
+
to,
|
|
1378
1413
|
status: {
|
|
1379
1414
|
ok: false,
|
|
1415
|
+
code,
|
|
1380
1416
|
reason
|
|
1381
1417
|
}
|
|
1382
|
-
})
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1418
|
+
})
|
|
1419
|
+
);
|
|
1420
|
+
this.protocolError(ProtocolError.HandshakeFailed, reason);
|
|
1421
|
+
this.deletePendingSession(session);
|
|
1422
|
+
}
|
|
1423
|
+
async onHandshakeRequest(session, msg) {
|
|
1424
|
+
if (!import_value2.Value.Check(ControlMessageHandshakeRequestSchema, msg.payload)) {
|
|
1425
|
+
this.rejectHandshakeRequest(
|
|
1426
|
+
session,
|
|
1427
|
+
msg.from,
|
|
1428
|
+
"received invalid handshake request",
|
|
1429
|
+
"MALFORMED_HANDSHAKE",
|
|
1430
|
+
{
|
|
1431
|
+
...session.loggingMetadata,
|
|
1432
|
+
transportMessage: msg,
|
|
1433
|
+
connectedTo: msg.from,
|
|
1434
|
+
validationErrors: [
|
|
1435
|
+
...import_value2.Value.Errors(ControlMessageHandshakeRequestSchema, msg.payload)
|
|
1436
|
+
]
|
|
1437
|
+
}
|
|
1397
1438
|
);
|
|
1398
|
-
return
|
|
1439
|
+
return;
|
|
1399
1440
|
}
|
|
1400
|
-
const gotVersion =
|
|
1401
|
-
if (gotVersion
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
ok: false,
|
|
1412
|
-
reason
|
|
1441
|
+
const gotVersion = msg.payload.protocolVersion;
|
|
1442
|
+
if (!acceptedProtocolVersions.includes(gotVersion)) {
|
|
1443
|
+
this.rejectHandshakeRequest(
|
|
1444
|
+
session,
|
|
1445
|
+
msg.from,
|
|
1446
|
+
`expected protocol version oneof [${acceptedProtocolVersions.toString()}], got ${gotVersion}`,
|
|
1447
|
+
"PROTOCOL_VERSION_MISMATCH",
|
|
1448
|
+
{
|
|
1449
|
+
...session.loggingMetadata,
|
|
1450
|
+
connectedTo: msg.from,
|
|
1451
|
+
transportMessage: msg
|
|
1413
1452
|
}
|
|
1414
|
-
});
|
|
1415
|
-
conn.send(this.codec.toBuffer(responseMsg2));
|
|
1416
|
-
this.log?.warn(
|
|
1417
|
-
`received handshake msg with incompatible protocol version (got: ${gotVersion}, expected: ${PROTOCOL_VERSION})`,
|
|
1418
|
-
{ ...conn.loggingMetadata, clientId: this.clientId }
|
|
1419
1453
|
);
|
|
1420
|
-
|
|
1421
|
-
return false;
|
|
1454
|
+
return;
|
|
1422
1455
|
}
|
|
1423
|
-
|
|
1456
|
+
let oldSession = this.sessions.get(msg.from);
|
|
1424
1457
|
const parsedMetadata = await this.validateHandshakeMetadata(
|
|
1425
|
-
|
|
1458
|
+
session,
|
|
1426
1459
|
oldSession,
|
|
1427
|
-
|
|
1428
|
-
|
|
1460
|
+
msg.payload.metadata,
|
|
1461
|
+
msg.from
|
|
1429
1462
|
);
|
|
1430
1463
|
if (parsedMetadata === false) {
|
|
1431
|
-
return
|
|
1464
|
+
return;
|
|
1432
1465
|
}
|
|
1433
|
-
let session;
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1466
|
+
let connectCase = "new session";
|
|
1467
|
+
if (oldSession && oldSession.id === msg.payload.sessionId) {
|
|
1468
|
+
connectCase = "transparent reconnection";
|
|
1469
|
+
const clientNextExpectedSeq = msg.payload.expectedSessionState.nextExpectedSeq;
|
|
1470
|
+
const clientNextSentSeq = msg.payload.expectedSessionState.nextSentSeq ?? 0;
|
|
1471
|
+
const ourNextSeq = oldSession.nextSeq();
|
|
1472
|
+
const ourAck = oldSession.ack;
|
|
1473
|
+
if (clientNextSentSeq > ourAck) {
|
|
1474
|
+
this.rejectHandshakeRequest(
|
|
1475
|
+
session,
|
|
1476
|
+
msg.from,
|
|
1477
|
+
`client is in the future: server wanted next message to be ${ourAck} but client would have sent ${clientNextSentSeq}`,
|
|
1478
|
+
"SESSION_STATE_MISMATCH",
|
|
1479
|
+
{
|
|
1480
|
+
...session.loggingMetadata,
|
|
1481
|
+
connectedTo: msg.from,
|
|
1482
|
+
transportMessage: msg
|
|
1483
|
+
}
|
|
1484
|
+
);
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
if (ourNextSeq > clientNextExpectedSeq) {
|
|
1488
|
+
this.rejectHandshakeRequest(
|
|
1489
|
+
session,
|
|
1490
|
+
msg.from,
|
|
1491
|
+
`server is in the future: client wanted next message to be ${clientNextExpectedSeq} but server would have sent ${ourNextSeq}`,
|
|
1492
|
+
"SESSION_STATE_MISMATCH",
|
|
1493
|
+
{
|
|
1494
|
+
...session.loggingMetadata,
|
|
1495
|
+
connectedTo: msg.from,
|
|
1496
|
+
transportMessage: msg
|
|
1497
|
+
}
|
|
1498
|
+
);
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
if (oldSession.state === "Connected" /* Connected */) {
|
|
1502
|
+
const noConnectionSession = SessionStateGraph.transition.ConnectedToNoConnection(oldSession, {
|
|
1503
|
+
onSessionGracePeriodElapsed: () => {
|
|
1504
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1505
|
+
}
|
|
1452
1506
|
});
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
ok: false,
|
|
1459
|
-
reason
|
|
1507
|
+
oldSession = noConnectionSession;
|
|
1508
|
+
} else if (oldSession.state === "Handshaking" /* Handshaking */) {
|
|
1509
|
+
const noConnectionSession = SessionStateGraph.transition.HandshakingToNoConnection(oldSession, {
|
|
1510
|
+
onSessionGracePeriodElapsed: () => {
|
|
1511
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1460
1512
|
}
|
|
1461
1513
|
});
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1514
|
+
oldSession = noConnectionSession;
|
|
1515
|
+
} else if (oldSession.state === "Connecting" /* Connecting */) {
|
|
1516
|
+
const noConnectionSession = SessionStateGraph.transition.ConnectingToNoConnection(oldSession, {
|
|
1517
|
+
onSessionGracePeriodElapsed: () => {
|
|
1518
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
oldSession = noConnectionSession;
|
|
1469
1522
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1523
|
+
this.updateSession(oldSession);
|
|
1524
|
+
} else if (oldSession) {
|
|
1525
|
+
connectCase = "hard reconnection";
|
|
1526
|
+
this.deleteSession(oldSession);
|
|
1527
|
+
oldSession = void 0;
|
|
1472
1528
|
} else {
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1529
|
+
connectCase = "unknown session";
|
|
1530
|
+
const clientNextExpectedSeq = msg.payload.expectedSessionState.nextExpectedSeq;
|
|
1531
|
+
const clientNextSentSeq = msg.payload.expectedSessionState.nextSentSeq ?? 0;
|
|
1532
|
+
if (clientNextSentSeq > 0 || clientNextExpectedSeq > 0) {
|
|
1533
|
+
this.rejectHandshakeRequest(
|
|
1534
|
+
session,
|
|
1535
|
+
msg.from,
|
|
1536
|
+
`client is trying to reconnect to a session the server don't know about: ${msg.payload.sessionId}`,
|
|
1537
|
+
"SESSION_STATE_MISMATCH",
|
|
1538
|
+
{
|
|
1539
|
+
...session.loggingMetadata,
|
|
1540
|
+
connectedTo: msg.from,
|
|
1541
|
+
transportMessage: msg
|
|
1542
|
+
}
|
|
1543
|
+
);
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1481
1546
|
}
|
|
1482
|
-
|
|
1483
|
-
this.log?.
|
|
1484
|
-
`handshake from ${
|
|
1485
|
-
|
|
1547
|
+
const sessionId = msg.payload.sessionId;
|
|
1548
|
+
this.log?.info(
|
|
1549
|
+
`handshake from ${msg.from} ok (${connectCase}), responding with handshake success`,
|
|
1550
|
+
{
|
|
1551
|
+
...session.loggingMetadata,
|
|
1552
|
+
connectedTo: msg.from
|
|
1553
|
+
}
|
|
1486
1554
|
);
|
|
1487
1555
|
const responseMsg = handshakeResponseMessage({
|
|
1488
1556
|
from: this.clientId,
|
|
1489
|
-
to:
|
|
1557
|
+
to: msg.from,
|
|
1490
1558
|
status: {
|
|
1491
1559
|
ok: true,
|
|
1492
|
-
sessionId
|
|
1560
|
+
sessionId
|
|
1493
1561
|
}
|
|
1494
1562
|
});
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1563
|
+
session.sendHandshake(responseMsg);
|
|
1564
|
+
const connectedSession = SessionStateGraph.transition.WaitingForHandshakeToConnected(
|
|
1565
|
+
session,
|
|
1566
|
+
// by this point oldSession is either no connection or we dont have an old session
|
|
1567
|
+
oldSession,
|
|
1568
|
+
sessionId,
|
|
1569
|
+
msg.from,
|
|
1570
|
+
msg.tracing,
|
|
1571
|
+
{
|
|
1572
|
+
onConnectionErrored: (err) => {
|
|
1573
|
+
const errStr = coerceErrorString(err);
|
|
1574
|
+
this.log?.warn(
|
|
1575
|
+
`connection to ${connectedSession.to} errored: ${errStr}`,
|
|
1576
|
+
connectedSession.loggingMetadata
|
|
1577
|
+
);
|
|
1578
|
+
},
|
|
1579
|
+
onConnectionClosed: () => {
|
|
1580
|
+
this.log?.info(
|
|
1581
|
+
`connection to ${connectedSession.to} closed`,
|
|
1582
|
+
connectedSession.loggingMetadata
|
|
1583
|
+
);
|
|
1584
|
+
this.onConnClosed(connectedSession);
|
|
1585
|
+
},
|
|
1586
|
+
onMessage: (msg2) => this.handleMsg(msg2),
|
|
1587
|
+
onInvalidMessage: (reason) => {
|
|
1588
|
+
this.protocolError(ProtocolError.MessageOrderingViolated, reason);
|
|
1589
|
+
this.deleteSession(connectedSession);
|
|
1590
|
+
}
|
|
1591
|
+
},
|
|
1592
|
+
gotVersion
|
|
1593
|
+
);
|
|
1594
|
+
this.sessionHandshakeMetadata.set(connectedSession.to, parsedMetadata);
|
|
1595
|
+
this.updateSession(connectedSession);
|
|
1596
|
+
this.pendingSessions.delete(session);
|
|
1597
|
+
connectedSession.startActiveHeartbeat();
|
|
1598
|
+
}
|
|
1599
|
+
async validateHandshakeMetadata(handshakingSession, existingSession, rawMetadata, from) {
|
|
1600
|
+
let parsedMetadata = {};
|
|
1601
|
+
if (this.handshakeExtensions) {
|
|
1602
|
+
if (!import_value2.Value.Check(this.handshakeExtensions.schema, rawMetadata)) {
|
|
1603
|
+
this.rejectHandshakeRequest(
|
|
1604
|
+
handshakingSession,
|
|
1605
|
+
from,
|
|
1606
|
+
"received malformed handshake metadata",
|
|
1607
|
+
"MALFORMED_HANDSHAKE_META",
|
|
1608
|
+
{
|
|
1609
|
+
...handshakingSession.loggingMetadata,
|
|
1610
|
+
connectedTo: from,
|
|
1611
|
+
validationErrors: [
|
|
1612
|
+
...import_value2.Value.Errors(this.handshakeExtensions.schema, rawMetadata)
|
|
1613
|
+
]
|
|
1614
|
+
}
|
|
1615
|
+
);
|
|
1616
|
+
return false;
|
|
1617
|
+
}
|
|
1618
|
+
const previousParsedMetadata = existingSession ? this.sessionHandshakeMetadata.get(existingSession.to) : void 0;
|
|
1619
|
+
parsedMetadata = await this.handshakeExtensions.validate(
|
|
1620
|
+
rawMetadata,
|
|
1621
|
+
previousParsedMetadata
|
|
1622
|
+
);
|
|
1623
|
+
if (parsedMetadata === false) {
|
|
1624
|
+
this.rejectHandshakeRequest(
|
|
1625
|
+
handshakingSession,
|
|
1626
|
+
from,
|
|
1627
|
+
"rejected by handshake handler",
|
|
1628
|
+
"REJECTED_BY_CUSTOM_HANDLER",
|
|
1629
|
+
{
|
|
1630
|
+
...handshakingSession.loggingMetadata,
|
|
1631
|
+
connectedTo: from,
|
|
1632
|
+
clientId: this.clientId
|
|
1633
|
+
}
|
|
1634
|
+
);
|
|
1635
|
+
return false;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
return parsedMetadata;
|
|
1498
1639
|
}
|
|
1499
1640
|
};
|
|
1500
1641
|
|