@replit/river 0.23.16 → 0.24.0
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-UDXM64QK.js → chunk-AASMR3CQ.js} +24 -18
- package/dist/chunk-AASMR3CQ.js.map +1 -0
- package/dist/chunk-JA57I7MG.js +653 -0
- package/dist/chunk-JA57I7MG.js.map +1 -0
- package/dist/chunk-KX5PQRVN.js +382 -0
- package/dist/chunk-KX5PQRVN.js.map +1 -0
- package/dist/{chunk-LTSLICON.js → chunk-KYYB4DUR.js} +68 -519
- package/dist/chunk-KYYB4DUR.js.map +1 -0
- package/dist/chunk-NLQPPDOT.js +399 -0
- package/dist/chunk-NLQPPDOT.js.map +1 -0
- package/dist/{chunk-TXSQRTZB.js → chunk-PJGGC3LV.js} +55 -41
- package/dist/chunk-PJGGC3LV.js.map +1 -0
- package/dist/chunk-RXJLI2OP.js +50 -0
- package/dist/chunk-RXJLI2OP.js.map +1 -0
- package/dist/{chunk-6LCL2ZZF.js → chunk-TAH2GVTJ.js} +1 -1
- package/dist/chunk-TAH2GVTJ.js.map +1 -0
- package/dist/chunk-ZAT3R4CU.js +277 -0
- package/dist/chunk-ZAT3R4CU.js.map +1 -0
- package/dist/{client-0926d3d6.d.ts → client-ba0d3315.d.ts} +12 -15
- package/dist/{connection-99a67d3e.d.ts → connection-c3a96d09.d.ts} +1 -5
- package/dist/connection-d33e3246.d.ts +11 -0
- package/dist/{handshake-75d0124f.d.ts → handshake-cdead82a.d.ts} +149 -180
- package/dist/logging/index.cjs.map +1 -1
- package/dist/logging/index.d.cts +1 -1
- package/dist/logging/index.d.ts +1 -1
- package/dist/logging/index.js +1 -1
- package/dist/{index-ea74cdbb.d.ts → message-e6c560fd.d.ts} +2 -2
- package/dist/router/index.cjs +107 -530
- package/dist/router/index.cjs.map +1 -1
- package/dist/router/index.d.cts +12 -50
- package/dist/router/index.d.ts +12 -50
- package/dist/router/index.js +2 -4
- package/dist/server-2ef5e6ec.d.ts +42 -0
- package/dist/{services-75e84a9f.d.ts → services-e1417b33.d.ts} +7 -7
- package/dist/transport/impls/uds/client.cjs +1242 -1223
- package/dist/transport/impls/uds/client.cjs.map +1 -1
- package/dist/transport/impls/uds/client.d.cts +4 -4
- package/dist/transport/impls/uds/client.d.ts +4 -4
- package/dist/transport/impls/uds/client.js +7 -13
- package/dist/transport/impls/uds/client.js.map +1 -1
- package/dist/transport/impls/uds/server.cjs +1301 -1151
- package/dist/transport/impls/uds/server.cjs.map +1 -1
- package/dist/transport/impls/uds/server.d.cts +4 -4
- package/dist/transport/impls/uds/server.d.ts +4 -4
- package/dist/transport/impls/uds/server.js +6 -6
- package/dist/transport/impls/ws/client.cjs +980 -969
- package/dist/transport/impls/ws/client.cjs.map +1 -1
- package/dist/transport/impls/ws/client.d.cts +4 -4
- package/dist/transport/impls/ws/client.d.ts +4 -4
- package/dist/transport/impls/ws/client.js +6 -7
- package/dist/transport/impls/ws/client.js.map +1 -1
- package/dist/transport/impls/ws/server.cjs +1182 -1047
- package/dist/transport/impls/ws/server.cjs.map +1 -1
- package/dist/transport/impls/ws/server.d.cts +4 -4
- package/dist/transport/impls/ws/server.d.ts +4 -4
- package/dist/transport/impls/ws/server.js +6 -6
- package/dist/transport/index.cjs +1434 -1360
- package/dist/transport/index.cjs.map +1 -1
- package/dist/transport/index.d.cts +4 -4
- package/dist/transport/index.d.ts +4 -4
- package/dist/transport/index.js +9 -9
- package/dist/util/testHelpers.cjs +743 -309
- package/dist/util/testHelpers.cjs.map +1 -1
- package/dist/util/testHelpers.d.cts +10 -7
- package/dist/util/testHelpers.d.ts +10 -7
- package/dist/util/testHelpers.js +33 -10
- package/dist/util/testHelpers.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-6LCL2ZZF.js.map +0 -1
- package/dist/chunk-JA7XGTAL.js +0 -476
- package/dist/chunk-JA7XGTAL.js.map +0 -1
- package/dist/chunk-LTSLICON.js.map +0 -1
- package/dist/chunk-MQCGG6KL.js +0 -335
- package/dist/chunk-MQCGG6KL.js.map +0 -1
- package/dist/chunk-R47IZD67.js +0 -59
- package/dist/chunk-R47IZD67.js.map +0 -1
- package/dist/chunk-TXSQRTZB.js.map +0 -1
- package/dist/chunk-UDXM64QK.js.map +0 -1
- package/dist/chunk-WN77AT67.js +0 -476
- package/dist/chunk-WN77AT67.js.map +0 -1
- package/dist/chunk-YXDAOVP7.js +0 -347
- package/dist/chunk-YXDAOVP7.js.map +0 -1
- package/dist/connection-d738cc08.d.ts +0 -17
- package/dist/server-3740c5d9.d.ts +0 -24
|
@@ -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(),
|
|
@@ -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,12 +303,12 @@ 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: {
|
|
@@ -125,519 +320,58 @@ function handshakeRequestMessage({
|
|
|
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,
|
|
@@ -768,93 +501,714 @@ var BaseLogger = class {
|
|
|
768
501
|
this.output(msg, metadata ?? {}, "debug");
|
|
769
502
|
}
|
|
770
503
|
}
|
|
771
|
-
info(msg, metadata) {
|
|
772
|
-
if (LoggingLevels[this.minLevel] <= LoggingLevels.info) {
|
|
773
|
-
this.output(msg, metadata ?? {}, "info");
|
|
774
|
-
}
|
|
504
|
+
info(msg, metadata) {
|
|
505
|
+
if (LoggingLevels[this.minLevel] <= LoggingLevels.info) {
|
|
506
|
+
this.output(msg, metadata ?? {}, "info");
|
|
507
|
+
}
|
|
508
|
+
}
|
|
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
|
+
/**
|
|
650
|
+
* Index of the message we will send next (excluding handshake)
|
|
651
|
+
*/
|
|
652
|
+
seq;
|
|
653
|
+
/**
|
|
654
|
+
* Number of unique messages we've received this session (excluding handshake)
|
|
655
|
+
*/
|
|
656
|
+
ack;
|
|
657
|
+
sendBuffer;
|
|
658
|
+
constructor(id, from, to, seq, ack, sendBuffer, telemetry, options, log) {
|
|
659
|
+
super(from, options, log);
|
|
660
|
+
this.id = id;
|
|
661
|
+
this.to = to;
|
|
662
|
+
this.seq = seq;
|
|
663
|
+
this.ack = ack;
|
|
664
|
+
this.sendBuffer = sendBuffer;
|
|
665
|
+
this.telemetry = telemetry;
|
|
666
|
+
this.log = log;
|
|
667
|
+
}
|
|
668
|
+
get loggingMetadata() {
|
|
669
|
+
const spanContext = this.telemetry.span.spanContext();
|
|
670
|
+
return {
|
|
671
|
+
clientId: this.from,
|
|
672
|
+
connectedTo: this.to,
|
|
673
|
+
sessionId: this.id,
|
|
674
|
+
telemetry: {
|
|
675
|
+
traceId: spanContext.traceId,
|
|
676
|
+
spanId: spanContext.spanId
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
constructMsg(partialMsg) {
|
|
681
|
+
const msg = {
|
|
682
|
+
...partialMsg,
|
|
683
|
+
id: generateId(),
|
|
684
|
+
to: this.to,
|
|
685
|
+
from: this.from,
|
|
686
|
+
seq: this.seq,
|
|
687
|
+
ack: this.ack
|
|
688
|
+
};
|
|
689
|
+
this.seq++;
|
|
690
|
+
return msg;
|
|
691
|
+
}
|
|
692
|
+
nextSeq() {
|
|
693
|
+
return this.sendBuffer.length > 0 ? this.sendBuffer[0].seq : this.seq;
|
|
694
|
+
}
|
|
695
|
+
send(msg) {
|
|
696
|
+
const constructedMsg = this.constructMsg(msg);
|
|
697
|
+
this.sendBuffer.push(constructedMsg);
|
|
698
|
+
return constructedMsg.id;
|
|
699
|
+
}
|
|
700
|
+
_handleStateExit() {
|
|
701
|
+
}
|
|
702
|
+
_handleClose() {
|
|
703
|
+
this.sendBuffer.length = 0;
|
|
704
|
+
this.telemetry.span.end();
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
// transport/sessionStateMachine/SessionConnecting.ts
|
|
709
|
+
var SessionConnecting = class extends IdentifiedSession {
|
|
710
|
+
state = "Connecting" /* Connecting */;
|
|
711
|
+
connPromise;
|
|
712
|
+
listeners;
|
|
713
|
+
connectionTimeout;
|
|
714
|
+
constructor(connPromise, listeners, ...args) {
|
|
715
|
+
super(...args);
|
|
716
|
+
this.connPromise = connPromise;
|
|
717
|
+
this.listeners = listeners;
|
|
718
|
+
this.connectionTimeout = setTimeout(() => {
|
|
719
|
+
listeners.onConnectionTimeout();
|
|
720
|
+
}, this.options.connectionTimeoutMs);
|
|
721
|
+
connPromise.then(
|
|
722
|
+
(conn) => {
|
|
723
|
+
if (this._isConsumed)
|
|
724
|
+
return;
|
|
725
|
+
listeners.onConnectionEstablished(conn);
|
|
726
|
+
},
|
|
727
|
+
(err) => {
|
|
728
|
+
if (this._isConsumed)
|
|
729
|
+
return;
|
|
730
|
+
listeners.onConnectionFailed(err);
|
|
731
|
+
}
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
// close a pending connection if it resolves, ignore errors if the promise
|
|
735
|
+
// ends up rejected anyways
|
|
736
|
+
bestEffortClose() {
|
|
737
|
+
void this.connPromise.then((conn) => conn.close()).catch(() => {
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
_handleStateExit() {
|
|
741
|
+
super._handleStateExit();
|
|
742
|
+
clearTimeout(this.connectionTimeout);
|
|
743
|
+
this.connectionTimeout = void 0;
|
|
744
|
+
}
|
|
745
|
+
_handleClose() {
|
|
746
|
+
this.bestEffortClose();
|
|
747
|
+
super._handleClose();
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
// transport/sessionStateMachine/SessionNoConnection.ts
|
|
752
|
+
var SessionNoConnection = class extends IdentifiedSession {
|
|
753
|
+
state = "NoConnection" /* NoConnection */;
|
|
754
|
+
listeners;
|
|
755
|
+
gracePeriodTimeout;
|
|
756
|
+
constructor(listeners, ...args) {
|
|
757
|
+
super(...args);
|
|
758
|
+
this.listeners = listeners;
|
|
759
|
+
this.gracePeriodTimeout = setTimeout(() => {
|
|
760
|
+
this.listeners.onSessionGracePeriodElapsed();
|
|
761
|
+
}, this.options.sessionDisconnectGraceMs);
|
|
762
|
+
}
|
|
763
|
+
_handleClose() {
|
|
764
|
+
super._handleClose();
|
|
765
|
+
}
|
|
766
|
+
_handleStateExit() {
|
|
767
|
+
super._handleStateExit();
|
|
768
|
+
if (this.gracePeriodTimeout) {
|
|
769
|
+
clearTimeout(this.gracePeriodTimeout);
|
|
770
|
+
this.gracePeriodTimeout = void 0;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
// tracing/index.ts
|
|
776
|
+
var import_api = require("@opentelemetry/api");
|
|
777
|
+
|
|
778
|
+
// package.json
|
|
779
|
+
var version = "0.24.0";
|
|
780
|
+
|
|
781
|
+
// tracing/index.ts
|
|
782
|
+
function getPropagationContext(ctx) {
|
|
783
|
+
const tracing = {
|
|
784
|
+
traceparent: "",
|
|
785
|
+
tracestate: ""
|
|
786
|
+
};
|
|
787
|
+
import_api.propagation.inject(ctx, tracing);
|
|
788
|
+
return tracing;
|
|
789
|
+
}
|
|
790
|
+
function createSessionTelemetryInfo(sessionId, to, from, propagationCtx) {
|
|
791
|
+
const parentCtx = propagationCtx ? import_api.propagation.extract(import_api.context.active(), propagationCtx) : import_api.context.active();
|
|
792
|
+
const span = tracer.startSpan(
|
|
793
|
+
`session ${sessionId}`,
|
|
794
|
+
{
|
|
795
|
+
attributes: {
|
|
796
|
+
component: "river",
|
|
797
|
+
"river.session.id": sessionId,
|
|
798
|
+
"river.session.to": to,
|
|
799
|
+
"river.session.from": from
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
parentCtx
|
|
803
|
+
);
|
|
804
|
+
const ctx = import_api.trace.setSpan(parentCtx, span);
|
|
805
|
+
return { span, ctx };
|
|
806
|
+
}
|
|
807
|
+
var tracer = import_api.trace.getTracer("river", version);
|
|
808
|
+
var tracing_default = tracer;
|
|
809
|
+
|
|
810
|
+
// transport/sessionStateMachine/SessionWaitingForHandshake.ts
|
|
811
|
+
var SessionWaitingForHandshake = class extends CommonSession {
|
|
812
|
+
state = "WaitingForHandshake" /* WaitingForHandshake */;
|
|
813
|
+
conn;
|
|
814
|
+
listeners;
|
|
815
|
+
handshakeTimeout;
|
|
816
|
+
constructor(conn, listeners, ...args) {
|
|
817
|
+
super(...args);
|
|
818
|
+
this.conn = conn;
|
|
819
|
+
this.listeners = listeners;
|
|
820
|
+
this.handshakeTimeout = setTimeout(() => {
|
|
821
|
+
listeners.onHandshakeTimeout();
|
|
822
|
+
}, this.options.handshakeTimeoutMs);
|
|
823
|
+
this.conn.addDataListener(this.onHandshakeData);
|
|
824
|
+
this.conn.addErrorListener(listeners.onConnectionErrored);
|
|
825
|
+
this.conn.addCloseListener(listeners.onConnectionClosed);
|
|
826
|
+
}
|
|
827
|
+
onHandshakeData = (msg) => {
|
|
828
|
+
const parsedMsg = this.parseMsg(msg);
|
|
829
|
+
if (parsedMsg === null) {
|
|
830
|
+
this.listeners.onInvalidHandshake("could not parse message");
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
this.listeners.onHandshake(parsedMsg);
|
|
834
|
+
};
|
|
835
|
+
get loggingMetadata() {
|
|
836
|
+
return {
|
|
837
|
+
clientId: this.from,
|
|
838
|
+
connId: this.conn.id
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
sendHandshake(msg) {
|
|
842
|
+
return this.conn.send(this.options.codec.toBuffer(msg));
|
|
775
843
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
844
|
+
_handleStateExit() {
|
|
845
|
+
this.conn.removeDataListener(this.onHandshakeData);
|
|
846
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
847
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
848
|
+
clearTimeout(this.handshakeTimeout);
|
|
849
|
+
this.handshakeTimeout = void 0;
|
|
780
850
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
this.output(msg, metadata ?? {}, "error");
|
|
784
|
-
}
|
|
851
|
+
_handleClose() {
|
|
852
|
+
this.conn.close();
|
|
785
853
|
}
|
|
786
854
|
};
|
|
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
855
|
|
|
794
|
-
// transport/
|
|
795
|
-
var
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
856
|
+
// transport/sessionStateMachine/SessionHandshaking.ts
|
|
857
|
+
var SessionHandshaking = class extends IdentifiedSession {
|
|
858
|
+
state = "Handshaking" /* Handshaking */;
|
|
859
|
+
conn;
|
|
860
|
+
listeners;
|
|
861
|
+
handshakeTimeout;
|
|
862
|
+
constructor(conn, listeners, ...args) {
|
|
863
|
+
super(...args);
|
|
864
|
+
this.conn = conn;
|
|
865
|
+
this.listeners = listeners;
|
|
866
|
+
this.handshakeTimeout = setTimeout(() => {
|
|
867
|
+
listeners.onHandshakeTimeout();
|
|
868
|
+
}, this.options.handshakeTimeoutMs);
|
|
869
|
+
this.conn.addDataListener(this.onHandshakeData);
|
|
870
|
+
this.conn.addErrorListener(listeners.onConnectionErrored);
|
|
871
|
+
this.conn.addCloseListener(listeners.onConnectionClosed);
|
|
872
|
+
}
|
|
873
|
+
onHandshakeData = (msg) => {
|
|
874
|
+
const parsedMsg = this.parseMsg(msg);
|
|
875
|
+
if (parsedMsg === null) {
|
|
876
|
+
this.listeners.onInvalidHandshake("could not parse message");
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
this.listeners.onHandshake(parsedMsg);
|
|
880
|
+
};
|
|
881
|
+
sendHandshake(msg) {
|
|
882
|
+
return this.conn.send(this.options.codec.toBuffer(msg));
|
|
883
|
+
}
|
|
884
|
+
_handleStateExit() {
|
|
885
|
+
super._handleStateExit();
|
|
886
|
+
this.conn.removeDataListener(this.onHandshakeData);
|
|
887
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
888
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
889
|
+
clearTimeout(this.handshakeTimeout);
|
|
890
|
+
}
|
|
891
|
+
_handleClose() {
|
|
892
|
+
super._handleClose();
|
|
893
|
+
this.conn.close();
|
|
894
|
+
}
|
|
799
895
|
};
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
896
|
+
|
|
897
|
+
// transport/sessionStateMachine/SessionConnected.ts
|
|
898
|
+
var import_api2 = require("@opentelemetry/api");
|
|
899
|
+
var SessionConnected = class extends IdentifiedSession {
|
|
900
|
+
state = "Connected" /* Connected */;
|
|
901
|
+
conn;
|
|
902
|
+
listeners;
|
|
903
|
+
heartbeatHandle;
|
|
904
|
+
heartbeatMisses = 0;
|
|
905
|
+
get isActivelyHeartbeating() {
|
|
906
|
+
return this.heartbeatHandle !== void 0;
|
|
804
907
|
}
|
|
805
|
-
|
|
806
|
-
|
|
908
|
+
updateBookkeeping(ack, seq) {
|
|
909
|
+
this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
|
|
910
|
+
this.ack = seq + 1;
|
|
911
|
+
this.heartbeatMisses = 0;
|
|
807
912
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
913
|
+
send(msg) {
|
|
914
|
+
const constructedMsg = this.constructMsg(msg);
|
|
915
|
+
this.sendBuffer.push(constructedMsg);
|
|
916
|
+
this.conn.send(this.options.codec.toBuffer(constructedMsg));
|
|
917
|
+
return constructedMsg.id;
|
|
918
|
+
}
|
|
919
|
+
constructor(conn, listeners, ...args) {
|
|
920
|
+
super(...args);
|
|
921
|
+
this.conn = conn;
|
|
922
|
+
this.listeners = listeners;
|
|
923
|
+
this.conn.addDataListener(this.onMessageData);
|
|
924
|
+
this.conn.addCloseListener(listeners.onConnectionClosed);
|
|
925
|
+
this.conn.addErrorListener(listeners.onConnectionErrored);
|
|
926
|
+
if (this.sendBuffer.length > 0) {
|
|
927
|
+
this.log?.debug(
|
|
928
|
+
`sending ${this.sendBuffer.length} buffered messages`,
|
|
929
|
+
this.loggingMetadata
|
|
930
|
+
);
|
|
811
931
|
}
|
|
812
|
-
this.
|
|
813
|
-
|
|
814
|
-
removeEventListener(eventType, handler) {
|
|
815
|
-
const handlers = this.eventListeners[eventType];
|
|
816
|
-
if (handlers) {
|
|
817
|
-
this.eventListeners[eventType]?.delete(handler);
|
|
932
|
+
for (const msg of this.sendBuffer) {
|
|
933
|
+
conn.send(this.options.codec.toBuffer(msg));
|
|
818
934
|
}
|
|
819
935
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
936
|
+
startActiveHeartbeat() {
|
|
937
|
+
this.heartbeatHandle = setInterval(() => {
|
|
938
|
+
const misses = this.heartbeatMisses;
|
|
939
|
+
const missDuration = misses * this.options.heartbeatIntervalMs;
|
|
940
|
+
if (misses >= this.options.heartbeatsUntilDead) {
|
|
941
|
+
this.log?.info(
|
|
942
|
+
`closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
|
|
943
|
+
this.loggingMetadata
|
|
944
|
+
);
|
|
945
|
+
this.telemetry.span.addEvent("closing connection due to inactivity");
|
|
946
|
+
this.conn.close();
|
|
947
|
+
clearInterval(this.heartbeatHandle);
|
|
948
|
+
this.heartbeatHandle = void 0;
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
this.sendHeartbeat();
|
|
952
|
+
this.heartbeatMisses++;
|
|
953
|
+
}, this.options.heartbeatIntervalMs);
|
|
954
|
+
}
|
|
955
|
+
sendHeartbeat() {
|
|
956
|
+
this.send({
|
|
957
|
+
streamId: "heartbeat",
|
|
958
|
+
controlFlags: 1 /* AckBit */,
|
|
959
|
+
payload: {
|
|
960
|
+
type: "ACK"
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
onMessageData = (msg) => {
|
|
965
|
+
const parsedMsg = this.parseMsg(msg);
|
|
966
|
+
if (parsedMsg === null)
|
|
967
|
+
return;
|
|
968
|
+
if (parsedMsg.seq !== this.ack) {
|
|
969
|
+
if (parsedMsg.seq < this.ack) {
|
|
970
|
+
this.log?.debug(
|
|
971
|
+
`received duplicate msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack}), discarding`,
|
|
972
|
+
{
|
|
973
|
+
...this.loggingMetadata,
|
|
974
|
+
transportMessage: parsedMsg
|
|
975
|
+
}
|
|
976
|
+
);
|
|
977
|
+
} else {
|
|
978
|
+
const reason = `received out-of-order msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack})`;
|
|
979
|
+
this.log?.error(reason, {
|
|
980
|
+
...this.loggingMetadata,
|
|
981
|
+
transportMessage: parsedMsg,
|
|
982
|
+
tags: ["invariant-violation"]
|
|
983
|
+
});
|
|
984
|
+
this.telemetry.span.setStatus({
|
|
985
|
+
code: import_api2.SpanStatusCode.ERROR,
|
|
986
|
+
message: reason
|
|
987
|
+
});
|
|
988
|
+
this.listeners.onInvalidMessage(reason);
|
|
826
989
|
}
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
this.log?.debug(`received msg`, {
|
|
993
|
+
...this.loggingMetadata,
|
|
994
|
+
transportMessage: parsedMsg
|
|
995
|
+
});
|
|
996
|
+
this.updateBookkeeping(parsedMsg.ack, parsedMsg.seq);
|
|
997
|
+
if (!isAck(parsedMsg.controlFlags)) {
|
|
998
|
+
this.listeners.onMessage(parsedMsg);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
this.log?.debug(`discarding msg (ack bit set)`, {
|
|
1002
|
+
...this.loggingMetadata,
|
|
1003
|
+
transportMessage: parsedMsg
|
|
1004
|
+
});
|
|
1005
|
+
if (!this.isActivelyHeartbeating) {
|
|
1006
|
+
this.sendHeartbeat();
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
_handleStateExit() {
|
|
1010
|
+
super._handleStateExit();
|
|
1011
|
+
this.conn.removeDataListener(this.onMessageData);
|
|
1012
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
1013
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
1014
|
+
clearInterval(this.heartbeatHandle);
|
|
1015
|
+
this.heartbeatHandle = void 0;
|
|
1016
|
+
}
|
|
1017
|
+
_handleClose() {
|
|
1018
|
+
super._handleClose();
|
|
1019
|
+
this.conn.close();
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1023
|
+
// transport/sessionStateMachine/transitions.ts
|
|
1024
|
+
function inheritSharedSession(session) {
|
|
1025
|
+
return [
|
|
1026
|
+
session.id,
|
|
1027
|
+
session.from,
|
|
1028
|
+
session.to,
|
|
1029
|
+
session.seq,
|
|
1030
|
+
session.ack,
|
|
1031
|
+
session.sendBuffer,
|
|
1032
|
+
session.telemetry,
|
|
1033
|
+
session.options,
|
|
1034
|
+
session.log
|
|
1035
|
+
];
|
|
1036
|
+
}
|
|
1037
|
+
var SessionStateGraph = {
|
|
1038
|
+
entrypoints: {
|
|
1039
|
+
NoConnection(to, from, listeners, options, log) {
|
|
1040
|
+
const id = `session-${generateId()}`;
|
|
1041
|
+
const telemetry = createSessionTelemetryInfo(id, to, from);
|
|
1042
|
+
const sendBuffer = [];
|
|
1043
|
+
const session = new SessionNoConnection(
|
|
1044
|
+
listeners,
|
|
1045
|
+
id,
|
|
1046
|
+
from,
|
|
1047
|
+
to,
|
|
1048
|
+
0,
|
|
1049
|
+
0,
|
|
1050
|
+
sendBuffer,
|
|
1051
|
+
telemetry,
|
|
1052
|
+
options,
|
|
1053
|
+
log
|
|
1054
|
+
);
|
|
1055
|
+
session.log?.info(`session ${session.id} created in NoConnection state`, {
|
|
1056
|
+
...session.loggingMetadata,
|
|
1057
|
+
tags: ["state-transition"]
|
|
1058
|
+
});
|
|
1059
|
+
return session;
|
|
1060
|
+
},
|
|
1061
|
+
WaitingForHandshake(from, conn, listeners, options, log) {
|
|
1062
|
+
const session = new SessionWaitingForHandshake(
|
|
1063
|
+
conn,
|
|
1064
|
+
listeners,
|
|
1065
|
+
from,
|
|
1066
|
+
options,
|
|
1067
|
+
log
|
|
1068
|
+
);
|
|
1069
|
+
session.log?.info(`session created in WaitingForHandshake state`, {
|
|
1070
|
+
...session.loggingMetadata,
|
|
1071
|
+
tags: ["state-transition"]
|
|
1072
|
+
});
|
|
1073
|
+
return session;
|
|
1074
|
+
}
|
|
1075
|
+
},
|
|
1076
|
+
// All of the transitions 'move'/'consume' the old session and return a new one.
|
|
1077
|
+
// After a session is transitioned, any usage of the old session will throw.
|
|
1078
|
+
transition: {
|
|
1079
|
+
// happy path transitions
|
|
1080
|
+
NoConnectionToConnecting(oldSession, connPromise, listeners) {
|
|
1081
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1082
|
+
oldSession._handleStateExit();
|
|
1083
|
+
const session = new SessionConnecting(
|
|
1084
|
+
connPromise,
|
|
1085
|
+
listeners,
|
|
1086
|
+
...carriedState
|
|
1087
|
+
);
|
|
1088
|
+
session.log?.info(
|
|
1089
|
+
`session ${session.id} transition from NoConnection to Connecting`,
|
|
1090
|
+
{
|
|
1091
|
+
...session.loggingMetadata,
|
|
1092
|
+
tags: ["state-transition"]
|
|
1093
|
+
}
|
|
1094
|
+
);
|
|
1095
|
+
return session;
|
|
1096
|
+
},
|
|
1097
|
+
ConnectingToHandshaking(oldSession, conn, listeners) {
|
|
1098
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1099
|
+
oldSession._handleStateExit();
|
|
1100
|
+
const session = new SessionHandshaking(conn, listeners, ...carriedState);
|
|
1101
|
+
session.log?.info(
|
|
1102
|
+
`session ${session.id} transition from Connecting to Handshaking`,
|
|
1103
|
+
{
|
|
1104
|
+
...session.loggingMetadata,
|
|
1105
|
+
tags: ["state-transition"]
|
|
1106
|
+
}
|
|
1107
|
+
);
|
|
1108
|
+
return session;
|
|
1109
|
+
},
|
|
1110
|
+
HandshakingToConnected(oldSession, listeners) {
|
|
1111
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1112
|
+
const conn = oldSession.conn;
|
|
1113
|
+
oldSession._handleStateExit();
|
|
1114
|
+
const session = new SessionConnected(conn, listeners, ...carriedState);
|
|
1115
|
+
session.log?.info(
|
|
1116
|
+
`session ${session.id} transition from Handshaking to Connected`,
|
|
1117
|
+
{
|
|
1118
|
+
...session.loggingMetadata,
|
|
1119
|
+
tags: ["state-transition"]
|
|
1120
|
+
}
|
|
1121
|
+
);
|
|
1122
|
+
return session;
|
|
1123
|
+
},
|
|
1124
|
+
WaitingForHandshakeToConnected(pendingSession, oldSession, sessionId, to, propagationCtx, listeners) {
|
|
1125
|
+
const conn = pendingSession.conn;
|
|
1126
|
+
const { from, options } = pendingSession;
|
|
1127
|
+
const carriedState = oldSession ? (
|
|
1128
|
+
// old session exists, inherit state
|
|
1129
|
+
inheritSharedSession(oldSession)
|
|
1130
|
+
) : (
|
|
1131
|
+
// old session does not exist, create new state
|
|
1132
|
+
[
|
|
1133
|
+
sessionId,
|
|
1134
|
+
from,
|
|
1135
|
+
to,
|
|
1136
|
+
0,
|
|
1137
|
+
0,
|
|
1138
|
+
[],
|
|
1139
|
+
createSessionTelemetryInfo(sessionId, to, from, propagationCtx),
|
|
1140
|
+
options,
|
|
1141
|
+
pendingSession.log
|
|
1142
|
+
]
|
|
1143
|
+
);
|
|
1144
|
+
pendingSession._handleStateExit();
|
|
1145
|
+
oldSession?._handleStateExit();
|
|
1146
|
+
const session = new SessionConnected(conn, listeners, ...carriedState);
|
|
1147
|
+
session.log?.info(
|
|
1148
|
+
`session ${session.id} transition from WaitingForHandshake to Connected`,
|
|
1149
|
+
{
|
|
1150
|
+
...session.loggingMetadata,
|
|
1151
|
+
tags: ["state-transition"]
|
|
1152
|
+
}
|
|
1153
|
+
);
|
|
1154
|
+
return session;
|
|
1155
|
+
},
|
|
1156
|
+
// disconnect paths
|
|
1157
|
+
ConnectingToNoConnection(oldSession, listeners) {
|
|
1158
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1159
|
+
oldSession.bestEffortClose();
|
|
1160
|
+
oldSession._handleStateExit();
|
|
1161
|
+
const session = new SessionNoConnection(listeners, ...carriedState);
|
|
1162
|
+
session.log?.info(
|
|
1163
|
+
`session ${session.id} transition from Connecting to NoConnection`,
|
|
1164
|
+
{
|
|
1165
|
+
...session.loggingMetadata,
|
|
1166
|
+
tags: ["state-transition"]
|
|
1167
|
+
}
|
|
1168
|
+
);
|
|
1169
|
+
return session;
|
|
1170
|
+
},
|
|
1171
|
+
HandshakingToNoConnection(oldSession, listeners) {
|
|
1172
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1173
|
+
oldSession.conn.close();
|
|
1174
|
+
oldSession._handleStateExit();
|
|
1175
|
+
const session = new SessionNoConnection(listeners, ...carriedState);
|
|
1176
|
+
session.log?.info(
|
|
1177
|
+
`session ${session.id} transition from Handshaking to NoConnection`,
|
|
1178
|
+
{
|
|
1179
|
+
...session.loggingMetadata,
|
|
1180
|
+
tags: ["state-transition"]
|
|
1181
|
+
}
|
|
1182
|
+
);
|
|
1183
|
+
return session;
|
|
1184
|
+
},
|
|
1185
|
+
ConnectedToNoConnection(oldSession, listeners) {
|
|
1186
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1187
|
+
oldSession.conn.close();
|
|
1188
|
+
oldSession._handleStateExit();
|
|
1189
|
+
const session = new SessionNoConnection(listeners, ...carriedState);
|
|
1190
|
+
session.log?.info(
|
|
1191
|
+
`session ${session.id} transition from Connected to NoConnection`,
|
|
1192
|
+
{
|
|
1193
|
+
...session.loggingMetadata,
|
|
1194
|
+
tags: ["state-transition"]
|
|
1195
|
+
}
|
|
1196
|
+
);
|
|
1197
|
+
return session;
|
|
827
1198
|
}
|
|
828
1199
|
}
|
|
829
1200
|
};
|
|
830
1201
|
|
|
831
1202
|
// transport/transport.ts
|
|
832
|
-
var import_api3 = require("@opentelemetry/api");
|
|
833
1203
|
var Transport = class {
|
|
834
1204
|
/**
|
|
835
1205
|
* The status of the transport.
|
|
836
1206
|
*/
|
|
837
1207
|
status;
|
|
838
|
-
/**
|
|
839
|
-
* The {@link Codec} used to encode and decode messages.
|
|
840
|
-
*/
|
|
841
|
-
codec;
|
|
842
1208
|
/**
|
|
843
1209
|
* The client ID of this transport.
|
|
844
1210
|
*/
|
|
845
1211
|
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
|
-
);
|
|
857
|
-
}
|
|
858
1212
|
/**
|
|
859
1213
|
* The event dispatcher for handling events of type EventTypes.
|
|
860
1214
|
*/
|
|
@@ -864,320 +1218,35 @@ var Transport = class {
|
|
|
864
1218
|
*/
|
|
865
1219
|
options;
|
|
866
1220
|
log;
|
|
1221
|
+
sessions;
|
|
867
1222
|
/**
|
|
868
|
-
* Creates a new Transport instance.
|
|
869
|
-
*
|
|
870
|
-
* @param
|
|
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";
|
|
880
|
-
}
|
|
881
|
-
bindLogger(fn, level) {
|
|
882
|
-
if (typeof fn === "function") {
|
|
883
|
-
this.log = createLogProxy(new BaseLogger(fn, level));
|
|
884
|
-
return;
|
|
885
|
-
}
|
|
886
|
-
this.log = createLogProxy(fn);
|
|
887
|
-
}
|
|
888
|
-
/**
|
|
889
|
-
* Called when a new connection is established
|
|
890
|
-
* and we know the identity of the connected client.
|
|
891
|
-
* @param conn The connection object.
|
|
892
|
-
*/
|
|
893
|
-
onConnect(conn, session, isTransparentReconnect) {
|
|
894
|
-
this.eventDispatcher.dispatchEvent("connectionStatus", {
|
|
895
|
-
status: "connect",
|
|
896
|
-
conn
|
|
897
|
-
});
|
|
898
|
-
conn.telemetry = createConnectionTelemetryInfo(conn, session.telemetry);
|
|
899
|
-
session.replaceWithNewConnection(conn, isTransparentReconnect);
|
|
900
|
-
this.log?.info(`connected to ${session.to}`, {
|
|
901
|
-
...conn.loggingMetadata,
|
|
902
|
-
...session.loggingMetadata
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
createSession(to, conn, propagationCtx) {
|
|
906
|
-
const session = new Session(
|
|
907
|
-
conn,
|
|
908
|
-
this.clientId,
|
|
909
|
-
to,
|
|
910
|
-
this.options,
|
|
911
|
-
propagationCtx
|
|
912
|
-
);
|
|
913
|
-
if (this.log) {
|
|
914
|
-
session.bindLogger(this.log);
|
|
915
|
-
}
|
|
916
|
-
const currentSession = this.sessions.get(session.to);
|
|
917
|
-
if (currentSession) {
|
|
918
|
-
this.log?.warn(
|
|
919
|
-
`session ${session.id} from ${session.to} surreptitiously replacing ${currentSession.id}`,
|
|
920
|
-
{
|
|
921
|
-
...currentSession.loggingMetadata,
|
|
922
|
-
tags: ["invariant-violation"]
|
|
923
|
-
}
|
|
924
|
-
);
|
|
925
|
-
this.deleteSession({
|
|
926
|
-
session: currentSession,
|
|
927
|
-
closeHandshakingConnection: false
|
|
928
|
-
});
|
|
929
|
-
}
|
|
930
|
-
this.sessions.set(session.to, session);
|
|
931
|
-
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
932
|
-
status: "connect",
|
|
933
|
-
session
|
|
934
|
-
});
|
|
935
|
-
return session;
|
|
936
|
-
}
|
|
937
|
-
createNewSession({
|
|
938
|
-
to,
|
|
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;
|
|
954
|
-
}
|
|
955
|
-
session = this.createSession(to, conn, propagationCtx);
|
|
956
|
-
session.advertisedSessionId = sessionId;
|
|
957
|
-
this.log?.info(`created new session for ${to}`, session.loggingMetadata);
|
|
958
|
-
return session;
|
|
959
|
-
}
|
|
960
|
-
getExistingSession({
|
|
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;
|
|
973
|
-
}
|
|
974
|
-
this.log?.info(
|
|
975
|
-
`reused existing session for ${to}`,
|
|
976
|
-
session.loggingMetadata
|
|
977
|
-
);
|
|
978
|
-
return session;
|
|
979
|
-
}
|
|
980
|
-
getOrCreateSession({
|
|
981
|
-
to,
|
|
982
|
-
conn,
|
|
983
|
-
handshakingConn,
|
|
984
|
-
sessionId,
|
|
985
|
-
propagationCtx
|
|
986
|
-
}) {
|
|
987
|
-
let session = this.sessions.get(to);
|
|
988
|
-
const isReconnect = session !== void 0;
|
|
989
|
-
let isTransparentReconnect = isReconnect;
|
|
990
|
-
if (session?.advertisedSessionId !== void 0 && sessionId !== void 0 && session.advertisedSessionId !== sessionId) {
|
|
991
|
-
this.log?.info(
|
|
992
|
-
`session for ${to} already exists but has a different session id (expected: ${session.advertisedSessionId}, got: ${sessionId}), creating a new one`,
|
|
993
|
-
session.loggingMetadata
|
|
994
|
-
);
|
|
995
|
-
this.deleteSession({
|
|
996
|
-
session,
|
|
997
|
-
closeHandshakingConnection: handshakingConn !== void 0,
|
|
998
|
-
handshakingConn
|
|
999
|
-
});
|
|
1000
|
-
isTransparentReconnect = false;
|
|
1001
|
-
session = void 0;
|
|
1002
|
-
}
|
|
1003
|
-
if (!session) {
|
|
1004
|
-
session = this.createSession(to, conn, propagationCtx);
|
|
1005
|
-
this.log?.info(
|
|
1006
|
-
`no session for ${to}, created a new one`,
|
|
1007
|
-
session.loggingMetadata
|
|
1008
|
-
);
|
|
1009
|
-
}
|
|
1010
|
-
if (sessionId !== void 0) {
|
|
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}`,
|
|
1032
|
-
{
|
|
1033
|
-
...session.loggingMetadata,
|
|
1034
|
-
tags: ["invariant-violation"]
|
|
1035
|
-
}
|
|
1036
|
-
);
|
|
1037
|
-
return;
|
|
1038
|
-
}
|
|
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
|
-
}
|
|
1049
|
-
/**
|
|
1050
|
-
* The downstream implementation needs to call this when a connection is closed.
|
|
1051
|
-
* @param conn The connection object.
|
|
1052
|
-
* @param connectedTo The peer we are connected to.
|
|
1223
|
+
* Creates a new Transport instance.
|
|
1224
|
+
* @param codec The codec used to encode and decode messages.
|
|
1225
|
+
* @param clientId The client ID of this transport.
|
|
1053
1226
|
*/
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
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
|
-
});
|
|
1227
|
+
constructor(clientId, providedOptions) {
|
|
1228
|
+
this.options = { ...defaultTransportOptions, ...providedOptions };
|
|
1229
|
+
this.eventDispatcher = new EventDispatcher();
|
|
1230
|
+
this.clientId = clientId;
|
|
1231
|
+
this.status = "open";
|
|
1232
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
1089
1233
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
*/
|
|
1095
|
-
parseMsg(msg, conn) {
|
|
1096
|
-
const parsedMsg = this.codec.fromBuffer(msg);
|
|
1097
|
-
if (parsedMsg === null) {
|
|
1098
|
-
const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));
|
|
1099
|
-
this.log?.error(
|
|
1100
|
-
`received malformed msg, killing conn: ${decodedBuffer}`,
|
|
1101
|
-
{
|
|
1102
|
-
clientId: this.clientId,
|
|
1103
|
-
...conn.loggingMetadata
|
|
1104
|
-
}
|
|
1105
|
-
);
|
|
1106
|
-
return null;
|
|
1107
|
-
}
|
|
1108
|
-
if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
|
|
1109
|
-
this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
|
|
1110
|
-
clientId: this.clientId,
|
|
1111
|
-
...conn.loggingMetadata,
|
|
1112
|
-
validationErrors: [
|
|
1113
|
-
...import_value.Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
|
|
1114
|
-
]
|
|
1115
|
-
});
|
|
1116
|
-
return null;
|
|
1234
|
+
bindLogger(fn, level) {
|
|
1235
|
+
if (typeof fn === "function") {
|
|
1236
|
+
this.log = createLogProxy(new BaseLogger(fn, level));
|
|
1237
|
+
return;
|
|
1117
1238
|
}
|
|
1118
|
-
|
|
1239
|
+
this.log = createLogProxy(fn);
|
|
1119
1240
|
}
|
|
1120
1241
|
/**
|
|
1121
1242
|
* Called when a message is received by this transport.
|
|
1122
1243
|
* You generally shouldn't need to override this in downstream transport implementations.
|
|
1123
1244
|
* @param msg The received message.
|
|
1124
1245
|
*/
|
|
1125
|
-
handleMsg(msg
|
|
1246
|
+
handleMsg(msg) {
|
|
1126
1247
|
if (this.getStatus() !== "open")
|
|
1127
1248
|
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
|
-
}
|
|
1249
|
+
this.eventDispatcher.dispatchEvent("message", msg);
|
|
1181
1250
|
}
|
|
1182
1251
|
/**
|
|
1183
1252
|
* Adds a listener to this transport.
|
|
@@ -1195,34 +1264,6 @@ var Transport = class {
|
|
|
1195
1264
|
removeEventListener(type, handler) {
|
|
1196
1265
|
this.eventDispatcher.removeEventListener(type, handler);
|
|
1197
1266
|
}
|
|
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
|
-
sendCloseStream(to, streamId) {
|
|
1218
|
-
return this.send(to, {
|
|
1219
|
-
streamId,
|
|
1220
|
-
controlFlags: 4 /* StreamClosedBit */,
|
|
1221
|
-
payload: {
|
|
1222
|
-
type: "CLOSE"
|
|
1223
|
-
}
|
|
1224
|
-
});
|
|
1225
|
-
}
|
|
1226
1267
|
protocolError(type, message) {
|
|
1227
1268
|
this.eventDispatcher.dispatchEvent("protocolError", { type, message });
|
|
1228
1269
|
}
|
|
@@ -1234,7 +1275,7 @@ var Transport = class {
|
|
|
1234
1275
|
close() {
|
|
1235
1276
|
this.status = "closed";
|
|
1236
1277
|
for (const session of this.sessions.values()) {
|
|
1237
|
-
this.deleteSession(
|
|
1278
|
+
this.deleteSession(session);
|
|
1238
1279
|
}
|
|
1239
1280
|
this.eventDispatcher.dispatchEvent("transportStatus", {
|
|
1240
1281
|
status: this.status
|
|
@@ -1245,6 +1286,68 @@ var Transport = class {
|
|
|
1245
1286
|
getStatus() {
|
|
1246
1287
|
return this.status;
|
|
1247
1288
|
}
|
|
1289
|
+
updateSession(session) {
|
|
1290
|
+
const activeSession = this.sessions.get(session.to);
|
|
1291
|
+
if (activeSession && activeSession.id !== session.id) {
|
|
1292
|
+
const msg = `attempt to transition active session for ${session.to} but active session (${activeSession.id}) is different from handle (${session.id})`;
|
|
1293
|
+
throw new Error(msg);
|
|
1294
|
+
}
|
|
1295
|
+
this.sessions.set(session.to, session);
|
|
1296
|
+
if (!activeSession) {
|
|
1297
|
+
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
1298
|
+
status: "connect",
|
|
1299
|
+
session
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
this.eventDispatcher.dispatchEvent("sessionTransition", {
|
|
1303
|
+
state: session.state,
|
|
1304
|
+
session
|
|
1305
|
+
});
|
|
1306
|
+
return session;
|
|
1307
|
+
}
|
|
1308
|
+
// state transitions
|
|
1309
|
+
deleteSession(session) {
|
|
1310
|
+
session.log?.info(`closing session ${session.id}`, session.loggingMetadata);
|
|
1311
|
+
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
1312
|
+
status: "disconnect",
|
|
1313
|
+
session
|
|
1314
|
+
});
|
|
1315
|
+
session.close();
|
|
1316
|
+
this.sessions.delete(session.to);
|
|
1317
|
+
}
|
|
1318
|
+
// common listeners
|
|
1319
|
+
onSessionGracePeriodElapsed(session) {
|
|
1320
|
+
this.log?.warn(
|
|
1321
|
+
`session to ${session.to} grace period elapsed, closing`,
|
|
1322
|
+
session.loggingMetadata
|
|
1323
|
+
);
|
|
1324
|
+
this.deleteSession(session);
|
|
1325
|
+
}
|
|
1326
|
+
onConnectingFailed(session) {
|
|
1327
|
+
const noConnectionSession = SessionStateGraph.transition.ConnectingToNoConnection(session, {
|
|
1328
|
+
onSessionGracePeriodElapsed: () => {
|
|
1329
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
return this.updateSession(noConnectionSession);
|
|
1333
|
+
}
|
|
1334
|
+
onConnClosed(session) {
|
|
1335
|
+
let noConnectionSession;
|
|
1336
|
+
if (session.state === "Handshaking" /* Handshaking */) {
|
|
1337
|
+
noConnectionSession = SessionStateGraph.transition.HandshakingToNoConnection(session, {
|
|
1338
|
+
onSessionGracePeriodElapsed: () => {
|
|
1339
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1342
|
+
} else {
|
|
1343
|
+
noConnectionSession = SessionStateGraph.transition.ConnectedToNoConnection(session, {
|
|
1344
|
+
onSessionGracePeriodElapsed: () => {
|
|
1345
|
+
this.onSessionGracePeriodElapsed(noConnectionSession);
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
return this.updateSession(noConnectionSession);
|
|
1350
|
+
}
|
|
1248
1351
|
};
|
|
1249
1352
|
|
|
1250
1353
|
// util/stringify.ts
|
|
@@ -1262,10 +1365,6 @@ var ClientTransport = class extends Transport {
|
|
|
1262
1365
|
* The options for this transport.
|
|
1263
1366
|
*/
|
|
1264
1367
|
options;
|
|
1265
|
-
/**
|
|
1266
|
-
* The map of reconnect promises for each client ID.
|
|
1267
|
-
*/
|
|
1268
|
-
inflightConnectionPromises;
|
|
1269
1368
|
retryBudget;
|
|
1270
1369
|
/**
|
|
1271
1370
|
* A flag indicating whether the transport should automatically reconnect
|
|
@@ -1284,352 +1383,278 @@ var ClientTransport = class extends Transport {
|
|
|
1284
1383
|
...defaultClientTransportOptions,
|
|
1285
1384
|
...providedOptions
|
|
1286
1385
|
};
|
|
1287
|
-
this.inflightConnectionPromises = /* @__PURE__ */ new Map();
|
|
1288
1386
|
this.retryBudget = new LeakyBucketRateLimit(this.options);
|
|
1289
1387
|
}
|
|
1290
1388
|
extendHandshake(options) {
|
|
1291
1389
|
this.handshakeExtensions = options;
|
|
1292
1390
|
}
|
|
1293
|
-
|
|
1294
|
-
if (this.getStatus()
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
}, this.options.sessionDisconnectGraceMs);
|
|
1306
|
-
const handshakeHandler = (data) => {
|
|
1307
|
-
const maybeSession = this.receiveHandshakeResponseMessage(data, conn);
|
|
1308
|
-
clearTimeout(handshakeTimeout);
|
|
1309
|
-
if (!maybeSession) {
|
|
1310
|
-
conn.close();
|
|
1311
|
-
return;
|
|
1312
|
-
} else {
|
|
1313
|
-
session = maybeSession;
|
|
1314
|
-
}
|
|
1315
|
-
conn.removeDataListener(handshakeHandler);
|
|
1316
|
-
conn.addDataListener((data2) => {
|
|
1317
|
-
const parsed = this.parseMsg(data2, conn);
|
|
1318
|
-
if (!parsed) {
|
|
1319
|
-
conn.telemetry?.span.setStatus({
|
|
1320
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1321
|
-
message: "message parse failure"
|
|
1322
|
-
});
|
|
1323
|
-
conn.close();
|
|
1324
|
-
return;
|
|
1325
|
-
}
|
|
1326
|
-
this.handleMsg(parsed, conn);
|
|
1391
|
+
tryReconnecting(to) {
|
|
1392
|
+
if (this.reconnectOnConnectionDrop && this.getStatus() === "open") {
|
|
1393
|
+
this.connect(to);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
send(to, msg) {
|
|
1397
|
+
if (this.getStatus() === "closed") {
|
|
1398
|
+
const err = "transport is closed, cant send";
|
|
1399
|
+
this.log?.error(err, {
|
|
1400
|
+
clientId: this.clientId,
|
|
1401
|
+
transportMessage: msg,
|
|
1402
|
+
tags: ["invariant-violation"]
|
|
1327
1403
|
});
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1404
|
+
throw new Error(err);
|
|
1405
|
+
}
|
|
1406
|
+
let session = this.sessions.get(to);
|
|
1407
|
+
if (!session) {
|
|
1408
|
+
session = this.createUnconnectedSession(to);
|
|
1409
|
+
}
|
|
1410
|
+
return session.send(msg);
|
|
1411
|
+
}
|
|
1412
|
+
createUnconnectedSession(to) {
|
|
1413
|
+
const session = SessionStateGraph.entrypoints.NoConnection(
|
|
1414
|
+
to,
|
|
1415
|
+
this.clientId,
|
|
1416
|
+
{
|
|
1417
|
+
onSessionGracePeriodElapsed: () => {
|
|
1418
|
+
this.onSessionGracePeriodElapsed(session);
|
|
1342
1419
|
}
|
|
1343
|
-
|
|
1344
|
-
this.
|
|
1345
|
-
|
|
1346
|
-
|
|
1420
|
+
},
|
|
1421
|
+
this.options,
|
|
1422
|
+
this.log
|
|
1423
|
+
);
|
|
1424
|
+
this.updateSession(session);
|
|
1425
|
+
return session;
|
|
1426
|
+
}
|
|
1427
|
+
// listeners
|
|
1428
|
+
onConnectingFailed(session) {
|
|
1429
|
+
const noConnectionSession = super.onConnectingFailed(session);
|
|
1430
|
+
this.tryReconnecting(noConnectionSession.to);
|
|
1431
|
+
return noConnectionSession;
|
|
1432
|
+
}
|
|
1433
|
+
onConnClosed(session) {
|
|
1434
|
+
const noConnectionSession = super.onConnClosed(session);
|
|
1435
|
+
this.tryReconnecting(noConnectionSession.to);
|
|
1436
|
+
return noConnectionSession;
|
|
1437
|
+
}
|
|
1438
|
+
onConnectionEstablished(session, conn) {
|
|
1439
|
+
const handshakingSession = SessionStateGraph.transition.ConnectingToHandshaking(session, conn, {
|
|
1440
|
+
onConnectionErrored: (err) => {
|
|
1441
|
+
const errStr = coerceErrorString(err);
|
|
1442
|
+
this.log?.error(
|
|
1443
|
+
`connection to ${handshakingSession.to} errored during handshake: ${errStr}`,
|
|
1444
|
+
handshakingSession.loggingMetadata
|
|
1445
|
+
);
|
|
1446
|
+
},
|
|
1447
|
+
onConnectionClosed: () => {
|
|
1448
|
+
this.log?.warn(
|
|
1449
|
+
`connection to ${handshakingSession.to} closed during handshake`,
|
|
1450
|
+
handshakingSession.loggingMetadata
|
|
1451
|
+
);
|
|
1452
|
+
this.onConnClosed(handshakingSession);
|
|
1453
|
+
},
|
|
1454
|
+
onHandshake: (msg) => {
|
|
1455
|
+
this.onHandshakeResponse(handshakingSession, msg);
|
|
1456
|
+
},
|
|
1457
|
+
onInvalidHandshake: (reason) => {
|
|
1458
|
+
this.log?.error(
|
|
1459
|
+
`invalid handshake: ${reason}`,
|
|
1460
|
+
handshakingSession.loggingMetadata
|
|
1461
|
+
);
|
|
1462
|
+
this.deleteSession(session);
|
|
1463
|
+
this.protocolError(ProtocolError.HandshakeFailed, reason);
|
|
1464
|
+
},
|
|
1465
|
+
onHandshakeTimeout: () => {
|
|
1466
|
+
this.log?.error(
|
|
1467
|
+
`connection to ${handshakingSession.to} timed out during handshake`,
|
|
1468
|
+
handshakingSession.loggingMetadata
|
|
1469
|
+
);
|
|
1470
|
+
this.onConnClosed(handshakingSession);
|
|
1347
1471
|
}
|
|
1348
1472
|
});
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
...conn.loggingMetadata,
|
|
1358
|
-
...session?.loggingMetadata,
|
|
1359
|
-
clientId: this.clientId,
|
|
1360
|
-
connectedTo: to
|
|
1361
|
-
}
|
|
1362
|
-
);
|
|
1473
|
+
this.updateSession(handshakingSession);
|
|
1474
|
+
void this.sendHandshake(handshakingSession);
|
|
1475
|
+
return handshakingSession;
|
|
1476
|
+
}
|
|
1477
|
+
rejectHandshakeResponse(session, reason, metadata) {
|
|
1478
|
+
session.conn.telemetry?.span.setStatus({
|
|
1479
|
+
code: import_api3.SpanStatusCode.ERROR,
|
|
1480
|
+
message: reason
|
|
1363
1481
|
});
|
|
1482
|
+
this.log?.warn(reason, metadata);
|
|
1483
|
+
this.deleteSession(session);
|
|
1364
1484
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
});
|
|
1372
|
-
this.protocolError(
|
|
1373
|
-
ProtocolError.HandshakeFailed,
|
|
1374
|
-
"received non-transport message"
|
|
1375
|
-
);
|
|
1376
|
-
return false;
|
|
1377
|
-
}
|
|
1378
|
-
if (!import_value2.Value.Check(ControlMessageHandshakeResponseSchema, parsed.payload)) {
|
|
1379
|
-
conn.telemetry?.span.setStatus({
|
|
1380
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1381
|
-
message: "invalid handshake response"
|
|
1382
|
-
});
|
|
1383
|
-
this.log?.warn(`received invalid handshake resp`, {
|
|
1384
|
-
...conn.loggingMetadata,
|
|
1385
|
-
clientId: this.clientId,
|
|
1386
|
-
connectedTo: parsed.from,
|
|
1387
|
-
transportMessage: parsed,
|
|
1485
|
+
onHandshakeResponse(session, msg) {
|
|
1486
|
+
if (!import_value2.Value.Check(ControlMessageHandshakeResponseSchema, msg.payload)) {
|
|
1487
|
+
const reason = `received invalid handshake response`;
|
|
1488
|
+
this.rejectHandshakeResponse(session, reason, {
|
|
1489
|
+
...session.loggingMetadata,
|
|
1490
|
+
transportMessage: msg,
|
|
1388
1491
|
validationErrors: [
|
|
1389
|
-
...import_value2.Value.Errors(
|
|
1390
|
-
ControlMessageHandshakeResponseSchema,
|
|
1391
|
-
parsed.payload
|
|
1392
|
-
)
|
|
1492
|
+
...import_value2.Value.Errors(ControlMessageHandshakeResponseSchema, msg.payload)
|
|
1393
1493
|
]
|
|
1394
1494
|
});
|
|
1395
|
-
|
|
1396
|
-
ProtocolError.HandshakeFailed,
|
|
1397
|
-
"invalid handshake resp"
|
|
1398
|
-
);
|
|
1399
|
-
return false;
|
|
1495
|
+
return;
|
|
1400
1496
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
});
|
|
1497
|
+
if (!msg.payload.status.ok) {
|
|
1498
|
+
const retriable = msg.payload.status.code ? import_value2.Value.Check(
|
|
1499
|
+
HandshakeErrorRetriableResponseCodes,
|
|
1500
|
+
msg.payload.status.code
|
|
1501
|
+
) : false;
|
|
1502
|
+
const reason = `handshake failed: ${msg.payload.status.reason}`;
|
|
1503
|
+
this.rejectHandshakeResponse(session, reason, {
|
|
1504
|
+
...session.loggingMetadata,
|
|
1505
|
+
transportMessage: msg
|
|
1506
|
+
});
|
|
1507
|
+
if (retriable) {
|
|
1508
|
+
this.tryReconnecting(session.to);
|
|
1414
1509
|
} else {
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
message: "handshake rejected"
|
|
1418
|
-
});
|
|
1510
|
+
this.deleteSession(session);
|
|
1511
|
+
this.protocolError(ProtocolError.HandshakeFailed, reason);
|
|
1419
1512
|
}
|
|
1420
|
-
|
|
1421
|
-
`received handshake rejection: ${parsed.payload.status.reason}`,
|
|
1422
|
-
{
|
|
1423
|
-
...conn.loggingMetadata,
|
|
1424
|
-
clientId: this.clientId,
|
|
1425
|
-
connectedTo: parsed.from,
|
|
1426
|
-
transportMessage: parsed
|
|
1427
|
-
}
|
|
1428
|
-
);
|
|
1429
|
-
this.protocolError(
|
|
1430
|
-
ProtocolError.HandshakeFailed,
|
|
1431
|
-
parsed.payload.status.reason
|
|
1432
|
-
);
|
|
1433
|
-
return false;
|
|
1513
|
+
return;
|
|
1434
1514
|
}
|
|
1435
|
-
if (
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
conn.telemetry?.span.setStatus({
|
|
1441
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1442
|
-
message: "session id mismatch"
|
|
1443
|
-
});
|
|
1444
|
-
this.log?.warn(`handshake from ${parsed.from} session id mismatch`, {
|
|
1445
|
-
...conn.loggingMetadata,
|
|
1446
|
-
clientId: this.clientId,
|
|
1447
|
-
connectedTo: parsed.from,
|
|
1448
|
-
transportMessage: parsed
|
|
1515
|
+
if (msg.payload.status.sessionId !== session.id) {
|
|
1516
|
+
const reason = `session id mismatch: expected ${session.id}, got ${msg.payload.status.sessionId}`;
|
|
1517
|
+
this.rejectHandshakeResponse(session, reason, {
|
|
1518
|
+
...session.loggingMetadata,
|
|
1519
|
+
transportMessage: msg
|
|
1449
1520
|
});
|
|
1450
|
-
|
|
1451
|
-
return false;
|
|
1521
|
+
return;
|
|
1452
1522
|
}
|
|
1453
|
-
this.log?.
|
|
1454
|
-
...
|
|
1455
|
-
|
|
1456
|
-
connectedTo: parsed.from,
|
|
1457
|
-
transportMessage: parsed
|
|
1523
|
+
this.log?.info(`handshake from ${msg.from} ok`, {
|
|
1524
|
+
...session.loggingMetadata,
|
|
1525
|
+
transportMessage: msg
|
|
1458
1526
|
});
|
|
1459
|
-
const
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1527
|
+
const connectedSession = SessionStateGraph.transition.HandshakingToConnected(session, {
|
|
1528
|
+
onConnectionErrored: (err) => {
|
|
1529
|
+
const errStr = coerceErrorString(err);
|
|
1530
|
+
this.log?.warn(
|
|
1531
|
+
`connection to ${connectedSession.to} errored: ${errStr}`,
|
|
1532
|
+
connectedSession.loggingMetadata
|
|
1533
|
+
);
|
|
1534
|
+
},
|
|
1535
|
+
onConnectionClosed: () => {
|
|
1536
|
+
this.log?.info(
|
|
1537
|
+
`connection to ${connectedSession.to} closed`,
|
|
1538
|
+
connectedSession.loggingMetadata
|
|
1539
|
+
);
|
|
1540
|
+
this.onConnClosed(connectedSession);
|
|
1541
|
+
},
|
|
1542
|
+
onMessage: (msg2) => this.handleMsg(msg2),
|
|
1543
|
+
onInvalidMessage: (reason) => {
|
|
1544
|
+
this.deleteSession(connectedSession);
|
|
1545
|
+
this.protocolError(ProtocolError.MessageOrderingViolated, reason);
|
|
1546
|
+
}
|
|
1463
1547
|
});
|
|
1464
|
-
this.
|
|
1465
|
-
this.retryBudget.startRestoringBudget(
|
|
1466
|
-
return session;
|
|
1548
|
+
this.updateSession(connectedSession);
|
|
1549
|
+
this.retryBudget.startRestoringBudget(connectedSession.to);
|
|
1467
1550
|
}
|
|
1468
1551
|
/**
|
|
1469
1552
|
* Manually attempts to connect to a client.
|
|
1470
1553
|
* @param to The client ID of the node to connect to.
|
|
1471
1554
|
*/
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1555
|
+
connect(to) {
|
|
1556
|
+
let session = this.sessions.get(to);
|
|
1557
|
+
session ??= this.createUnconnectedSession(to);
|
|
1558
|
+
if (session.state !== "NoConnection" /* NoConnection */) {
|
|
1559
|
+
this.log?.debug(
|
|
1560
|
+
`session to ${to} has state ${session.state}, skipping connect attempt`,
|
|
1561
|
+
session.loggingMetadata
|
|
1562
|
+
);
|
|
1478
1563
|
return;
|
|
1479
1564
|
}
|
|
1480
|
-
|
|
1481
|
-
if (!canProceedWithConnection()) {
|
|
1565
|
+
if (this.getStatus() !== "open") {
|
|
1482
1566
|
this.log?.info(
|
|
1483
1567
|
`transport state is no longer open, cancelling attempt to connect to ${to}`,
|
|
1484
|
-
|
|
1568
|
+
session.loggingMetadata
|
|
1485
1569
|
);
|
|
1486
1570
|
return;
|
|
1487
1571
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
this.protocolError(ProtocolError.RetriesExceeded, errMsg);
|
|
1495
|
-
return;
|
|
1496
|
-
}
|
|
1497
|
-
let sleep = Promise.resolve();
|
|
1498
|
-
const backoffMs = this.retryBudget.getBackoffMs(to);
|
|
1499
|
-
if (backoffMs > 0) {
|
|
1500
|
-
sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
1501
|
-
}
|
|
1502
|
-
this.log?.info(
|
|
1503
|
-
`attempting connection to ${to} (${backoffMs}ms backoff)`,
|
|
1504
|
-
{
|
|
1505
|
-
clientId: this.clientId,
|
|
1506
|
-
connectedTo: to
|
|
1507
|
-
}
|
|
1508
|
-
);
|
|
1509
|
-
this.retryBudget.consumeBudget(to);
|
|
1510
|
-
reconnectPromise = tracing_default.startActiveSpan("connect", async (span) => {
|
|
1511
|
-
try {
|
|
1512
|
-
span.addEvent("backoff", { backoffMs });
|
|
1513
|
-
await sleep;
|
|
1514
|
-
if (!canProceedWithConnection()) {
|
|
1515
|
-
throw new Error("transport state is no longer open");
|
|
1516
|
-
}
|
|
1517
|
-
span.addEvent("connecting");
|
|
1518
|
-
const conn = await this.createNewOutgoingConnection(to);
|
|
1519
|
-
if (!canProceedWithConnection()) {
|
|
1520
|
-
this.log?.info(
|
|
1521
|
-
`transport state is no longer open, closing pre-handshake connection to ${to}`,
|
|
1522
|
-
{
|
|
1523
|
-
...conn.loggingMetadata,
|
|
1524
|
-
clientId: this.clientId,
|
|
1525
|
-
connectedTo: to
|
|
1526
|
-
}
|
|
1527
|
-
);
|
|
1528
|
-
conn.close();
|
|
1529
|
-
throw new Error("transport state is no longer open");
|
|
1530
|
-
}
|
|
1531
|
-
span.addEvent("sending handshake");
|
|
1532
|
-
const ok = await this.sendHandshake(to, conn);
|
|
1533
|
-
if (!ok) {
|
|
1534
|
-
conn.close();
|
|
1535
|
-
throw new Error("failed to send handshake");
|
|
1536
|
-
}
|
|
1537
|
-
return conn;
|
|
1538
|
-
} catch (err) {
|
|
1539
|
-
const errStr = coerceErrorString(err);
|
|
1540
|
-
span.recordException(errStr);
|
|
1541
|
-
span.setStatus({ code: import_api4.SpanStatusCode.ERROR });
|
|
1542
|
-
throw err;
|
|
1543
|
-
} finally {
|
|
1544
|
-
span.end();
|
|
1545
|
-
}
|
|
1546
|
-
});
|
|
1547
|
-
this.inflightConnectionPromises.set(to, reconnectPromise);
|
|
1548
|
-
} else {
|
|
1549
|
-
this.log?.info(
|
|
1550
|
-
`attempting connection to ${to} (reusing previous attempt)`,
|
|
1551
|
-
{
|
|
1552
|
-
clientId: this.clientId,
|
|
1553
|
-
connectedTo: to
|
|
1554
|
-
}
|
|
1555
|
-
);
|
|
1572
|
+
if (!this.retryBudget.hasBudget(to)) {
|
|
1573
|
+
const budgetConsumed = this.retryBudget.getBudgetConsumed(to);
|
|
1574
|
+
const errMsg = `tried to connect to ${to} but retry budget exceeded (more than ${budgetConsumed} attempts in the last ${this.retryBudget.totalBudgetRestoreTime}ms)`;
|
|
1575
|
+
this.log?.error(errMsg, session.loggingMetadata);
|
|
1576
|
+
this.protocolError(ProtocolError.RetriesExceeded, errMsg);
|
|
1577
|
+
return;
|
|
1556
1578
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
const errStr = coerceErrorString(error);
|
|
1562
|
-
if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {
|
|
1563
|
-
this.log?.warn(`connection to ${to} failed (${errStr})`, {
|
|
1564
|
-
clientId: this.clientId,
|
|
1565
|
-
connectedTo: to
|
|
1566
|
-
});
|
|
1567
|
-
} else {
|
|
1568
|
-
this.log?.warn(`connection to ${to} failed (${errStr}), retrying`, {
|
|
1569
|
-
clientId: this.clientId,
|
|
1570
|
-
connectedTo: to
|
|
1571
|
-
});
|
|
1572
|
-
await this.connect(to);
|
|
1573
|
-
}
|
|
1579
|
+
let sleep = Promise.resolve();
|
|
1580
|
+
const backoffMs = this.retryBudget.getBackoffMs(to);
|
|
1581
|
+
if (backoffMs > 0) {
|
|
1582
|
+
sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
1574
1583
|
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1584
|
+
this.log?.info(
|
|
1585
|
+
`attempting connection to ${to} (${backoffMs}ms backoff)`,
|
|
1586
|
+
session.loggingMetadata
|
|
1587
|
+
);
|
|
1588
|
+
this.retryBudget.consumeBudget(to);
|
|
1589
|
+
const reconnectPromise = tracing_default.startActiveSpan("connect", async (span) => {
|
|
1590
|
+
try {
|
|
1591
|
+
span.addEvent("backoff", { backoffMs });
|
|
1592
|
+
await sleep;
|
|
1593
|
+
if (this.getStatus() !== "open") {
|
|
1594
|
+
throw new Error("transport state is no longer open");
|
|
1595
|
+
}
|
|
1596
|
+
span.addEvent("connecting");
|
|
1597
|
+
return await this.createNewOutgoingConnection(to);
|
|
1598
|
+
} catch (err) {
|
|
1599
|
+
const errStr = coerceErrorString(err);
|
|
1600
|
+
span.recordException(errStr);
|
|
1601
|
+
span.setStatus({ code: import_api3.SpanStatusCode.ERROR });
|
|
1602
|
+
throw err;
|
|
1603
|
+
} finally {
|
|
1604
|
+
span.end();
|
|
1605
|
+
}
|
|
1586
1606
|
});
|
|
1607
|
+
const connectingSession = SessionStateGraph.transition.NoConnectionToConnecting(
|
|
1608
|
+
session,
|
|
1609
|
+
reconnectPromise,
|
|
1610
|
+
{
|
|
1611
|
+
onConnectionEstablished: (conn) => {
|
|
1612
|
+
this.log?.debug(
|
|
1613
|
+
`connection to ${connectingSession.to} established`,
|
|
1614
|
+
connectingSession.loggingMetadata
|
|
1615
|
+
);
|
|
1616
|
+
this.onConnectionEstablished(connectingSession, conn);
|
|
1617
|
+
},
|
|
1618
|
+
onConnectionFailed: (error) => {
|
|
1619
|
+
const errStr = coerceErrorString(error);
|
|
1620
|
+
this.log?.error(
|
|
1621
|
+
`error connecting to ${connectingSession.to}: ${errStr}`,
|
|
1622
|
+
connectingSession.loggingMetadata
|
|
1623
|
+
);
|
|
1624
|
+
this.onConnectingFailed(connectingSession);
|
|
1625
|
+
},
|
|
1626
|
+
onConnectionTimeout: () => {
|
|
1627
|
+
this.log?.error(
|
|
1628
|
+
`connection to ${connectingSession.to} timed out`,
|
|
1629
|
+
connectingSession.loggingMetadata
|
|
1630
|
+
);
|
|
1631
|
+
this.onConnectingFailed(connectingSession);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
);
|
|
1635
|
+
this.updateSession(connectingSession);
|
|
1587
1636
|
}
|
|
1588
|
-
async sendHandshake(
|
|
1637
|
+
async sendHandshake(session) {
|
|
1589
1638
|
let metadata = void 0;
|
|
1590
1639
|
if (this.handshakeExtensions) {
|
|
1591
1640
|
metadata = await this.handshakeExtensions.construct();
|
|
1592
|
-
if (!import_value2.Value.Check(this.handshakeExtensions.schema, metadata)) {
|
|
1593
|
-
this.log?.error(`constructed handshake metadata did not match schema`, {
|
|
1594
|
-
...conn.loggingMetadata,
|
|
1595
|
-
clientId: this.clientId,
|
|
1596
|
-
connectedTo: to,
|
|
1597
|
-
validationErrors: [
|
|
1598
|
-
...import_value2.Value.Errors(this.handshakeExtensions.schema, metadata)
|
|
1599
|
-
],
|
|
1600
|
-
tags: ["invariant-violation"]
|
|
1601
|
-
});
|
|
1602
|
-
this.protocolError(
|
|
1603
|
-
ProtocolError.HandshakeFailed,
|
|
1604
|
-
"handshake metadata did not match schema"
|
|
1605
|
-
);
|
|
1606
|
-
conn.telemetry?.span.setStatus({
|
|
1607
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1608
|
-
message: "handshake meta mismatch"
|
|
1609
|
-
});
|
|
1610
|
-
return false;
|
|
1611
|
-
}
|
|
1612
1641
|
}
|
|
1613
|
-
const { session } = this.getOrCreateSession({ to, handshakingConn: conn });
|
|
1614
1642
|
const requestMsg = handshakeRequestMessage({
|
|
1615
1643
|
from: this.clientId,
|
|
1616
|
-
to,
|
|
1644
|
+
to: session.to,
|
|
1617
1645
|
sessionId: session.id,
|
|
1618
1646
|
expectedSessionState: {
|
|
1619
|
-
|
|
1620
|
-
|
|
1647
|
+
nextExpectedSeq: session.ack,
|
|
1648
|
+
nextSentSeq: session.nextSeq()
|
|
1621
1649
|
},
|
|
1622
1650
|
metadata,
|
|
1623
1651
|
tracing: getPropagationContext(session.telemetry.ctx)
|
|
1624
1652
|
});
|
|
1625
|
-
this.log?.debug(`sending handshake request to ${to}`, {
|
|
1626
|
-
...
|
|
1627
|
-
clientId: this.clientId,
|
|
1628
|
-
connectedTo: to,
|
|
1653
|
+
this.log?.debug(`sending handshake request to ${session.to}`, {
|
|
1654
|
+
...session.loggingMetadata,
|
|
1629
1655
|
transportMessage: requestMsg
|
|
1630
1656
|
});
|
|
1631
|
-
|
|
1632
|
-
return true;
|
|
1657
|
+
session.sendHandshake(requestMsg);
|
|
1633
1658
|
}
|
|
1634
1659
|
close() {
|
|
1635
1660
|
this.retryBudget.close();
|
|
@@ -1645,10 +1670,6 @@ var UnixDomainSocketClientTransport = class extends ClientTransport {
|
|
|
1645
1670
|
this.path = socketPath;
|
|
1646
1671
|
}
|
|
1647
1672
|
async createNewOutgoingConnection(to) {
|
|
1648
|
-
const oldConnection = this.connections.get(to);
|
|
1649
|
-
if (oldConnection) {
|
|
1650
|
-
oldConnection.close();
|
|
1651
|
-
}
|
|
1652
1673
|
this.log?.info(`establishing a new uds to ${to}`, {
|
|
1653
1674
|
clientId: this.clientId,
|
|
1654
1675
|
connectedTo: to
|
|
@@ -1659,9 +1680,7 @@ var UnixDomainSocketClientTransport = class extends ClientTransport {
|
|
|
1659
1680
|
sock2.on("error", (err) => reject(err));
|
|
1660
1681
|
sock2.connect(this.path);
|
|
1661
1682
|
});
|
|
1662
|
-
|
|
1663
|
-
this.handleConnection(conn, to);
|
|
1664
|
-
return conn;
|
|
1683
|
+
return new UdsConnection(sock);
|
|
1665
1684
|
}
|
|
1666
1685
|
};
|
|
1667
1686
|
// Annotate the CommonJS export names for ESM import in node:
|