@replit/river 0.23.16 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -20
- package/dist/{chunk-UDXM64QK.js → chunk-AASMR3CQ.js} +24 -18
- package/dist/chunk-AASMR3CQ.js.map +1 -0
- package/dist/chunk-JA57I7MG.js +653 -0
- package/dist/chunk-JA57I7MG.js.map +1 -0
- package/dist/chunk-KX5PQRVN.js +382 -0
- package/dist/chunk-KX5PQRVN.js.map +1 -0
- package/dist/{chunk-LTSLICON.js → chunk-KYYB4DUR.js} +68 -519
- package/dist/chunk-KYYB4DUR.js.map +1 -0
- package/dist/chunk-NLQPPDOT.js +399 -0
- package/dist/chunk-NLQPPDOT.js.map +1 -0
- package/dist/{chunk-TXSQRTZB.js → chunk-PJGGC3LV.js} +55 -41
- package/dist/chunk-PJGGC3LV.js.map +1 -0
- package/dist/chunk-RXJLI2OP.js +50 -0
- package/dist/chunk-RXJLI2OP.js.map +1 -0
- package/dist/{chunk-6LCL2ZZF.js → chunk-TAH2GVTJ.js} +1 -1
- package/dist/chunk-TAH2GVTJ.js.map +1 -0
- package/dist/chunk-ZAT3R4CU.js +277 -0
- package/dist/chunk-ZAT3R4CU.js.map +1 -0
- package/dist/{client-0926d3d6.d.ts → client-ba0d3315.d.ts} +12 -15
- package/dist/{connection-99a67d3e.d.ts → connection-c3a96d09.d.ts} +1 -5
- package/dist/connection-d33e3246.d.ts +11 -0
- package/dist/{handshake-75d0124f.d.ts → handshake-cdead82a.d.ts} +149 -180
- package/dist/logging/index.cjs.map +1 -1
- package/dist/logging/index.d.cts +1 -1
- package/dist/logging/index.d.ts +1 -1
- package/dist/logging/index.js +1 -1
- package/dist/{index-ea74cdbb.d.ts → message-e6c560fd.d.ts} +2 -2
- package/dist/router/index.cjs +107 -530
- package/dist/router/index.cjs.map +1 -1
- package/dist/router/index.d.cts +12 -50
- package/dist/router/index.d.ts +12 -50
- package/dist/router/index.js +2 -4
- package/dist/server-2ef5e6ec.d.ts +42 -0
- package/dist/{services-75e84a9f.d.ts → services-e1417b33.d.ts} +7 -7
- package/dist/transport/impls/uds/client.cjs +1242 -1223
- package/dist/transport/impls/uds/client.cjs.map +1 -1
- package/dist/transport/impls/uds/client.d.cts +4 -4
- package/dist/transport/impls/uds/client.d.ts +4 -4
- package/dist/transport/impls/uds/client.js +7 -13
- package/dist/transport/impls/uds/client.js.map +1 -1
- package/dist/transport/impls/uds/server.cjs +1301 -1151
- package/dist/transport/impls/uds/server.cjs.map +1 -1
- package/dist/transport/impls/uds/server.d.cts +4 -4
- package/dist/transport/impls/uds/server.d.ts +4 -4
- package/dist/transport/impls/uds/server.js +6 -6
- package/dist/transport/impls/ws/client.cjs +980 -969
- package/dist/transport/impls/ws/client.cjs.map +1 -1
- package/dist/transport/impls/ws/client.d.cts +4 -4
- package/dist/transport/impls/ws/client.d.ts +4 -4
- package/dist/transport/impls/ws/client.js +6 -7
- package/dist/transport/impls/ws/client.js.map +1 -1
- package/dist/transport/impls/ws/server.cjs +1182 -1047
- package/dist/transport/impls/ws/server.cjs.map +1 -1
- package/dist/transport/impls/ws/server.d.cts +4 -4
- package/dist/transport/impls/ws/server.d.ts +4 -4
- package/dist/transport/impls/ws/server.js +6 -6
- package/dist/transport/index.cjs +1434 -1360
- package/dist/transport/index.cjs.map +1 -1
- package/dist/transport/index.d.cts +4 -4
- package/dist/transport/index.d.ts +4 -4
- package/dist/transport/index.js +9 -9
- package/dist/util/testHelpers.cjs +743 -309
- package/dist/util/testHelpers.cjs.map +1 -1
- package/dist/util/testHelpers.d.cts +10 -7
- package/dist/util/testHelpers.d.ts +10 -7
- package/dist/util/testHelpers.js +33 -10
- package/dist/util/testHelpers.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-6LCL2ZZF.js.map +0 -1
- package/dist/chunk-JA7XGTAL.js +0 -476
- package/dist/chunk-JA7XGTAL.js.map +0 -1
- package/dist/chunk-LTSLICON.js.map +0 -1
- package/dist/chunk-MQCGG6KL.js +0 -335
- package/dist/chunk-MQCGG6KL.js.map +0 -1
- package/dist/chunk-R47IZD67.js +0 -59
- package/dist/chunk-R47IZD67.js.map +0 -1
- package/dist/chunk-TXSQRTZB.js.map +0 -1
- package/dist/chunk-UDXM64QK.js.map +0 -1
- package/dist/chunk-WN77AT67.js +0 -476
- package/dist/chunk-WN77AT67.js.map +0 -1
- package/dist/chunk-YXDAOVP7.js +0 -347
- package/dist/chunk-YXDAOVP7.js.map +0 -1
- package/dist/connection-d738cc08.d.ts +0 -17
- package/dist/server-3740c5d9.d.ts +0 -24
|
@@ -34,12 +34,15 @@ __export(testHelpers_exports, {
|
|
|
34
34
|
asClientStream: () => asClientStream,
|
|
35
35
|
asClientSubscription: () => asClientSubscription,
|
|
36
36
|
asClientUpload: () => asClientUpload,
|
|
37
|
+
closeAllConnections: () => closeAllConnections,
|
|
37
38
|
createDummyTransportMessage: () => createDummyTransportMessage,
|
|
38
39
|
createLocalWebSocketClient: () => createLocalWebSocketClient,
|
|
39
40
|
createWebSocketServer: () => createWebSocketServer,
|
|
40
41
|
dummySession: () => dummySession,
|
|
42
|
+
getTransportConnections: () => getTransportConnections,
|
|
41
43
|
getUnixSocketPath: () => getUnixSocketPath,
|
|
42
44
|
iterNext: () => iterNext,
|
|
45
|
+
numberOfConnections: () => numberOfConnections,
|
|
43
46
|
onUdsServeReady: () => onUdsServeReady,
|
|
44
47
|
onWsServerReady: () => onWsServerReady,
|
|
45
48
|
payloadToTransportMessage: () => payloadToTransportMessage,
|
|
@@ -331,16 +334,112 @@ function _pushable(getNext, options) {
|
|
|
331
334
|
return pushable2;
|
|
332
335
|
}
|
|
333
336
|
|
|
334
|
-
//
|
|
337
|
+
// transport/message.ts
|
|
335
338
|
var import_typebox = require("@sinclair/typebox");
|
|
339
|
+
|
|
340
|
+
// transport/id.ts
|
|
341
|
+
var import_nanoid = require("nanoid");
|
|
342
|
+
var alphabet = (0, import_nanoid.customAlphabet)(
|
|
343
|
+
"1234567890abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ"
|
|
344
|
+
);
|
|
345
|
+
var generateId = () => alphabet(12);
|
|
346
|
+
|
|
347
|
+
// transport/message.ts
|
|
348
|
+
var TransportMessageSchema = (t) => import_typebox.Type.Object({
|
|
349
|
+
id: import_typebox.Type.String(),
|
|
350
|
+
from: import_typebox.Type.String(),
|
|
351
|
+
to: import_typebox.Type.String(),
|
|
352
|
+
seq: import_typebox.Type.Integer(),
|
|
353
|
+
ack: import_typebox.Type.Integer(),
|
|
354
|
+
serviceName: import_typebox.Type.Optional(import_typebox.Type.String()),
|
|
355
|
+
procedureName: import_typebox.Type.Optional(import_typebox.Type.String()),
|
|
356
|
+
streamId: import_typebox.Type.String(),
|
|
357
|
+
controlFlags: import_typebox.Type.Integer(),
|
|
358
|
+
tracing: import_typebox.Type.Optional(
|
|
359
|
+
import_typebox.Type.Object({
|
|
360
|
+
traceparent: import_typebox.Type.String(),
|
|
361
|
+
tracestate: import_typebox.Type.String()
|
|
362
|
+
})
|
|
363
|
+
),
|
|
364
|
+
payload: t
|
|
365
|
+
});
|
|
366
|
+
var ControlMessageAckSchema = import_typebox.Type.Object({
|
|
367
|
+
type: import_typebox.Type.Literal("ACK")
|
|
368
|
+
});
|
|
369
|
+
var ControlMessageCloseSchema = import_typebox.Type.Object({
|
|
370
|
+
type: import_typebox.Type.Literal("CLOSE")
|
|
371
|
+
});
|
|
372
|
+
var ControlMessageHandshakeRequestSchema = import_typebox.Type.Object({
|
|
373
|
+
type: import_typebox.Type.Literal("HANDSHAKE_REQ"),
|
|
374
|
+
protocolVersion: import_typebox.Type.String(),
|
|
375
|
+
sessionId: import_typebox.Type.String(),
|
|
376
|
+
/**
|
|
377
|
+
* Specifies what the server's expected session state (from the pov of the client). This can be
|
|
378
|
+
* used by the server to know whether this is a new or a reestablished connection, and whether it
|
|
379
|
+
* is compatible with what it already has.
|
|
380
|
+
*/
|
|
381
|
+
expectedSessionState: import_typebox.Type.Object({
|
|
382
|
+
// what the client expects the server to send next
|
|
383
|
+
nextExpectedSeq: import_typebox.Type.Integer(),
|
|
384
|
+
// TODO: remove optional once we know all servers
|
|
385
|
+
// are nextSentSeq here
|
|
386
|
+
// what the server expects the client to send next
|
|
387
|
+
nextSentSeq: import_typebox.Type.Optional(import_typebox.Type.Integer())
|
|
388
|
+
}),
|
|
389
|
+
metadata: import_typebox.Type.Optional(import_typebox.Type.Unknown())
|
|
390
|
+
});
|
|
391
|
+
var HandshakeErrorRetriableResponseCodes = import_typebox.Type.Union([
|
|
392
|
+
import_typebox.Type.Literal("SESSION_STATE_MISMATCH")
|
|
393
|
+
]);
|
|
394
|
+
var HandshakeErrorFatalResponseCodes = import_typebox.Type.Union([
|
|
395
|
+
import_typebox.Type.Literal("MALFORMED_HANDSHAKE_META"),
|
|
396
|
+
import_typebox.Type.Literal("MALFORMED_HANDSHAKE"),
|
|
397
|
+
import_typebox.Type.Literal("PROTOCOL_VERSION_MISMATCH"),
|
|
398
|
+
import_typebox.Type.Literal("REJECTED_BY_CUSTOM_HANDLER")
|
|
399
|
+
]);
|
|
400
|
+
var HandshakeErrorResponseCodes = import_typebox.Type.Union([
|
|
401
|
+
HandshakeErrorRetriableResponseCodes,
|
|
402
|
+
HandshakeErrorFatalResponseCodes
|
|
403
|
+
]);
|
|
404
|
+
var ControlMessageHandshakeResponseSchema = import_typebox.Type.Object({
|
|
405
|
+
type: import_typebox.Type.Literal("HANDSHAKE_RESP"),
|
|
406
|
+
status: import_typebox.Type.Union([
|
|
407
|
+
import_typebox.Type.Object({
|
|
408
|
+
ok: import_typebox.Type.Literal(true),
|
|
409
|
+
sessionId: import_typebox.Type.String()
|
|
410
|
+
}),
|
|
411
|
+
import_typebox.Type.Object({
|
|
412
|
+
ok: import_typebox.Type.Literal(false),
|
|
413
|
+
reason: import_typebox.Type.String(),
|
|
414
|
+
// TODO: remove optional once we know all servers
|
|
415
|
+
// are sending code here
|
|
416
|
+
code: import_typebox.Type.Optional(HandshakeErrorResponseCodes)
|
|
417
|
+
})
|
|
418
|
+
])
|
|
419
|
+
});
|
|
420
|
+
var ControlMessagePayloadSchema = import_typebox.Type.Union([
|
|
421
|
+
ControlMessageCloseSchema,
|
|
422
|
+
ControlMessageAckSchema,
|
|
423
|
+
ControlMessageHandshakeRequestSchema,
|
|
424
|
+
ControlMessageHandshakeResponseSchema
|
|
425
|
+
]);
|
|
426
|
+
var OpaqueTransportMessageSchema = TransportMessageSchema(
|
|
427
|
+
import_typebox.Type.Unknown()
|
|
428
|
+
);
|
|
429
|
+
function isAck(controlFlag) {
|
|
430
|
+
return (controlFlag & 1 /* AckBit */) === 1 /* AckBit */;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// router/result.ts
|
|
434
|
+
var import_typebox2 = require("@sinclair/typebox");
|
|
336
435
|
var UNCAUGHT_ERROR = "UNCAUGHT_ERROR";
|
|
337
436
|
var UNEXPECTED_DISCONNECT = "UNEXPECTED_DISCONNECT";
|
|
338
|
-
var RiverUncaughtSchema =
|
|
339
|
-
code:
|
|
340
|
-
|
|
341
|
-
|
|
437
|
+
var RiverUncaughtSchema = import_typebox2.Type.Object({
|
|
438
|
+
code: import_typebox2.Type.Union([
|
|
439
|
+
import_typebox2.Type.Literal(UNCAUGHT_ERROR),
|
|
440
|
+
import_typebox2.Type.Literal(UNEXPECTED_DISCONNECT)
|
|
342
441
|
]),
|
|
343
|
-
message:
|
|
442
|
+
message: import_typebox2.Type.String()
|
|
344
443
|
});
|
|
345
444
|
function Err(error) {
|
|
346
445
|
return {
|
|
@@ -353,19 +452,19 @@ function Err(error) {
|
|
|
353
452
|
var import_api = require("@opentelemetry/api");
|
|
354
453
|
|
|
355
454
|
// package.json
|
|
356
|
-
var version = "0.
|
|
455
|
+
var version = "0.24.0";
|
|
357
456
|
|
|
358
457
|
// tracing/index.ts
|
|
359
|
-
function createSessionTelemetryInfo(
|
|
458
|
+
function createSessionTelemetryInfo(sessionId, to, from, propagationCtx) {
|
|
360
459
|
const parentCtx = propagationCtx ? import_api.propagation.extract(import_api.context.active(), propagationCtx) : import_api.context.active();
|
|
361
460
|
const span = tracer.startSpan(
|
|
362
|
-
`session ${
|
|
461
|
+
`session ${sessionId}`,
|
|
363
462
|
{
|
|
364
463
|
attributes: {
|
|
365
464
|
component: "river",
|
|
366
|
-
"river.session.id":
|
|
367
|
-
"river.session.to":
|
|
368
|
-
"river.session.from":
|
|
465
|
+
"river.session.id": sessionId,
|
|
466
|
+
"river.session.to": to,
|
|
467
|
+
"river.session.from": from
|
|
369
468
|
}
|
|
370
469
|
},
|
|
371
470
|
parentCtx
|
|
@@ -386,298 +485,6 @@ function coerceErrorString(err) {
|
|
|
386
485
|
// util/testHelpers.ts
|
|
387
486
|
var import_nanoid2 = require("nanoid");
|
|
388
487
|
|
|
389
|
-
// transport/session.ts
|
|
390
|
-
var import_nanoid = require("nanoid");
|
|
391
|
-
var import_api2 = require("@opentelemetry/api");
|
|
392
|
-
var nanoid = (0, import_nanoid.customAlphabet)("1234567890abcdefghijklmnopqrstuvxyz", 6);
|
|
393
|
-
var unsafeId = () => nanoid();
|
|
394
|
-
var Session = class {
|
|
395
|
-
codec;
|
|
396
|
-
options;
|
|
397
|
-
telemetry;
|
|
398
|
-
/**
|
|
399
|
-
* The buffer of messages that have been sent but not yet acknowledged.
|
|
400
|
-
*/
|
|
401
|
-
sendBuffer = [];
|
|
402
|
-
/**
|
|
403
|
-
* The active connection associated with this session
|
|
404
|
-
*/
|
|
405
|
-
connection;
|
|
406
|
-
/**
|
|
407
|
-
* A connection that is currently undergoing handshaking. Used to distinguish between the active
|
|
408
|
-
* connection, but still be able to close it if needed.
|
|
409
|
-
*/
|
|
410
|
-
handshakingConnection;
|
|
411
|
-
from;
|
|
412
|
-
to;
|
|
413
|
-
/**
|
|
414
|
-
* The unique ID of this session.
|
|
415
|
-
*/
|
|
416
|
-
id;
|
|
417
|
-
/**
|
|
418
|
-
* What the other side advertised as their session ID
|
|
419
|
-
* for this session.
|
|
420
|
-
*/
|
|
421
|
-
advertisedSessionId;
|
|
422
|
-
/**
|
|
423
|
-
* Number of messages we've sent along this session (excluding handshake and acks)
|
|
424
|
-
*/
|
|
425
|
-
seq = 0;
|
|
426
|
-
/**
|
|
427
|
-
* Number of unique messages we've received this session (excluding handshake and acks)
|
|
428
|
-
*/
|
|
429
|
-
ack = 0;
|
|
430
|
-
/**
|
|
431
|
-
* The grace period between when the inner connection is disconnected
|
|
432
|
-
* and when we should consider the entire session disconnected.
|
|
433
|
-
*/
|
|
434
|
-
disconnectionGrace;
|
|
435
|
-
/**
|
|
436
|
-
* Number of heartbeats we've sent without a response.
|
|
437
|
-
*/
|
|
438
|
-
heartbeatMisses;
|
|
439
|
-
/**
|
|
440
|
-
* The interval for sending heartbeats.
|
|
441
|
-
*/
|
|
442
|
-
heartbeat;
|
|
443
|
-
log;
|
|
444
|
-
constructor(conn, from, to, options, propagationCtx) {
|
|
445
|
-
this.id = `session-${nanoid(12)}`;
|
|
446
|
-
this.options = options;
|
|
447
|
-
this.from = from;
|
|
448
|
-
this.to = to;
|
|
449
|
-
this.connection = conn;
|
|
450
|
-
this.codec = options.codec;
|
|
451
|
-
this.heartbeatMisses = 0;
|
|
452
|
-
this.heartbeat = setInterval(
|
|
453
|
-
() => this.sendHeartbeat(),
|
|
454
|
-
options.heartbeatIntervalMs
|
|
455
|
-
);
|
|
456
|
-
this.telemetry = createSessionTelemetryInfo(this, propagationCtx);
|
|
457
|
-
}
|
|
458
|
-
bindLogger(log) {
|
|
459
|
-
this.log = log;
|
|
460
|
-
}
|
|
461
|
-
get loggingMetadata() {
|
|
462
|
-
const spanContext = this.telemetry.span.spanContext();
|
|
463
|
-
return {
|
|
464
|
-
clientId: this.from,
|
|
465
|
-
connectedTo: this.to,
|
|
466
|
-
sessionId: this.id,
|
|
467
|
-
connId: this.connection?.id,
|
|
468
|
-
telemetry: {
|
|
469
|
-
traceId: spanContext.traceId,
|
|
470
|
-
spanId: spanContext.spanId
|
|
471
|
-
}
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
/**
|
|
475
|
-
* Sends a message over the session's connection.
|
|
476
|
-
* If the connection is not ready or the message fails to send, the message can be buffered for retry unless skipped.
|
|
477
|
-
*
|
|
478
|
-
* @param msg The partial message to be sent, which will be constructed into a full message.
|
|
479
|
-
* @param addToSendBuff Whether to add the message to the send buffer for retry.
|
|
480
|
-
* @returns The full transport ID of the message that was attempted to be sent.
|
|
481
|
-
*/
|
|
482
|
-
send(msg) {
|
|
483
|
-
const fullMsg = this.constructMsg(msg);
|
|
484
|
-
this.log?.debug(`sending msg`, {
|
|
485
|
-
...this.loggingMetadata,
|
|
486
|
-
transportMessage: fullMsg
|
|
487
|
-
});
|
|
488
|
-
if (this.connection) {
|
|
489
|
-
const ok = this.connection.send(this.codec.toBuffer(fullMsg));
|
|
490
|
-
if (ok)
|
|
491
|
-
return fullMsg.id;
|
|
492
|
-
this.log?.info(
|
|
493
|
-
`failed to send msg to ${fullMsg.to}, connection is probably dead`,
|
|
494
|
-
{
|
|
495
|
-
...this.loggingMetadata,
|
|
496
|
-
transportMessage: fullMsg
|
|
497
|
-
}
|
|
498
|
-
);
|
|
499
|
-
} else {
|
|
500
|
-
this.log?.debug(
|
|
501
|
-
`buffering msg to ${fullMsg.to}, connection not ready yet`,
|
|
502
|
-
{ ...this.loggingMetadata, transportMessage: fullMsg }
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
return fullMsg.id;
|
|
506
|
-
}
|
|
507
|
-
sendHeartbeat() {
|
|
508
|
-
const misses = this.heartbeatMisses;
|
|
509
|
-
const missDuration = misses * this.options.heartbeatIntervalMs;
|
|
510
|
-
if (misses > this.options.heartbeatsUntilDead) {
|
|
511
|
-
if (this.connection) {
|
|
512
|
-
this.log?.info(
|
|
513
|
-
`closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
|
|
514
|
-
this.loggingMetadata
|
|
515
|
-
);
|
|
516
|
-
this.telemetry.span.addEvent("closing connection due to inactivity");
|
|
517
|
-
this.closeStaleConnection();
|
|
518
|
-
}
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
this.send({
|
|
522
|
-
streamId: "heartbeat",
|
|
523
|
-
controlFlags: 1 /* AckBit */,
|
|
524
|
-
payload: {
|
|
525
|
-
type: "ACK"
|
|
526
|
-
}
|
|
527
|
-
});
|
|
528
|
-
this.heartbeatMisses++;
|
|
529
|
-
}
|
|
530
|
-
resetBufferedMessages() {
|
|
531
|
-
this.sendBuffer = [];
|
|
532
|
-
this.seq = 0;
|
|
533
|
-
this.ack = 0;
|
|
534
|
-
}
|
|
535
|
-
sendBufferedMessages(conn) {
|
|
536
|
-
this.log?.info(`resending ${this.sendBuffer.length} buffered messages`, {
|
|
537
|
-
...this.loggingMetadata,
|
|
538
|
-
connId: conn.id
|
|
539
|
-
});
|
|
540
|
-
for (const msg of this.sendBuffer) {
|
|
541
|
-
this.log?.debug(`resending msg`, {
|
|
542
|
-
...this.loggingMetadata,
|
|
543
|
-
transportMessage: msg,
|
|
544
|
-
connId: conn.id
|
|
545
|
-
});
|
|
546
|
-
const ok = conn.send(this.codec.toBuffer(msg));
|
|
547
|
-
if (!ok) {
|
|
548
|
-
const errMsg = `failed to send buffered message to ${this.to} (sus, this is a fresh connection)`;
|
|
549
|
-
conn.telemetry?.span.setStatus({
|
|
550
|
-
code: import_api2.SpanStatusCode.ERROR,
|
|
551
|
-
message: errMsg
|
|
552
|
-
});
|
|
553
|
-
this.log?.error(errMsg, {
|
|
554
|
-
...this.loggingMetadata,
|
|
555
|
-
transportMessage: msg,
|
|
556
|
-
connId: conn.id,
|
|
557
|
-
tags: ["invariant-violation"]
|
|
558
|
-
});
|
|
559
|
-
conn.close();
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
updateBookkeeping(ack, seq) {
|
|
565
|
-
if (seq + 1 < this.ack) {
|
|
566
|
-
this.log?.error(`received stale seq ${seq} + 1 < ${this.ack}`, {
|
|
567
|
-
...this.loggingMetadata,
|
|
568
|
-
tags: ["invariant-violation"]
|
|
569
|
-
});
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
|
|
573
|
-
this.ack = seq + 1;
|
|
574
|
-
}
|
|
575
|
-
closeStaleConnection(conn) {
|
|
576
|
-
if (this.connection === void 0 || this.connection === conn)
|
|
577
|
-
return;
|
|
578
|
-
this.log?.info(
|
|
579
|
-
`closing old inner connection from session to ${this.to}`,
|
|
580
|
-
this.loggingMetadata
|
|
581
|
-
);
|
|
582
|
-
this.connection.close();
|
|
583
|
-
this.connection = void 0;
|
|
584
|
-
}
|
|
585
|
-
replaceWithNewConnection(newConn, isTransparentReconnect) {
|
|
586
|
-
this.closeStaleConnection(newConn);
|
|
587
|
-
this.cancelGrace();
|
|
588
|
-
if (isTransparentReconnect) {
|
|
589
|
-
this.sendBufferedMessages(newConn);
|
|
590
|
-
}
|
|
591
|
-
this.connection = newConn;
|
|
592
|
-
this.handshakingConnection = void 0;
|
|
593
|
-
}
|
|
594
|
-
replaceWithNewHandshakingConnection(newConn) {
|
|
595
|
-
this.handshakingConnection = newConn;
|
|
596
|
-
}
|
|
597
|
-
beginGrace(cb) {
|
|
598
|
-
this.log?.info(
|
|
599
|
-
`starting ${this.options.sessionDisconnectGraceMs}ms grace period until session to ${this.to} is closed`,
|
|
600
|
-
this.loggingMetadata
|
|
601
|
-
);
|
|
602
|
-
this.cancelGrace();
|
|
603
|
-
this.disconnectionGrace = setTimeout(() => {
|
|
604
|
-
this.log?.info(
|
|
605
|
-
`grace period for ${this.to} elapsed`,
|
|
606
|
-
this.loggingMetadata
|
|
607
|
-
);
|
|
608
|
-
cb();
|
|
609
|
-
}, this.options.sessionDisconnectGraceMs);
|
|
610
|
-
}
|
|
611
|
-
// called on reconnect of the underlying session
|
|
612
|
-
cancelGrace() {
|
|
613
|
-
this.heartbeatMisses = 0;
|
|
614
|
-
clearTimeout(this.disconnectionGrace);
|
|
615
|
-
this.disconnectionGrace = void 0;
|
|
616
|
-
}
|
|
617
|
-
/**
|
|
618
|
-
* Used to close the handshaking connection, if set.
|
|
619
|
-
*/
|
|
620
|
-
closeHandshakingConnection(expectedHandshakingConn) {
|
|
621
|
-
if (this.handshakingConnection === void 0)
|
|
622
|
-
return;
|
|
623
|
-
if (expectedHandshakingConn !== void 0 && this.handshakingConnection === expectedHandshakingConn) {
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
this.handshakingConnection.close();
|
|
627
|
-
this.handshakingConnection = void 0;
|
|
628
|
-
}
|
|
629
|
-
// closed when we want to discard the whole session
|
|
630
|
-
// (i.e. shutdown or session disconnect)
|
|
631
|
-
close() {
|
|
632
|
-
this.closeStaleConnection();
|
|
633
|
-
this.cancelGrace();
|
|
634
|
-
this.resetBufferedMessages();
|
|
635
|
-
clearInterval(this.heartbeat);
|
|
636
|
-
}
|
|
637
|
-
get connected() {
|
|
638
|
-
return this.connection !== void 0;
|
|
639
|
-
}
|
|
640
|
-
get nextExpectedAck() {
|
|
641
|
-
return this.seq;
|
|
642
|
-
}
|
|
643
|
-
get nextExpectedSeq() {
|
|
644
|
-
return this.ack;
|
|
645
|
-
}
|
|
646
|
-
/**
|
|
647
|
-
* Check that the peer's next expected seq number matches something that is in our send buffer
|
|
648
|
-
* _or_ matches our actual next seq.
|
|
649
|
-
*/
|
|
650
|
-
nextExpectedSeqInRange(nextExpectedSeq) {
|
|
651
|
-
for (const msg of this.sendBuffer) {
|
|
652
|
-
if (nextExpectedSeq === msg.seq) {
|
|
653
|
-
return true;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
return nextExpectedSeq === this.seq;
|
|
657
|
-
}
|
|
658
|
-
// This is only used in tests to make the session misbehave.
|
|
659
|
-
/* @internal */
|
|
660
|
-
advanceAckForTesting(by) {
|
|
661
|
-
this.ack += by;
|
|
662
|
-
}
|
|
663
|
-
constructMsg(partialMsg) {
|
|
664
|
-
const msg = {
|
|
665
|
-
...partialMsg,
|
|
666
|
-
id: unsafeId(),
|
|
667
|
-
to: this.to,
|
|
668
|
-
from: this.from,
|
|
669
|
-
seq: this.seq,
|
|
670
|
-
ack: this.ack
|
|
671
|
-
};
|
|
672
|
-
this.seq++;
|
|
673
|
-
this.sendBuffer.push(msg);
|
|
674
|
-
return msg;
|
|
675
|
-
}
|
|
676
|
-
inspectSendBuffer() {
|
|
677
|
-
return this.sendBuffer;
|
|
678
|
-
}
|
|
679
|
-
};
|
|
680
|
-
|
|
681
488
|
// codec/json.ts
|
|
682
489
|
var encoder = new TextEncoder();
|
|
683
490
|
var decoder = new TextDecoder();
|
|
@@ -735,6 +542,8 @@ var defaultTransportOptions = {
|
|
|
735
542
|
heartbeatIntervalMs: 1e3,
|
|
736
543
|
heartbeatsUntilDead: 2,
|
|
737
544
|
sessionDisconnectGraceMs: 5e3,
|
|
545
|
+
connectionTimeoutMs: 2e3,
|
|
546
|
+
handshakeTimeoutMs: 1e3,
|
|
738
547
|
codec: NaiveJsonCodec
|
|
739
548
|
};
|
|
740
549
|
var defaultConnectionRetryOptions = {
|
|
@@ -752,6 +561,609 @@ var defaultServerTransportOptions = {
|
|
|
752
561
|
...defaultTransportOptions
|
|
753
562
|
};
|
|
754
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
|
+
// transport/sessionStateMachine/SessionWaitingForHandshake.ts
|
|
776
|
+
var SessionWaitingForHandshake = class extends CommonSession {
|
|
777
|
+
state = "WaitingForHandshake" /* WaitingForHandshake */;
|
|
778
|
+
conn;
|
|
779
|
+
listeners;
|
|
780
|
+
handshakeTimeout;
|
|
781
|
+
constructor(conn, listeners, ...args) {
|
|
782
|
+
super(...args);
|
|
783
|
+
this.conn = conn;
|
|
784
|
+
this.listeners = listeners;
|
|
785
|
+
this.handshakeTimeout = setTimeout(() => {
|
|
786
|
+
listeners.onHandshakeTimeout();
|
|
787
|
+
}, this.options.handshakeTimeoutMs);
|
|
788
|
+
this.conn.addDataListener(this.onHandshakeData);
|
|
789
|
+
this.conn.addErrorListener(listeners.onConnectionErrored);
|
|
790
|
+
this.conn.addCloseListener(listeners.onConnectionClosed);
|
|
791
|
+
}
|
|
792
|
+
onHandshakeData = (msg) => {
|
|
793
|
+
const parsedMsg = this.parseMsg(msg);
|
|
794
|
+
if (parsedMsg === null) {
|
|
795
|
+
this.listeners.onInvalidHandshake("could not parse message");
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
this.listeners.onHandshake(parsedMsg);
|
|
799
|
+
};
|
|
800
|
+
get loggingMetadata() {
|
|
801
|
+
return {
|
|
802
|
+
clientId: this.from,
|
|
803
|
+
connId: this.conn.id
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
sendHandshake(msg) {
|
|
807
|
+
return this.conn.send(this.options.codec.toBuffer(msg));
|
|
808
|
+
}
|
|
809
|
+
_handleStateExit() {
|
|
810
|
+
this.conn.removeDataListener(this.onHandshakeData);
|
|
811
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
812
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
813
|
+
clearTimeout(this.handshakeTimeout);
|
|
814
|
+
this.handshakeTimeout = void 0;
|
|
815
|
+
}
|
|
816
|
+
_handleClose() {
|
|
817
|
+
this.conn.close();
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
// transport/sessionStateMachine/SessionHandshaking.ts
|
|
822
|
+
var SessionHandshaking = class extends IdentifiedSession {
|
|
823
|
+
state = "Handshaking" /* Handshaking */;
|
|
824
|
+
conn;
|
|
825
|
+
listeners;
|
|
826
|
+
handshakeTimeout;
|
|
827
|
+
constructor(conn, listeners, ...args) {
|
|
828
|
+
super(...args);
|
|
829
|
+
this.conn = conn;
|
|
830
|
+
this.listeners = listeners;
|
|
831
|
+
this.handshakeTimeout = setTimeout(() => {
|
|
832
|
+
listeners.onHandshakeTimeout();
|
|
833
|
+
}, this.options.handshakeTimeoutMs);
|
|
834
|
+
this.conn.addDataListener(this.onHandshakeData);
|
|
835
|
+
this.conn.addErrorListener(listeners.onConnectionErrored);
|
|
836
|
+
this.conn.addCloseListener(listeners.onConnectionClosed);
|
|
837
|
+
}
|
|
838
|
+
onHandshakeData = (msg) => {
|
|
839
|
+
const parsedMsg = this.parseMsg(msg);
|
|
840
|
+
if (parsedMsg === null) {
|
|
841
|
+
this.listeners.onInvalidHandshake("could not parse message");
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
this.listeners.onHandshake(parsedMsg);
|
|
845
|
+
};
|
|
846
|
+
sendHandshake(msg) {
|
|
847
|
+
return this.conn.send(this.options.codec.toBuffer(msg));
|
|
848
|
+
}
|
|
849
|
+
_handleStateExit() {
|
|
850
|
+
super._handleStateExit();
|
|
851
|
+
this.conn.removeDataListener(this.onHandshakeData);
|
|
852
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
853
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
854
|
+
clearTimeout(this.handshakeTimeout);
|
|
855
|
+
}
|
|
856
|
+
_handleClose() {
|
|
857
|
+
super._handleClose();
|
|
858
|
+
this.conn.close();
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
// transport/sessionStateMachine/SessionConnected.ts
|
|
863
|
+
var import_api2 = require("@opentelemetry/api");
|
|
864
|
+
var SessionConnected = class extends IdentifiedSession {
|
|
865
|
+
state = "Connected" /* Connected */;
|
|
866
|
+
conn;
|
|
867
|
+
listeners;
|
|
868
|
+
heartbeatHandle;
|
|
869
|
+
heartbeatMisses = 0;
|
|
870
|
+
get isActivelyHeartbeating() {
|
|
871
|
+
return this.heartbeatHandle !== void 0;
|
|
872
|
+
}
|
|
873
|
+
updateBookkeeping(ack, seq) {
|
|
874
|
+
this.sendBuffer = this.sendBuffer.filter((unacked) => unacked.seq >= ack);
|
|
875
|
+
this.ack = seq + 1;
|
|
876
|
+
this.heartbeatMisses = 0;
|
|
877
|
+
}
|
|
878
|
+
send(msg) {
|
|
879
|
+
const constructedMsg = this.constructMsg(msg);
|
|
880
|
+
this.sendBuffer.push(constructedMsg);
|
|
881
|
+
this.conn.send(this.options.codec.toBuffer(constructedMsg));
|
|
882
|
+
return constructedMsg.id;
|
|
883
|
+
}
|
|
884
|
+
constructor(conn, listeners, ...args) {
|
|
885
|
+
super(...args);
|
|
886
|
+
this.conn = conn;
|
|
887
|
+
this.listeners = listeners;
|
|
888
|
+
this.conn.addDataListener(this.onMessageData);
|
|
889
|
+
this.conn.addCloseListener(listeners.onConnectionClosed);
|
|
890
|
+
this.conn.addErrorListener(listeners.onConnectionErrored);
|
|
891
|
+
if (this.sendBuffer.length > 0) {
|
|
892
|
+
this.log?.debug(
|
|
893
|
+
`sending ${this.sendBuffer.length} buffered messages`,
|
|
894
|
+
this.loggingMetadata
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
for (const msg of this.sendBuffer) {
|
|
898
|
+
conn.send(this.options.codec.toBuffer(msg));
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
startActiveHeartbeat() {
|
|
902
|
+
this.heartbeatHandle = setInterval(() => {
|
|
903
|
+
const misses = this.heartbeatMisses;
|
|
904
|
+
const missDuration = misses * this.options.heartbeatIntervalMs;
|
|
905
|
+
if (misses >= this.options.heartbeatsUntilDead) {
|
|
906
|
+
this.log?.info(
|
|
907
|
+
`closing connection to ${this.to} due to inactivity (missed ${misses} heartbeats which is ${missDuration}ms)`,
|
|
908
|
+
this.loggingMetadata
|
|
909
|
+
);
|
|
910
|
+
this.telemetry.span.addEvent("closing connection due to inactivity");
|
|
911
|
+
this.conn.close();
|
|
912
|
+
clearInterval(this.heartbeatHandle);
|
|
913
|
+
this.heartbeatHandle = void 0;
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
this.sendHeartbeat();
|
|
917
|
+
this.heartbeatMisses++;
|
|
918
|
+
}, this.options.heartbeatIntervalMs);
|
|
919
|
+
}
|
|
920
|
+
sendHeartbeat() {
|
|
921
|
+
this.send({
|
|
922
|
+
streamId: "heartbeat",
|
|
923
|
+
controlFlags: 1 /* AckBit */,
|
|
924
|
+
payload: {
|
|
925
|
+
type: "ACK"
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
onMessageData = (msg) => {
|
|
930
|
+
const parsedMsg = this.parseMsg(msg);
|
|
931
|
+
if (parsedMsg === null)
|
|
932
|
+
return;
|
|
933
|
+
if (parsedMsg.seq !== this.ack) {
|
|
934
|
+
if (parsedMsg.seq < this.ack) {
|
|
935
|
+
this.log?.debug(
|
|
936
|
+
`received duplicate msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack}), discarding`,
|
|
937
|
+
{
|
|
938
|
+
...this.loggingMetadata,
|
|
939
|
+
transportMessage: parsedMsg
|
|
940
|
+
}
|
|
941
|
+
);
|
|
942
|
+
} else {
|
|
943
|
+
const reason = `received out-of-order msg (got seq: ${parsedMsg.seq}, wanted seq: ${this.ack})`;
|
|
944
|
+
this.log?.error(reason, {
|
|
945
|
+
...this.loggingMetadata,
|
|
946
|
+
transportMessage: parsedMsg,
|
|
947
|
+
tags: ["invariant-violation"]
|
|
948
|
+
});
|
|
949
|
+
this.telemetry.span.setStatus({
|
|
950
|
+
code: import_api2.SpanStatusCode.ERROR,
|
|
951
|
+
message: reason
|
|
952
|
+
});
|
|
953
|
+
this.listeners.onInvalidMessage(reason);
|
|
954
|
+
}
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
this.log?.debug(`received msg`, {
|
|
958
|
+
...this.loggingMetadata,
|
|
959
|
+
transportMessage: parsedMsg
|
|
960
|
+
});
|
|
961
|
+
this.updateBookkeeping(parsedMsg.ack, parsedMsg.seq);
|
|
962
|
+
if (!isAck(parsedMsg.controlFlags)) {
|
|
963
|
+
this.listeners.onMessage(parsedMsg);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
this.log?.debug(`discarding msg (ack bit set)`, {
|
|
967
|
+
...this.loggingMetadata,
|
|
968
|
+
transportMessage: parsedMsg
|
|
969
|
+
});
|
|
970
|
+
if (!this.isActivelyHeartbeating) {
|
|
971
|
+
this.sendHeartbeat();
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
_handleStateExit() {
|
|
975
|
+
super._handleStateExit();
|
|
976
|
+
this.conn.removeDataListener(this.onMessageData);
|
|
977
|
+
this.conn.removeCloseListener(this.listeners.onConnectionClosed);
|
|
978
|
+
this.conn.removeErrorListener(this.listeners.onConnectionErrored);
|
|
979
|
+
clearInterval(this.heartbeatHandle);
|
|
980
|
+
this.heartbeatHandle = void 0;
|
|
981
|
+
}
|
|
982
|
+
_handleClose() {
|
|
983
|
+
super._handleClose();
|
|
984
|
+
this.conn.close();
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
// transport/sessionStateMachine/transitions.ts
|
|
989
|
+
function inheritSharedSession(session) {
|
|
990
|
+
return [
|
|
991
|
+
session.id,
|
|
992
|
+
session.from,
|
|
993
|
+
session.to,
|
|
994
|
+
session.seq,
|
|
995
|
+
session.ack,
|
|
996
|
+
session.sendBuffer,
|
|
997
|
+
session.telemetry,
|
|
998
|
+
session.options,
|
|
999
|
+
session.log
|
|
1000
|
+
];
|
|
1001
|
+
}
|
|
1002
|
+
var SessionStateGraph = {
|
|
1003
|
+
entrypoints: {
|
|
1004
|
+
NoConnection(to, from, listeners, options, log) {
|
|
1005
|
+
const id = `session-${generateId()}`;
|
|
1006
|
+
const telemetry = createSessionTelemetryInfo(id, to, from);
|
|
1007
|
+
const sendBuffer = [];
|
|
1008
|
+
const session = new SessionNoConnection(
|
|
1009
|
+
listeners,
|
|
1010
|
+
id,
|
|
1011
|
+
from,
|
|
1012
|
+
to,
|
|
1013
|
+
0,
|
|
1014
|
+
0,
|
|
1015
|
+
sendBuffer,
|
|
1016
|
+
telemetry,
|
|
1017
|
+
options,
|
|
1018
|
+
log
|
|
1019
|
+
);
|
|
1020
|
+
session.log?.info(`session ${session.id} created in NoConnection state`, {
|
|
1021
|
+
...session.loggingMetadata,
|
|
1022
|
+
tags: ["state-transition"]
|
|
1023
|
+
});
|
|
1024
|
+
return session;
|
|
1025
|
+
},
|
|
1026
|
+
WaitingForHandshake(from, conn, listeners, options, log) {
|
|
1027
|
+
const session = new SessionWaitingForHandshake(
|
|
1028
|
+
conn,
|
|
1029
|
+
listeners,
|
|
1030
|
+
from,
|
|
1031
|
+
options,
|
|
1032
|
+
log
|
|
1033
|
+
);
|
|
1034
|
+
session.log?.info(`session created in WaitingForHandshake state`, {
|
|
1035
|
+
...session.loggingMetadata,
|
|
1036
|
+
tags: ["state-transition"]
|
|
1037
|
+
});
|
|
1038
|
+
return session;
|
|
1039
|
+
}
|
|
1040
|
+
},
|
|
1041
|
+
// All of the transitions 'move'/'consume' the old session and return a new one.
|
|
1042
|
+
// After a session is transitioned, any usage of the old session will throw.
|
|
1043
|
+
transition: {
|
|
1044
|
+
// happy path transitions
|
|
1045
|
+
NoConnectionToConnecting(oldSession, connPromise, listeners) {
|
|
1046
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1047
|
+
oldSession._handleStateExit();
|
|
1048
|
+
const session = new SessionConnecting(
|
|
1049
|
+
connPromise,
|
|
1050
|
+
listeners,
|
|
1051
|
+
...carriedState
|
|
1052
|
+
);
|
|
1053
|
+
session.log?.info(
|
|
1054
|
+
`session ${session.id} transition from NoConnection to Connecting`,
|
|
1055
|
+
{
|
|
1056
|
+
...session.loggingMetadata,
|
|
1057
|
+
tags: ["state-transition"]
|
|
1058
|
+
}
|
|
1059
|
+
);
|
|
1060
|
+
return session;
|
|
1061
|
+
},
|
|
1062
|
+
ConnectingToHandshaking(oldSession, conn, listeners) {
|
|
1063
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1064
|
+
oldSession._handleStateExit();
|
|
1065
|
+
const session = new SessionHandshaking(conn, listeners, ...carriedState);
|
|
1066
|
+
session.log?.info(
|
|
1067
|
+
`session ${session.id} transition from Connecting to Handshaking`,
|
|
1068
|
+
{
|
|
1069
|
+
...session.loggingMetadata,
|
|
1070
|
+
tags: ["state-transition"]
|
|
1071
|
+
}
|
|
1072
|
+
);
|
|
1073
|
+
return session;
|
|
1074
|
+
},
|
|
1075
|
+
HandshakingToConnected(oldSession, listeners) {
|
|
1076
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1077
|
+
const conn = oldSession.conn;
|
|
1078
|
+
oldSession._handleStateExit();
|
|
1079
|
+
const session = new SessionConnected(conn, listeners, ...carriedState);
|
|
1080
|
+
session.log?.info(
|
|
1081
|
+
`session ${session.id} transition from Handshaking to Connected`,
|
|
1082
|
+
{
|
|
1083
|
+
...session.loggingMetadata,
|
|
1084
|
+
tags: ["state-transition"]
|
|
1085
|
+
}
|
|
1086
|
+
);
|
|
1087
|
+
return session;
|
|
1088
|
+
},
|
|
1089
|
+
WaitingForHandshakeToConnected(pendingSession, oldSession, sessionId, to, propagationCtx, listeners) {
|
|
1090
|
+
const conn = pendingSession.conn;
|
|
1091
|
+
const { from, options } = pendingSession;
|
|
1092
|
+
const carriedState = oldSession ? (
|
|
1093
|
+
// old session exists, inherit state
|
|
1094
|
+
inheritSharedSession(oldSession)
|
|
1095
|
+
) : (
|
|
1096
|
+
// old session does not exist, create new state
|
|
1097
|
+
[
|
|
1098
|
+
sessionId,
|
|
1099
|
+
from,
|
|
1100
|
+
to,
|
|
1101
|
+
0,
|
|
1102
|
+
0,
|
|
1103
|
+
[],
|
|
1104
|
+
createSessionTelemetryInfo(sessionId, to, from, propagationCtx),
|
|
1105
|
+
options,
|
|
1106
|
+
pendingSession.log
|
|
1107
|
+
]
|
|
1108
|
+
);
|
|
1109
|
+
pendingSession._handleStateExit();
|
|
1110
|
+
oldSession?._handleStateExit();
|
|
1111
|
+
const session = new SessionConnected(conn, listeners, ...carriedState);
|
|
1112
|
+
session.log?.info(
|
|
1113
|
+
`session ${session.id} transition from WaitingForHandshake to Connected`,
|
|
1114
|
+
{
|
|
1115
|
+
...session.loggingMetadata,
|
|
1116
|
+
tags: ["state-transition"]
|
|
1117
|
+
}
|
|
1118
|
+
);
|
|
1119
|
+
return session;
|
|
1120
|
+
},
|
|
1121
|
+
// disconnect paths
|
|
1122
|
+
ConnectingToNoConnection(oldSession, listeners) {
|
|
1123
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1124
|
+
oldSession.bestEffortClose();
|
|
1125
|
+
oldSession._handleStateExit();
|
|
1126
|
+
const session = new SessionNoConnection(listeners, ...carriedState);
|
|
1127
|
+
session.log?.info(
|
|
1128
|
+
`session ${session.id} transition from Connecting to NoConnection`,
|
|
1129
|
+
{
|
|
1130
|
+
...session.loggingMetadata,
|
|
1131
|
+
tags: ["state-transition"]
|
|
1132
|
+
}
|
|
1133
|
+
);
|
|
1134
|
+
return session;
|
|
1135
|
+
},
|
|
1136
|
+
HandshakingToNoConnection(oldSession, listeners) {
|
|
1137
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1138
|
+
oldSession.conn.close();
|
|
1139
|
+
oldSession._handleStateExit();
|
|
1140
|
+
const session = new SessionNoConnection(listeners, ...carriedState);
|
|
1141
|
+
session.log?.info(
|
|
1142
|
+
`session ${session.id} transition from Handshaking to NoConnection`,
|
|
1143
|
+
{
|
|
1144
|
+
...session.loggingMetadata,
|
|
1145
|
+
tags: ["state-transition"]
|
|
1146
|
+
}
|
|
1147
|
+
);
|
|
1148
|
+
return session;
|
|
1149
|
+
},
|
|
1150
|
+
ConnectedToNoConnection(oldSession, listeners) {
|
|
1151
|
+
const carriedState = inheritSharedSession(oldSession);
|
|
1152
|
+
oldSession.conn.close();
|
|
1153
|
+
oldSession._handleStateExit();
|
|
1154
|
+
const session = new SessionNoConnection(listeners, ...carriedState);
|
|
1155
|
+
session.log?.info(
|
|
1156
|
+
`session ${session.id} transition from Connected to NoConnection`,
|
|
1157
|
+
{
|
|
1158
|
+
...session.loggingMetadata,
|
|
1159
|
+
tags: ["state-transition"]
|
|
1160
|
+
}
|
|
1161
|
+
);
|
|
1162
|
+
return session;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
|
|
755
1167
|
// util/testHelpers.ts
|
|
756
1168
|
function createLocalWebSocketClient(port) {
|
|
757
1169
|
const sock = new import_ws.default(`ws://localhost:${port}`);
|
|
@@ -817,10 +1229,13 @@ function catchProcError(err) {
|
|
|
817
1229
|
}
|
|
818
1230
|
var testingSessionOptions = defaultTransportOptions;
|
|
819
1231
|
function dummySession() {
|
|
820
|
-
return
|
|
821
|
-
void 0,
|
|
1232
|
+
return SessionStateGraph.entrypoints.NoConnection(
|
|
822
1233
|
"client",
|
|
823
1234
|
"server",
|
|
1235
|
+
{
|
|
1236
|
+
onSessionGracePeriodElapsed: () => {
|
|
1237
|
+
}
|
|
1238
|
+
},
|
|
824
1239
|
testingSessionOptions
|
|
825
1240
|
);
|
|
826
1241
|
}
|
|
@@ -830,8 +1245,7 @@ function dummyCtx(state, session, extendedContext) {
|
|
|
830
1245
|
state,
|
|
831
1246
|
to: session.to,
|
|
832
1247
|
from: session.from,
|
|
833
|
-
streamId: (
|
|
834
|
-
session,
|
|
1248
|
+
streamId: generateId(),
|
|
835
1249
|
metadata: {}
|
|
836
1250
|
};
|
|
837
1251
|
}
|
|
@@ -880,20 +1294,40 @@ function asClientUpload(state, proc, init, extendedContext, session = dummySessi
|
|
|
880
1294
|
}
|
|
881
1295
|
}
|
|
882
1296
|
var getUnixSocketPath = () => {
|
|
883
|
-
return
|
|
1297
|
+
return `/tmp/${(0, import_nanoid2.nanoid)()}.sock`;
|
|
884
1298
|
};
|
|
1299
|
+
function getTransportConnections(transport) {
|
|
1300
|
+
const connections = [];
|
|
1301
|
+
for (const session of transport.sessions.values()) {
|
|
1302
|
+
if (session.state === "Connected" /* Connected */) {
|
|
1303
|
+
connections.push(session.conn);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
return connections;
|
|
1307
|
+
}
|
|
1308
|
+
function numberOfConnections(transport) {
|
|
1309
|
+
return getTransportConnections(transport).length;
|
|
1310
|
+
}
|
|
1311
|
+
function closeAllConnections(transport) {
|
|
1312
|
+
for (const conn of getTransportConnections(transport)) {
|
|
1313
|
+
conn.close();
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
885
1316
|
// Annotate the CommonJS export names for ESM import in node:
|
|
886
1317
|
0 && (module.exports = {
|
|
887
1318
|
asClientRpc,
|
|
888
1319
|
asClientStream,
|
|
889
1320
|
asClientSubscription,
|
|
890
1321
|
asClientUpload,
|
|
1322
|
+
closeAllConnections,
|
|
891
1323
|
createDummyTransportMessage,
|
|
892
1324
|
createLocalWebSocketClient,
|
|
893
1325
|
createWebSocketServer,
|
|
894
1326
|
dummySession,
|
|
1327
|
+
getTransportConnections,
|
|
895
1328
|
getUnixSocketPath,
|
|
896
1329
|
iterNext,
|
|
1330
|
+
numberOfConnections,
|
|
897
1331
|
onUdsServeReady,
|
|
898
1332
|
onWsServerReady,
|
|
899
1333
|
payloadToTransportMessage,
|