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