@replit/river 0.23.18 → 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 +17 -16
- package/dist/{chunk-AVL32IMG.js → chunk-AASMR3CQ.js} +20 -16
- 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-EV5HW4IC.js → chunk-KYYB4DUR.js} +65 -53
- 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-R2HAS3GM.js → chunk-PJGGC3LV.js} +55 -41
- package/dist/chunk-PJGGC3LV.js.map +1 -0
- package/dist/{chunk-7MJYOL32.js → chunk-RXJLI2OP.js} +15 -23
- 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-5776a6bb.d.ts → client-ba0d3315.d.ts} +12 -15
- package/dist/{connection-bd35d442.d.ts → connection-c3a96d09.d.ts} +1 -5
- package/dist/connection-d33e3246.d.ts +11 -0
- package/dist/{handshake-a947c234.d.ts → handshake-cdead82a.d.ts} +148 -183
- 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 +104 -63
- package/dist/router/index.cjs.map +1 -1
- package/dist/router/index.d.cts +11 -10
- package/dist/router/index.d.ts +11 -10
- package/dist/router/index.js +2 -2
- package/dist/server-2ef5e6ec.d.ts +42 -0
- package/dist/{services-38b3f758.d.ts → services-e1417b33.d.ts} +3 -3
- package/dist/transport/impls/uds/client.cjs +1246 -1230
- package/dist/transport/impls/uds/client.cjs.map +1 -1
- package/dist/transport/impls/uds/client.d.cts +4 -4
- package/dist/transport/impls/uds/client.d.ts +4 -4
- package/dist/transport/impls/uds/client.js +7 -13
- package/dist/transport/impls/uds/client.js.map +1 -1
- package/dist/transport/impls/uds/server.cjs +1298 -1151
- package/dist/transport/impls/uds/server.cjs.map +1 -1
- package/dist/transport/impls/uds/server.d.cts +4 -4
- package/dist/transport/impls/uds/server.d.ts +4 -4
- package/dist/transport/impls/uds/server.js +6 -6
- package/dist/transport/impls/ws/client.cjs +976 -965
- package/dist/transport/impls/ws/client.cjs.map +1 -1
- package/dist/transport/impls/ws/client.d.cts +4 -4
- package/dist/transport/impls/ws/client.d.ts +4 -4
- package/dist/transport/impls/ws/client.js +6 -7
- package/dist/transport/impls/ws/client.js.map +1 -1
- package/dist/transport/impls/ws/server.cjs +1182 -1047
- package/dist/transport/impls/ws/server.cjs.map +1 -1
- package/dist/transport/impls/ws/server.d.cts +4 -4
- package/dist/transport/impls/ws/server.d.ts +4 -4
- package/dist/transport/impls/ws/server.js +6 -6
- package/dist/transport/index.cjs +1433 -1360
- package/dist/transport/index.cjs.map +1 -1
- package/dist/transport/index.d.cts +4 -4
- package/dist/transport/index.d.ts +4 -4
- package/dist/transport/index.js +9 -9
- package/dist/util/testHelpers.cjs +743 -310
- package/dist/util/testHelpers.cjs.map +1 -1
- package/dist/util/testHelpers.d.cts +9 -6
- package/dist/util/testHelpers.d.ts +9 -6
- package/dist/util/testHelpers.js +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-7MJYOL32.js.map +0 -1
- package/dist/chunk-AVL32IMG.js.map +0 -1
- package/dist/chunk-DPKOJQWF.js +0 -476
- package/dist/chunk-DPKOJQWF.js.map +0 -1
- package/dist/chunk-EV5HW4IC.js.map +0 -1
- package/dist/chunk-J6N6H2WU.js +0 -476
- package/dist/chunk-J6N6H2WU.js.map +0 -1
- package/dist/chunk-MW5JXLHY.js +0 -348
- package/dist/chunk-MW5JXLHY.js.map +0 -1
- package/dist/chunk-R2HAS3GM.js.map +0 -1
- package/dist/chunk-RJOWZIWB.js +0 -335
- package/dist/chunk-RJOWZIWB.js.map +0 -1
- package/dist/connection-df85db7e.d.ts +0 -17
- package/dist/server-53cd5b7e.d.ts +0 -24
|
@@ -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,521 +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 || this.sock.closed) {
|
|
581
|
-
return false;
|
|
582
|
-
}
|
|
583
|
-
this.sock.write(MessageFramer.write(payload));
|
|
584
|
-
return true;
|
|
585
|
-
}
|
|
586
|
-
close() {
|
|
587
|
-
this.sock.destroy();
|
|
588
|
-
this.framer.destroy();
|
|
589
|
-
}
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
// transport/client.ts
|
|
593
|
-
var import_api4 = require("@opentelemetry/api");
|
|
594
|
-
|
|
595
|
-
// codec/json.ts
|
|
596
|
-
var encoder = new TextEncoder();
|
|
597
|
-
var decoder = new TextDecoder();
|
|
598
|
-
function uint8ArrayToBase64(uint8Array) {
|
|
599
|
-
let binary = "";
|
|
600
|
-
uint8Array.forEach((byte) => {
|
|
601
|
-
binary += String.fromCharCode(byte);
|
|
602
|
-
});
|
|
603
|
-
return btoa(binary);
|
|
604
|
-
}
|
|
605
|
-
function base64ToUint8Array(base64) {
|
|
606
|
-
const binaryString = atob(base64);
|
|
607
|
-
const uint8Array = new Uint8Array(binaryString.length);
|
|
608
|
-
for (let i = 0; i < binaryString.length; i++) {
|
|
609
|
-
uint8Array[i] = binaryString.charCodeAt(i);
|
|
610
|
-
}
|
|
611
|
-
return uint8Array;
|
|
612
|
-
}
|
|
613
|
-
var NaiveJsonCodec = {
|
|
614
|
-
toBuffer: (obj) => {
|
|
615
|
-
return encoder.encode(
|
|
616
|
-
JSON.stringify(obj, function replacer(key) {
|
|
617
|
-
const val = this[key];
|
|
618
|
-
if (val instanceof Uint8Array) {
|
|
619
|
-
return { $t: uint8ArrayToBase64(val) };
|
|
620
|
-
} else {
|
|
621
|
-
return val;
|
|
622
|
-
}
|
|
623
|
-
})
|
|
624
|
-
);
|
|
625
|
-
},
|
|
626
|
-
fromBuffer: (buff) => {
|
|
627
|
-
try {
|
|
628
|
-
const parsed = JSON.parse(
|
|
629
|
-
decoder.decode(buff),
|
|
630
|
-
function reviver(_key, val) {
|
|
631
|
-
if (val?.$t) {
|
|
632
|
-
return base64ToUint8Array(val.$t);
|
|
633
|
-
} else {
|
|
634
|
-
return val;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
);
|
|
638
|
-
if (typeof parsed === "object")
|
|
639
|
-
return parsed;
|
|
640
|
-
return null;
|
|
641
|
-
} catch {
|
|
642
|
-
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;
|
|
643
375
|
}
|
|
644
376
|
}
|
|
645
377
|
};
|
|
@@ -649,7 +381,8 @@ var defaultTransportOptions = {
|
|
|
649
381
|
heartbeatIntervalMs: 1e3,
|
|
650
382
|
heartbeatsUntilDead: 2,
|
|
651
383
|
sessionDisconnectGraceMs: 5e3,
|
|
652
|
-
|
|
384
|
+
connectionTimeoutMs: 2e3,
|
|
385
|
+
handshakeTimeoutMs: 1e3,
|
|
653
386
|
codec: NaiveJsonCodec
|
|
654
387
|
};
|
|
655
388
|
var defaultConnectionRetryOptions = {
|
|
@@ -738,9 +471,6 @@ var LeakyBucketRateLimit = class {
|
|
|
738
471
|
}
|
|
739
472
|
};
|
|
740
473
|
|
|
741
|
-
// transport/transport.ts
|
|
742
|
-
var import_value = require("@sinclair/typebox/value");
|
|
743
|
-
|
|
744
474
|
// logging/log.ts
|
|
745
475
|
var LoggingLevels = {
|
|
746
476
|
debug: -1,
|
|
@@ -766,98 +496,719 @@ var BaseLogger = class {
|
|
|
766
496
|
this.minLevel = minLevel;
|
|
767
497
|
this.output = output;
|
|
768
498
|
}
|
|
769
|
-
debug(msg, metadata) {
|
|
770
|
-
if (LoggingLevels[this.minLevel] <= LoggingLevels.debug) {
|
|
771
|
-
this.output(msg, metadata ?? {}, "debug");
|
|
772
|
-
}
|
|
499
|
+
debug(msg, metadata) {
|
|
500
|
+
if (LoggingLevels[this.minLevel] <= LoggingLevels.debug) {
|
|
501
|
+
this.output(msg, metadata ?? {}, "debug");
|
|
502
|
+
}
|
|
503
|
+
}
|
|
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));
|
|
773
843
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
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;
|
|
778
850
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
this.output(msg, metadata ?? {}, "warn");
|
|
782
|
-
}
|
|
851
|
+
_handleClose() {
|
|
852
|
+
this.conn.close();
|
|
783
853
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
854
|
+
};
|
|
855
|
+
|
|
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;
|
|
787
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();
|
|
788
894
|
}
|
|
789
895
|
};
|
|
790
|
-
var createLogProxy = (log) => ({
|
|
791
|
-
debug: cleanedLogFn(log.debug.bind(log)),
|
|
792
|
-
info: cleanedLogFn(log.info.bind(log)),
|
|
793
|
-
warn: cleanedLogFn(log.warn.bind(log)),
|
|
794
|
-
error: cleanedLogFn(log.error.bind(log))
|
|
795
|
-
});
|
|
796
896
|
|
|
797
|
-
// transport/
|
|
798
|
-
var
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
this.
|
|
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;
|
|
807
907
|
}
|
|
808
|
-
|
|
809
|
-
|
|
908
|
+
updateBookkeeping(ack, seq) {
|
|
909
|
+
this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
|
|
910
|
+
this.ack = seq + 1;
|
|
911
|
+
this.heartbeatMisses = 0;
|
|
810
912
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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
|
+
);
|
|
814
931
|
}
|
|
815
|
-
this.
|
|
816
|
-
|
|
817
|
-
removeEventListener(eventType, handler) {
|
|
818
|
-
const handlers = this.eventListeners[eventType];
|
|
819
|
-
if (handlers) {
|
|
820
|
-
this.eventListeners[eventType]?.delete(handler);
|
|
932
|
+
for (const msg of this.sendBuffer) {
|
|
933
|
+
conn.send(this.options.codec.toBuffer(msg));
|
|
821
934
|
}
|
|
822
935
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
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);
|
|
829
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;
|
|
830
1198
|
}
|
|
831
1199
|
}
|
|
832
1200
|
};
|
|
833
1201
|
|
|
834
1202
|
// transport/transport.ts
|
|
835
|
-
var import_api3 = require("@opentelemetry/api");
|
|
836
1203
|
var Transport = class {
|
|
837
1204
|
/**
|
|
838
1205
|
* The status of the transport.
|
|
839
1206
|
*/
|
|
840
1207
|
status;
|
|
841
|
-
/**
|
|
842
|
-
* The {@link Codec} used to encode and decode messages.
|
|
843
|
-
*/
|
|
844
|
-
codec;
|
|
845
1208
|
/**
|
|
846
1209
|
* The client ID of this transport.
|
|
847
1210
|
*/
|
|
848
1211
|
clientId;
|
|
849
|
-
/**
|
|
850
|
-
* The map of {@link Session}s managed by this transport.
|
|
851
|
-
*/
|
|
852
|
-
sessions;
|
|
853
|
-
/**
|
|
854
|
-
* The map of {@link Connection}s managed by this transport.
|
|
855
|
-
*/
|
|
856
|
-
get connections() {
|
|
857
|
-
return new Map(
|
|
858
|
-
[...this.sessions].map(([client, session]) => [client, session.connection]).filter((entry) => entry[1] !== void 0)
|
|
859
|
-
);
|
|
860
|
-
}
|
|
861
1212
|
/**
|
|
862
1213
|
* The event dispatcher for handling events of type EventTypes.
|
|
863
1214
|
*/
|
|
@@ -867,320 +1218,35 @@ var Transport = class {
|
|
|
867
1218
|
*/
|
|
868
1219
|
options;
|
|
869
1220
|
log;
|
|
1221
|
+
sessions;
|
|
870
1222
|
/**
|
|
871
|
-
* Creates a new Transport instance.
|
|
872
|
-
*
|
|
873
|
-
* @param
|
|
874
|
-
* @param clientId The client ID of this transport.
|
|
875
|
-
*/
|
|
876
|
-
constructor(clientId, providedOptions) {
|
|
877
|
-
this.options = { ...defaultTransportOptions, ...providedOptions };
|
|
878
|
-
this.eventDispatcher = new EventDispatcher();
|
|
879
|
-
this.sessions = /* @__PURE__ */ new Map();
|
|
880
|
-
this.codec = this.options.codec;
|
|
881
|
-
this.clientId = clientId;
|
|
882
|
-
this.status = "open";
|
|
883
|
-
}
|
|
884
|
-
bindLogger(fn, level) {
|
|
885
|
-
if (typeof fn === "function") {
|
|
886
|
-
this.log = createLogProxy(new BaseLogger(fn, level));
|
|
887
|
-
return;
|
|
888
|
-
}
|
|
889
|
-
this.log = createLogProxy(fn);
|
|
890
|
-
}
|
|
891
|
-
/**
|
|
892
|
-
* Called when a new connection is established
|
|
893
|
-
* and we know the identity of the connected client.
|
|
894
|
-
* @param conn The connection object.
|
|
895
|
-
*/
|
|
896
|
-
onConnect(conn, session, isTransparentReconnect) {
|
|
897
|
-
this.eventDispatcher.dispatchEvent("connectionStatus", {
|
|
898
|
-
status: "connect",
|
|
899
|
-
conn
|
|
900
|
-
});
|
|
901
|
-
conn.telemetry = createConnectionTelemetryInfo(conn, session.telemetry);
|
|
902
|
-
session.replaceWithNewConnection(conn, isTransparentReconnect);
|
|
903
|
-
this.log?.info(`connected to ${session.to}`, {
|
|
904
|
-
...conn.loggingMetadata,
|
|
905
|
-
...session.loggingMetadata
|
|
906
|
-
});
|
|
907
|
-
}
|
|
908
|
-
createSession(to, conn, propagationCtx) {
|
|
909
|
-
const session = new Session(
|
|
910
|
-
conn,
|
|
911
|
-
this.clientId,
|
|
912
|
-
to,
|
|
913
|
-
this.options,
|
|
914
|
-
propagationCtx
|
|
915
|
-
);
|
|
916
|
-
if (this.log) {
|
|
917
|
-
session.bindLogger(this.log);
|
|
918
|
-
}
|
|
919
|
-
const currentSession = this.sessions.get(session.to);
|
|
920
|
-
if (currentSession) {
|
|
921
|
-
this.log?.warn(
|
|
922
|
-
`session ${session.id} from ${session.to} surreptitiously replacing ${currentSession.id}`,
|
|
923
|
-
{
|
|
924
|
-
...currentSession.loggingMetadata,
|
|
925
|
-
tags: ["invariant-violation"]
|
|
926
|
-
}
|
|
927
|
-
);
|
|
928
|
-
this.deleteSession({
|
|
929
|
-
session: currentSession,
|
|
930
|
-
closeHandshakingConnection: false
|
|
931
|
-
});
|
|
932
|
-
}
|
|
933
|
-
this.sessions.set(session.to, session);
|
|
934
|
-
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
935
|
-
status: "connect",
|
|
936
|
-
session
|
|
937
|
-
});
|
|
938
|
-
return session;
|
|
939
|
-
}
|
|
940
|
-
createNewSession({
|
|
941
|
-
to,
|
|
942
|
-
conn,
|
|
943
|
-
sessionId,
|
|
944
|
-
propagationCtx
|
|
945
|
-
}) {
|
|
946
|
-
let session = this.sessions.get(to);
|
|
947
|
-
if (session !== void 0) {
|
|
948
|
-
this.log?.info(
|
|
949
|
-
`session for ${to} already exists, replacing it with a new session as requested`,
|
|
950
|
-
session.loggingMetadata
|
|
951
|
-
);
|
|
952
|
-
this.deleteSession({
|
|
953
|
-
session,
|
|
954
|
-
closeHandshakingConnection: false
|
|
955
|
-
});
|
|
956
|
-
session = void 0;
|
|
957
|
-
}
|
|
958
|
-
session = this.createSession(to, conn, propagationCtx);
|
|
959
|
-
session.advertisedSessionId = sessionId;
|
|
960
|
-
this.log?.info(`created new session for ${to}`, session.loggingMetadata);
|
|
961
|
-
return session;
|
|
962
|
-
}
|
|
963
|
-
getExistingSession({
|
|
964
|
-
to,
|
|
965
|
-
sessionId,
|
|
966
|
-
nextExpectedSeq
|
|
967
|
-
}) {
|
|
968
|
-
const session = this.sessions.get(to);
|
|
969
|
-
if (
|
|
970
|
-
// reject this request if there was no previous session to replace
|
|
971
|
-
session === void 0 || // or if both parties do not agree about the next expected sequence number
|
|
972
|
-
!session.nextExpectedSeqInRange(nextExpectedSeq) || // or if both parties do not agree on the advertised session id
|
|
973
|
-
session.advertisedSessionId !== sessionId
|
|
974
|
-
) {
|
|
975
|
-
return false;
|
|
976
|
-
}
|
|
977
|
-
this.log?.info(
|
|
978
|
-
`reused existing session for ${to}`,
|
|
979
|
-
session.loggingMetadata
|
|
980
|
-
);
|
|
981
|
-
return session;
|
|
982
|
-
}
|
|
983
|
-
getOrCreateSession({
|
|
984
|
-
to,
|
|
985
|
-
conn,
|
|
986
|
-
handshakingConn,
|
|
987
|
-
sessionId,
|
|
988
|
-
propagationCtx
|
|
989
|
-
}) {
|
|
990
|
-
let session = this.sessions.get(to);
|
|
991
|
-
const isReconnect = session !== void 0;
|
|
992
|
-
let isTransparentReconnect = isReconnect;
|
|
993
|
-
if (session?.advertisedSessionId !== void 0 && sessionId !== void 0 && session.advertisedSessionId !== sessionId) {
|
|
994
|
-
this.log?.info(
|
|
995
|
-
`session for ${to} already exists but has a different session id (expected: ${session.advertisedSessionId}, got: ${sessionId}), creating a new one`,
|
|
996
|
-
session.loggingMetadata
|
|
997
|
-
);
|
|
998
|
-
this.deleteSession({
|
|
999
|
-
session,
|
|
1000
|
-
closeHandshakingConnection: handshakingConn !== void 0,
|
|
1001
|
-
handshakingConn
|
|
1002
|
-
});
|
|
1003
|
-
isTransparentReconnect = false;
|
|
1004
|
-
session = void 0;
|
|
1005
|
-
}
|
|
1006
|
-
if (!session) {
|
|
1007
|
-
session = this.createSession(to, conn, propagationCtx);
|
|
1008
|
-
this.log?.info(
|
|
1009
|
-
`no session for ${to}, created a new one`,
|
|
1010
|
-
session.loggingMetadata
|
|
1011
|
-
);
|
|
1012
|
-
}
|
|
1013
|
-
if (sessionId !== void 0) {
|
|
1014
|
-
session.advertisedSessionId = sessionId;
|
|
1015
|
-
}
|
|
1016
|
-
if (handshakingConn !== void 0) {
|
|
1017
|
-
session.replaceWithNewHandshakingConnection(handshakingConn);
|
|
1018
|
-
}
|
|
1019
|
-
return { session, isReconnect, isTransparentReconnect };
|
|
1020
|
-
}
|
|
1021
|
-
deleteSession({
|
|
1022
|
-
session,
|
|
1023
|
-
closeHandshakingConnection,
|
|
1024
|
-
handshakingConn
|
|
1025
|
-
}) {
|
|
1026
|
-
if (closeHandshakingConnection) {
|
|
1027
|
-
session.closeHandshakingConnection(handshakingConn);
|
|
1028
|
-
}
|
|
1029
|
-
session.close();
|
|
1030
|
-
session.telemetry.span.end();
|
|
1031
|
-
const currentSession = this.sessions.get(session.to);
|
|
1032
|
-
if (currentSession && currentSession.id !== session.id) {
|
|
1033
|
-
this.log?.warn(
|
|
1034
|
-
`session ${session.id} disconnect from ${session.to}, mismatch with ${currentSession.id}`,
|
|
1035
|
-
{
|
|
1036
|
-
...session.loggingMetadata,
|
|
1037
|
-
tags: ["invariant-violation"]
|
|
1038
|
-
}
|
|
1039
|
-
);
|
|
1040
|
-
return;
|
|
1041
|
-
}
|
|
1042
|
-
this.sessions.delete(session.to);
|
|
1043
|
-
this.log?.info(
|
|
1044
|
-
`session ${session.id} disconnect from ${session.to}`,
|
|
1045
|
-
session.loggingMetadata
|
|
1046
|
-
);
|
|
1047
|
-
this.eventDispatcher.dispatchEvent("sessionStatus", {
|
|
1048
|
-
status: "disconnect",
|
|
1049
|
-
session
|
|
1050
|
-
});
|
|
1051
|
-
}
|
|
1052
|
-
/**
|
|
1053
|
-
* The downstream implementation needs to call this when a connection is closed.
|
|
1054
|
-
* @param conn The connection object.
|
|
1055
|
-
* @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.
|
|
1056
1226
|
*/
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
...conn.loggingMetadata,
|
|
1064
|
-
tags: ["invariant-violation"]
|
|
1065
|
-
});
|
|
1066
|
-
return;
|
|
1067
|
-
}
|
|
1068
|
-
conn.telemetry?.span.end();
|
|
1069
|
-
this.eventDispatcher.dispatchEvent("connectionStatus", {
|
|
1070
|
-
status: "disconnect",
|
|
1071
|
-
conn
|
|
1072
|
-
});
|
|
1073
|
-
session.connection = void 0;
|
|
1074
|
-
session.beginGrace(() => {
|
|
1075
|
-
if (session.connection !== void 0) {
|
|
1076
|
-
session.telemetry.span.addEvent("session grace period race");
|
|
1077
|
-
this.log?.warn("session grace period race", {
|
|
1078
|
-
clientId: this.clientId,
|
|
1079
|
-
...session.loggingMetadata,
|
|
1080
|
-
...conn.loggingMetadata,
|
|
1081
|
-
tags: ["invariant-violation"]
|
|
1082
|
-
});
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
session.telemetry.span.addEvent("session grace period expired");
|
|
1086
|
-
this.deleteSession({
|
|
1087
|
-
session,
|
|
1088
|
-
closeHandshakingConnection: true,
|
|
1089
|
-
handshakingConn: conn
|
|
1090
|
-
});
|
|
1091
|
-
});
|
|
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();
|
|
1092
1233
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
*/
|
|
1098
|
-
parseMsg(msg, conn) {
|
|
1099
|
-
const parsedMsg = this.codec.fromBuffer(msg);
|
|
1100
|
-
if (parsedMsg === null) {
|
|
1101
|
-
const decodedBuffer = new TextDecoder().decode(Buffer.from(msg));
|
|
1102
|
-
this.log?.error(
|
|
1103
|
-
`received malformed msg, killing conn: ${decodedBuffer}`,
|
|
1104
|
-
{
|
|
1105
|
-
clientId: this.clientId,
|
|
1106
|
-
...conn.loggingMetadata
|
|
1107
|
-
}
|
|
1108
|
-
);
|
|
1109
|
-
return null;
|
|
1110
|
-
}
|
|
1111
|
-
if (!import_value.Value.Check(OpaqueTransportMessageSchema, parsedMsg)) {
|
|
1112
|
-
this.log?.error(`received invalid msg: ${JSON.stringify(parsedMsg)}`, {
|
|
1113
|
-
clientId: this.clientId,
|
|
1114
|
-
...conn.loggingMetadata,
|
|
1115
|
-
validationErrors: [
|
|
1116
|
-
...import_value.Value.Errors(OpaqueTransportMessageSchema, parsedMsg)
|
|
1117
|
-
]
|
|
1118
|
-
});
|
|
1119
|
-
return null;
|
|
1234
|
+
bindLogger(fn, level) {
|
|
1235
|
+
if (typeof fn === "function") {
|
|
1236
|
+
this.log = createLogProxy(new BaseLogger(fn, level));
|
|
1237
|
+
return;
|
|
1120
1238
|
}
|
|
1121
|
-
|
|
1239
|
+
this.log = createLogProxy(fn);
|
|
1122
1240
|
}
|
|
1123
1241
|
/**
|
|
1124
1242
|
* Called when a message is received by this transport.
|
|
1125
1243
|
* You generally shouldn't need to override this in downstream transport implementations.
|
|
1126
1244
|
* @param msg The received message.
|
|
1127
1245
|
*/
|
|
1128
|
-
handleMsg(msg
|
|
1246
|
+
handleMsg(msg) {
|
|
1129
1247
|
if (this.getStatus() !== "open")
|
|
1130
1248
|
return;
|
|
1131
|
-
|
|
1132
|
-
if (!session) {
|
|
1133
|
-
this.log?.error(`received message for unknown session from ${msg.from}`, {
|
|
1134
|
-
clientId: this.clientId,
|
|
1135
|
-
transportMessage: msg,
|
|
1136
|
-
...conn.loggingMetadata,
|
|
1137
|
-
tags: ["invariant-violation"]
|
|
1138
|
-
});
|
|
1139
|
-
return;
|
|
1140
|
-
}
|
|
1141
|
-
session.cancelGrace();
|
|
1142
|
-
this.log?.debug(`received msg`, {
|
|
1143
|
-
clientId: this.clientId,
|
|
1144
|
-
transportMessage: msg,
|
|
1145
|
-
...conn.loggingMetadata
|
|
1146
|
-
});
|
|
1147
|
-
if (msg.seq !== session.nextExpectedSeq) {
|
|
1148
|
-
if (msg.seq < session.nextExpectedSeq) {
|
|
1149
|
-
this.log?.debug(
|
|
1150
|
-
`received duplicate msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq}), discarding`,
|
|
1151
|
-
{
|
|
1152
|
-
clientId: this.clientId,
|
|
1153
|
-
transportMessage: msg,
|
|
1154
|
-
...conn.loggingMetadata
|
|
1155
|
-
}
|
|
1156
|
-
);
|
|
1157
|
-
} else {
|
|
1158
|
-
const errMsg = `received out-of-order msg (got seq: ${msg.seq}, wanted seq: ${session.nextExpectedSeq})`;
|
|
1159
|
-
this.log?.error(`${errMsg}, marking connection as dead`, {
|
|
1160
|
-
clientId: this.clientId,
|
|
1161
|
-
transportMessage: msg,
|
|
1162
|
-
...conn.loggingMetadata,
|
|
1163
|
-
tags: ["invariant-violation"]
|
|
1164
|
-
});
|
|
1165
|
-
this.protocolError(ProtocolError.MessageOrderingViolated, errMsg);
|
|
1166
|
-
session.telemetry.span.setStatus({
|
|
1167
|
-
code: import_api3.SpanStatusCode.ERROR,
|
|
1168
|
-
message: "message order violated"
|
|
1169
|
-
});
|
|
1170
|
-
this.deleteSession({ session, closeHandshakingConnection: true });
|
|
1171
|
-
}
|
|
1172
|
-
return;
|
|
1173
|
-
}
|
|
1174
|
-
session.updateBookkeeping(msg.ack, msg.seq);
|
|
1175
|
-
if (!isAck(msg.controlFlags)) {
|
|
1176
|
-
this.eventDispatcher.dispatchEvent("message", msg);
|
|
1177
|
-
} else {
|
|
1178
|
-
this.log?.debug(`discarding msg (ack bit set)`, {
|
|
1179
|
-
clientId: this.clientId,
|
|
1180
|
-
transportMessage: msg,
|
|
1181
|
-
...conn.loggingMetadata
|
|
1182
|
-
});
|
|
1183
|
-
}
|
|
1249
|
+
this.eventDispatcher.dispatchEvent("message", msg);
|
|
1184
1250
|
}
|
|
1185
1251
|
/**
|
|
1186
1252
|
* Adds a listener to this transport.
|
|
@@ -1198,34 +1264,6 @@ var Transport = class {
|
|
|
1198
1264
|
removeEventListener(type, handler) {
|
|
1199
1265
|
this.eventDispatcher.removeEventListener(type, handler);
|
|
1200
1266
|
}
|
|
1201
|
-
/**
|
|
1202
|
-
* Sends a message over this transport, delegating to the appropriate connection to actually
|
|
1203
|
-
* send the message.
|
|
1204
|
-
* @param msg The message to send.
|
|
1205
|
-
* @returns The ID of the sent message or undefined if it wasn't sent
|
|
1206
|
-
*/
|
|
1207
|
-
send(to, msg) {
|
|
1208
|
-
if (this.getStatus() === "closed") {
|
|
1209
|
-
const err = "transport is closed, cant send";
|
|
1210
|
-
this.log?.error(err, {
|
|
1211
|
-
clientId: this.clientId,
|
|
1212
|
-
transportMessage: msg,
|
|
1213
|
-
tags: ["invariant-violation"]
|
|
1214
|
-
});
|
|
1215
|
-
throw new Error(err);
|
|
1216
|
-
}
|
|
1217
|
-
return this.getOrCreateSession({ to }).session.send(msg);
|
|
1218
|
-
}
|
|
1219
|
-
// control helpers
|
|
1220
|
-
sendCloseStream(to, streamId) {
|
|
1221
|
-
return this.send(to, {
|
|
1222
|
-
streamId,
|
|
1223
|
-
controlFlags: 4 /* StreamClosedBit */,
|
|
1224
|
-
payload: {
|
|
1225
|
-
type: "CLOSE"
|
|
1226
|
-
}
|
|
1227
|
-
});
|
|
1228
|
-
}
|
|
1229
1267
|
protocolError(type, message) {
|
|
1230
1268
|
this.eventDispatcher.dispatchEvent("protocolError", { type, message });
|
|
1231
1269
|
}
|
|
@@ -1237,7 +1275,7 @@ var Transport = class {
|
|
|
1237
1275
|
close() {
|
|
1238
1276
|
this.status = "closed";
|
|
1239
1277
|
for (const session of this.sessions.values()) {
|
|
1240
|
-
this.deleteSession(
|
|
1278
|
+
this.deleteSession(session);
|
|
1241
1279
|
}
|
|
1242
1280
|
this.eventDispatcher.dispatchEvent("transportStatus", {
|
|
1243
1281
|
status: this.status
|
|
@@ -1248,6 +1286,68 @@ var Transport = class {
|
|
|
1248
1286
|
getStatus() {
|
|
1249
1287
|
return this.status;
|
|
1250
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
|
+
}
|
|
1251
1351
|
};
|
|
1252
1352
|
|
|
1253
1353
|
// util/stringify.ts
|
|
@@ -1265,10 +1365,6 @@ var ClientTransport = class extends Transport {
|
|
|
1265
1365
|
* The options for this transport.
|
|
1266
1366
|
*/
|
|
1267
1367
|
options;
|
|
1268
|
-
/**
|
|
1269
|
-
* The map of reconnect promises for each client ID.
|
|
1270
|
-
*/
|
|
1271
|
-
inflightConnectionPromises;
|
|
1272
1368
|
retryBudget;
|
|
1273
1369
|
/**
|
|
1274
1370
|
* A flag indicating whether the transport should automatically reconnect
|
|
@@ -1287,352 +1383,278 @@ var ClientTransport = class extends Transport {
|
|
|
1287
1383
|
...defaultClientTransportOptions,
|
|
1288
1384
|
...providedOptions
|
|
1289
1385
|
};
|
|
1290
|
-
this.inflightConnectionPromises = /* @__PURE__ */ new Map();
|
|
1291
1386
|
this.retryBudget = new LeakyBucketRateLimit(this.options);
|
|
1292
1387
|
}
|
|
1293
1388
|
extendHandshake(options) {
|
|
1294
1389
|
this.handshakeExtensions = options;
|
|
1295
1390
|
}
|
|
1296
|
-
|
|
1297
|
-
if (this.getStatus()
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
}, this.options.handshakeTimeoutMs);
|
|
1309
|
-
const handshakeHandler = (data) => {
|
|
1310
|
-
const maybeSession = this.receiveHandshakeResponseMessage(data, conn);
|
|
1311
|
-
clearTimeout(handshakeTimeout);
|
|
1312
|
-
if (!maybeSession) {
|
|
1313
|
-
conn.close();
|
|
1314
|
-
return;
|
|
1315
|
-
} else {
|
|
1316
|
-
session = maybeSession;
|
|
1317
|
-
}
|
|
1318
|
-
conn.removeDataListener(handshakeHandler);
|
|
1319
|
-
conn.addDataListener((data2) => {
|
|
1320
|
-
const parsed = this.parseMsg(data2, conn);
|
|
1321
|
-
if (!parsed) {
|
|
1322
|
-
conn.telemetry?.span.setStatus({
|
|
1323
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1324
|
-
message: "message parse failure"
|
|
1325
|
-
});
|
|
1326
|
-
conn.close();
|
|
1327
|
-
return;
|
|
1328
|
-
}
|
|
1329
|
-
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"]
|
|
1330
1403
|
});
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
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);
|
|
1345
1419
|
}
|
|
1346
|
-
|
|
1347
|
-
this.
|
|
1348
|
-
|
|
1349
|
-
|
|
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);
|
|
1350
1471
|
}
|
|
1351
1472
|
});
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
...conn.loggingMetadata,
|
|
1361
|
-
...session?.loggingMetadata,
|
|
1362
|
-
clientId: this.clientId,
|
|
1363
|
-
connectedTo: to
|
|
1364
|
-
}
|
|
1365
|
-
);
|
|
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
|
|
1366
1481
|
});
|
|
1482
|
+
this.log?.warn(reason, metadata);
|
|
1483
|
+
this.deleteSession(session);
|
|
1367
1484
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
});
|
|
1375
|
-
this.protocolError(
|
|
1376
|
-
ProtocolError.HandshakeFailed,
|
|
1377
|
-
"received non-transport message"
|
|
1378
|
-
);
|
|
1379
|
-
return false;
|
|
1380
|
-
}
|
|
1381
|
-
if (!import_value2.Value.Check(ControlMessageHandshakeResponseSchema, parsed.payload)) {
|
|
1382
|
-
conn.telemetry?.span.setStatus({
|
|
1383
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1384
|
-
message: "invalid handshake response"
|
|
1385
|
-
});
|
|
1386
|
-
this.log?.warn(`received invalid handshake resp`, {
|
|
1387
|
-
...conn.loggingMetadata,
|
|
1388
|
-
clientId: this.clientId,
|
|
1389
|
-
connectedTo: parsed.from,
|
|
1390
|
-
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,
|
|
1391
1491
|
validationErrors: [
|
|
1392
|
-
...import_value2.Value.Errors(
|
|
1393
|
-
ControlMessageHandshakeResponseSchema,
|
|
1394
|
-
parsed.payload
|
|
1395
|
-
)
|
|
1492
|
+
...import_value2.Value.Errors(ControlMessageHandshakeResponseSchema, msg.payload)
|
|
1396
1493
|
]
|
|
1397
1494
|
});
|
|
1398
|
-
|
|
1399
|
-
ProtocolError.HandshakeFailed,
|
|
1400
|
-
"invalid handshake resp"
|
|
1401
|
-
);
|
|
1402
|
-
return false;
|
|
1495
|
+
return;
|
|
1403
1496
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
});
|
|
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);
|
|
1417
1509
|
} else {
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
message: "handshake rejected"
|
|
1421
|
-
});
|
|
1510
|
+
this.deleteSession(session);
|
|
1511
|
+
this.protocolError(ProtocolError.HandshakeFailed, reason);
|
|
1422
1512
|
}
|
|
1423
|
-
|
|
1424
|
-
`received handshake rejection: ${parsed.payload.status.reason}`,
|
|
1425
|
-
{
|
|
1426
|
-
...conn.loggingMetadata,
|
|
1427
|
-
clientId: this.clientId,
|
|
1428
|
-
connectedTo: parsed.from,
|
|
1429
|
-
transportMessage: parsed
|
|
1430
|
-
}
|
|
1431
|
-
);
|
|
1432
|
-
this.protocolError(
|
|
1433
|
-
ProtocolError.HandshakeFailed,
|
|
1434
|
-
parsed.payload.status.reason
|
|
1435
|
-
);
|
|
1436
|
-
return false;
|
|
1513
|
+
return;
|
|
1437
1514
|
}
|
|
1438
|
-
if (
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
conn.telemetry?.span.setStatus({
|
|
1444
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1445
|
-
message: "session id mismatch"
|
|
1446
|
-
});
|
|
1447
|
-
this.log?.warn(`handshake from ${parsed.from} session id mismatch`, {
|
|
1448
|
-
...conn.loggingMetadata,
|
|
1449
|
-
clientId: this.clientId,
|
|
1450
|
-
connectedTo: parsed.from,
|
|
1451
|
-
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
|
|
1452
1520
|
});
|
|
1453
|
-
|
|
1454
|
-
return false;
|
|
1521
|
+
return;
|
|
1455
1522
|
}
|
|
1456
|
-
this.log?.
|
|
1457
|
-
...
|
|
1458
|
-
|
|
1459
|
-
connectedTo: parsed.from,
|
|
1460
|
-
transportMessage: parsed
|
|
1523
|
+
this.log?.info(`handshake from ${msg.from} ok`, {
|
|
1524
|
+
...session.loggingMetadata,
|
|
1525
|
+
transportMessage: msg
|
|
1461
1526
|
});
|
|
1462
|
-
const
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
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
|
+
}
|
|
1466
1547
|
});
|
|
1467
|
-
this.
|
|
1468
|
-
this.retryBudget.startRestoringBudget(
|
|
1469
|
-
return session;
|
|
1548
|
+
this.updateSession(connectedSession);
|
|
1549
|
+
this.retryBudget.startRestoringBudget(connectedSession.to);
|
|
1470
1550
|
}
|
|
1471
1551
|
/**
|
|
1472
1552
|
* Manually attempts to connect to a client.
|
|
1473
1553
|
* @param to The client ID of the node to connect to.
|
|
1474
1554
|
*/
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
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
|
+
);
|
|
1481
1563
|
return;
|
|
1482
1564
|
}
|
|
1483
|
-
|
|
1484
|
-
if (!canProceedWithConnection()) {
|
|
1565
|
+
if (this.getStatus() !== "open") {
|
|
1485
1566
|
this.log?.info(
|
|
1486
1567
|
`transport state is no longer open, cancelling attempt to connect to ${to}`,
|
|
1487
|
-
|
|
1568
|
+
session.loggingMetadata
|
|
1488
1569
|
);
|
|
1489
1570
|
return;
|
|
1490
1571
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
this.protocolError(ProtocolError.RetriesExceeded, errMsg);
|
|
1498
|
-
return;
|
|
1499
|
-
}
|
|
1500
|
-
let sleep = Promise.resolve();
|
|
1501
|
-
const backoffMs = this.retryBudget.getBackoffMs(to);
|
|
1502
|
-
if (backoffMs > 0) {
|
|
1503
|
-
sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
1504
|
-
}
|
|
1505
|
-
this.log?.info(
|
|
1506
|
-
`attempting connection to ${to} (${backoffMs}ms backoff)`,
|
|
1507
|
-
{
|
|
1508
|
-
clientId: this.clientId,
|
|
1509
|
-
connectedTo: to
|
|
1510
|
-
}
|
|
1511
|
-
);
|
|
1512
|
-
this.retryBudget.consumeBudget(to);
|
|
1513
|
-
reconnectPromise = tracing_default.startActiveSpan("connect", async (span) => {
|
|
1514
|
-
try {
|
|
1515
|
-
span.addEvent("backoff", { backoffMs });
|
|
1516
|
-
await sleep;
|
|
1517
|
-
if (!canProceedWithConnection()) {
|
|
1518
|
-
throw new Error("transport state is no longer open");
|
|
1519
|
-
}
|
|
1520
|
-
span.addEvent("connecting");
|
|
1521
|
-
const conn = await this.createNewOutgoingConnection(to);
|
|
1522
|
-
if (!canProceedWithConnection()) {
|
|
1523
|
-
this.log?.info(
|
|
1524
|
-
`transport state is no longer open, closing pre-handshake connection to ${to}`,
|
|
1525
|
-
{
|
|
1526
|
-
...conn.loggingMetadata,
|
|
1527
|
-
clientId: this.clientId,
|
|
1528
|
-
connectedTo: to
|
|
1529
|
-
}
|
|
1530
|
-
);
|
|
1531
|
-
conn.close();
|
|
1532
|
-
throw new Error("transport state is no longer open");
|
|
1533
|
-
}
|
|
1534
|
-
span.addEvent("sending handshake");
|
|
1535
|
-
const ok = await this.sendHandshake(to, conn);
|
|
1536
|
-
if (!ok) {
|
|
1537
|
-
conn.close();
|
|
1538
|
-
throw new Error("failed to send handshake");
|
|
1539
|
-
}
|
|
1540
|
-
return conn;
|
|
1541
|
-
} catch (err) {
|
|
1542
|
-
const errStr = coerceErrorString(err);
|
|
1543
|
-
span.recordException(errStr);
|
|
1544
|
-
span.setStatus({ code: import_api4.SpanStatusCode.ERROR });
|
|
1545
|
-
throw err;
|
|
1546
|
-
} finally {
|
|
1547
|
-
span.end();
|
|
1548
|
-
}
|
|
1549
|
-
});
|
|
1550
|
-
this.inflightConnectionPromises.set(to, reconnectPromise);
|
|
1551
|
-
} else {
|
|
1552
|
-
this.log?.info(
|
|
1553
|
-
`attempting connection to ${to} (reusing previous attempt)`,
|
|
1554
|
-
{
|
|
1555
|
-
clientId: this.clientId,
|
|
1556
|
-
connectedTo: to
|
|
1557
|
-
}
|
|
1558
|
-
);
|
|
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;
|
|
1559
1578
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
const errStr = coerceErrorString(error);
|
|
1565
|
-
if (!this.reconnectOnConnectionDrop || !canProceedWithConnection()) {
|
|
1566
|
-
this.log?.warn(`connection to ${to} failed (${errStr})`, {
|
|
1567
|
-
clientId: this.clientId,
|
|
1568
|
-
connectedTo: to
|
|
1569
|
-
});
|
|
1570
|
-
} else {
|
|
1571
|
-
this.log?.warn(`connection to ${to} failed (${errStr}), retrying`, {
|
|
1572
|
-
clientId: this.clientId,
|
|
1573
|
-
connectedTo: to
|
|
1574
|
-
});
|
|
1575
|
-
await this.connect(to);
|
|
1576
|
-
}
|
|
1579
|
+
let sleep = Promise.resolve();
|
|
1580
|
+
const backoffMs = this.retryBudget.getBackoffMs(to);
|
|
1581
|
+
if (backoffMs > 0) {
|
|
1582
|
+
sleep = new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
1577
1583
|
}
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
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
|
+
}
|
|
1589
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);
|
|
1590
1636
|
}
|
|
1591
|
-
async sendHandshake(
|
|
1637
|
+
async sendHandshake(session) {
|
|
1592
1638
|
let metadata = void 0;
|
|
1593
1639
|
if (this.handshakeExtensions) {
|
|
1594
1640
|
metadata = await this.handshakeExtensions.construct();
|
|
1595
|
-
if (!import_value2.Value.Check(this.handshakeExtensions.schema, metadata)) {
|
|
1596
|
-
this.log?.error(`constructed handshake metadata did not match schema`, {
|
|
1597
|
-
...conn.loggingMetadata,
|
|
1598
|
-
clientId: this.clientId,
|
|
1599
|
-
connectedTo: to,
|
|
1600
|
-
validationErrors: [
|
|
1601
|
-
...import_value2.Value.Errors(this.handshakeExtensions.schema, metadata)
|
|
1602
|
-
],
|
|
1603
|
-
tags: ["invariant-violation"]
|
|
1604
|
-
});
|
|
1605
|
-
this.protocolError(
|
|
1606
|
-
ProtocolError.HandshakeFailed,
|
|
1607
|
-
"handshake metadata did not match schema"
|
|
1608
|
-
);
|
|
1609
|
-
conn.telemetry?.span.setStatus({
|
|
1610
|
-
code: import_api4.SpanStatusCode.ERROR,
|
|
1611
|
-
message: "handshake meta mismatch"
|
|
1612
|
-
});
|
|
1613
|
-
return false;
|
|
1614
|
-
}
|
|
1615
1641
|
}
|
|
1616
|
-
const { session } = this.getOrCreateSession({ to, handshakingConn: conn });
|
|
1617
1642
|
const requestMsg = handshakeRequestMessage({
|
|
1618
1643
|
from: this.clientId,
|
|
1619
|
-
to,
|
|
1644
|
+
to: session.to,
|
|
1620
1645
|
sessionId: session.id,
|
|
1621
1646
|
expectedSessionState: {
|
|
1622
|
-
|
|
1623
|
-
|
|
1647
|
+
nextExpectedSeq: session.ack,
|
|
1648
|
+
nextSentSeq: session.nextSeq()
|
|
1624
1649
|
},
|
|
1625
1650
|
metadata,
|
|
1626
1651
|
tracing: getPropagationContext(session.telemetry.ctx)
|
|
1627
1652
|
});
|
|
1628
|
-
this.log?.debug(`sending handshake request to ${to}`, {
|
|
1629
|
-
...
|
|
1630
|
-
clientId: this.clientId,
|
|
1631
|
-
connectedTo: to,
|
|
1653
|
+
this.log?.debug(`sending handshake request to ${session.to}`, {
|
|
1654
|
+
...session.loggingMetadata,
|
|
1632
1655
|
transportMessage: requestMsg
|
|
1633
1656
|
});
|
|
1634
|
-
|
|
1635
|
-
return true;
|
|
1657
|
+
session.sendHandshake(requestMsg);
|
|
1636
1658
|
}
|
|
1637
1659
|
close() {
|
|
1638
1660
|
this.retryBudget.close();
|
|
@@ -1648,10 +1670,6 @@ var UnixDomainSocketClientTransport = class extends ClientTransport {
|
|
|
1648
1670
|
this.path = socketPath;
|
|
1649
1671
|
}
|
|
1650
1672
|
async createNewOutgoingConnection(to) {
|
|
1651
|
-
const oldConnection = this.connections.get(to);
|
|
1652
|
-
if (oldConnection) {
|
|
1653
|
-
oldConnection.close();
|
|
1654
|
-
}
|
|
1655
1673
|
this.log?.info(`establishing a new uds to ${to}`, {
|
|
1656
1674
|
clientId: this.clientId,
|
|
1657
1675
|
connectedTo: to
|
|
@@ -1662,9 +1680,7 @@ var UnixDomainSocketClientTransport = class extends ClientTransport {
|
|
|
1662
1680
|
sock2.on("error", (err) => reject(err));
|
|
1663
1681
|
sock2.connect(this.path);
|
|
1664
1682
|
});
|
|
1665
|
-
|
|
1666
|
-
this.handleConnection(conn, to);
|
|
1667
|
-
return conn;
|
|
1683
|
+
return new UdsConnection(sock);
|
|
1668
1684
|
}
|
|
1669
1685
|
};
|
|
1670
1686
|
// Annotate the CommonJS export names for ESM import in node:
|