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