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