@rivetkit/engine-runner 25.8.0 → 25.8.3
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/package.json +2 -2
- package/src/log.ts +2 -2
- package/src/mod.ts +172 -62
- package/src/tunnel.ts +15 -8
- package/src/utils.ts +1 -1
- package/src/websocket-tunnel-adapter.ts +93 -41
- package/tsconfig.json +2 -4
- package/tsup.config.ts +1 -1
- package/turbo.json +2 -2
- package/vitest.config.ts +2 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rivetkit/engine-runner",
|
|
3
|
-
"version": "25.8.
|
|
3
|
+
"version": "25.8.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"import": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"uuid": "^12.0.0",
|
|
17
17
|
"pino": "^9.9.5",
|
|
18
18
|
"ws": "^8.18.3",
|
|
19
|
-
"@rivetkit/engine-runner-protocol": "25.8.
|
|
19
|
+
"@rivetkit/engine-runner-protocol": "25.8.3"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^22.18.1",
|
package/src/log.ts
CHANGED
package/src/mod.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import type WebSocket from "ws";
|
|
2
|
-
import { importWebSocket } from "./websocket.js";
|
|
3
1
|
import * as protocol from "@rivetkit/engine-runner-protocol";
|
|
4
|
-
import {
|
|
2
|
+
import type { Logger } from "pino";
|
|
3
|
+
import type WebSocket from "ws";
|
|
4
|
+
import { logger, setLogger } from "./log.js";
|
|
5
5
|
import { Tunnel } from "./tunnel";
|
|
6
|
+
import { calculateBackoff, unreachable } from "./utils";
|
|
7
|
+
import { importWebSocket } from "./websocket.js";
|
|
6
8
|
import type { WebSocketTunnelAdapter } from "./websocket-tunnel-adapter";
|
|
7
|
-
import type { Logger } from "pino";
|
|
8
|
-
import { setLogger, logger } from "./log.js";
|
|
9
9
|
|
|
10
10
|
const KV_EXPIRE: number = 30_000;
|
|
11
11
|
const PROTOCOL_VERSION: number = 1;
|
|
12
12
|
|
|
13
13
|
/** Warn once the backlog significantly exceeds the server's ack batch size. */
|
|
14
14
|
const EVENT_BACKLOG_WARN_THRESHOLD = 10_000;
|
|
15
|
+
const SIGNAL_HANDLERS: (() => void)[] = [];
|
|
15
16
|
|
|
16
17
|
export interface ActorInstance {
|
|
17
18
|
actorId: string;
|
|
@@ -44,8 +45,17 @@ export interface RunnerConfig {
|
|
|
44
45
|
onConnected: () => void;
|
|
45
46
|
onDisconnected: () => void;
|
|
46
47
|
onShutdown: () => void;
|
|
47
|
-
fetch: (
|
|
48
|
-
|
|
48
|
+
fetch: (
|
|
49
|
+
runner: Runner,
|
|
50
|
+
actorId: string,
|
|
51
|
+
request: Request,
|
|
52
|
+
) => Promise<Response>;
|
|
53
|
+
websocket?: (
|
|
54
|
+
runner: Runner,
|
|
55
|
+
actorId: string,
|
|
56
|
+
ws: any,
|
|
57
|
+
request: Request,
|
|
58
|
+
) => Promise<void>;
|
|
49
59
|
onActorStart: (
|
|
50
60
|
actorId: string,
|
|
51
61
|
generation: number,
|
|
@@ -107,14 +117,12 @@ export class Runner {
|
|
|
107
117
|
#kvCleanupInterval?: NodeJS.Timeout;
|
|
108
118
|
|
|
109
119
|
// Tunnel for HTTP/WebSocket forwarding
|
|
110
|
-
#tunnel: Tunnel;
|
|
120
|
+
#tunnel: Tunnel | undefined;
|
|
111
121
|
|
|
112
122
|
constructor(config: RunnerConfig) {
|
|
113
123
|
this.#config = config;
|
|
114
124
|
if (this.#config.logger) setLogger(this.#config.logger);
|
|
115
125
|
|
|
116
|
-
this.#tunnel = new Tunnel(this);
|
|
117
|
-
|
|
118
126
|
// Start cleaning up old unsent KV requests every 15 seconds
|
|
119
127
|
this.#kvCleanupInterval = setInterval(() => {
|
|
120
128
|
this.#cleanupOldKvRequests();
|
|
@@ -134,11 +142,21 @@ export class Runner {
|
|
|
134
142
|
}
|
|
135
143
|
|
|
136
144
|
async stopActor(actorId: string, generation?: number) {
|
|
145
|
+
const actor = this.getActor(actorId, generation);
|
|
146
|
+
if (!actor) return;
|
|
147
|
+
|
|
148
|
+
this.#sendActorIntent(actorId, actor.generation, "stop");
|
|
149
|
+
|
|
150
|
+
// NOTE: We do NOT remove the actor from this.#actors here
|
|
151
|
+
// The server will send a StopActor command if it wants to fully stop
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async forceStopActor(actorId: string, generation?: number) {
|
|
137
155
|
const actor = this.#removeActor(actorId, generation);
|
|
138
156
|
if (!actor) return;
|
|
139
157
|
|
|
140
158
|
// Unregister actor from tunnel
|
|
141
|
-
this.#tunnel
|
|
159
|
+
this.#tunnel?.unregisterActor(actor);
|
|
142
160
|
|
|
143
161
|
// If onActorStop times out, Pegboard will handle this timeout with ACTOR_STOP_THRESHOLD_DURATION_MS
|
|
144
162
|
try {
|
|
@@ -152,6 +170,7 @@ export class Runner {
|
|
|
152
170
|
this.#config.onActorStop(actorId, actor.generation).catch((err) => {
|
|
153
171
|
logger()?.error({
|
|
154
172
|
msg: "error in onactorstop for actor",
|
|
173
|
+
runnerId: this.runnerId,
|
|
155
174
|
actorId,
|
|
156
175
|
err,
|
|
157
176
|
});
|
|
@@ -159,25 +178,31 @@ export class Runner {
|
|
|
159
178
|
}
|
|
160
179
|
|
|
161
180
|
#stopAllActors() {
|
|
162
|
-
logger()?.info(
|
|
163
|
-
"stopping all actors due to runner lost threshold exceeded",
|
|
164
|
-
|
|
181
|
+
logger()?.info({
|
|
182
|
+
msg: "stopping all actors due to runner lost threshold exceeded",
|
|
183
|
+
runnerId: this.runnerId,
|
|
184
|
+
});
|
|
165
185
|
|
|
166
186
|
const actorIds = Array.from(this.#actors.keys());
|
|
167
187
|
for (const actorId of actorIds) {
|
|
168
|
-
this.
|
|
188
|
+
this.forceStopActor(actorId);
|
|
169
189
|
}
|
|
170
190
|
}
|
|
171
191
|
|
|
172
192
|
getActor(actorId: string, generation?: number): ActorInstance | undefined {
|
|
173
193
|
const actor = this.#actors.get(actorId);
|
|
174
194
|
if (!actor) {
|
|
175
|
-
logger()?.error({
|
|
195
|
+
logger()?.error({
|
|
196
|
+
msg: "actor not found",
|
|
197
|
+
runnerId: this.runnerId,
|
|
198
|
+
actorId,
|
|
199
|
+
});
|
|
176
200
|
return undefined;
|
|
177
201
|
}
|
|
178
202
|
if (generation !== undefined && actor.generation !== generation) {
|
|
179
203
|
logger()?.error({
|
|
180
204
|
msg: "actor generation mismatch",
|
|
205
|
+
runnerId: this.runnerId,
|
|
181
206
|
actorId,
|
|
182
207
|
generation,
|
|
183
208
|
});
|
|
@@ -202,12 +227,17 @@ export class Runner {
|
|
|
202
227
|
): ActorInstance | undefined {
|
|
203
228
|
const actor = this.#actors.get(actorId);
|
|
204
229
|
if (!actor) {
|
|
205
|
-
logger()?.error({
|
|
230
|
+
logger()?.error({
|
|
231
|
+
msg: "actor not found for removal",
|
|
232
|
+
runnerId: this.runnerId,
|
|
233
|
+
actorId,
|
|
234
|
+
});
|
|
206
235
|
return undefined;
|
|
207
236
|
}
|
|
208
237
|
if (generation !== undefined && actor.generation !== generation) {
|
|
209
238
|
logger()?.error({
|
|
210
239
|
msg: "actor generation mismatch",
|
|
240
|
+
runnerId: this.runnerId,
|
|
211
241
|
actorId,
|
|
212
242
|
generation,
|
|
213
243
|
});
|
|
@@ -225,6 +255,7 @@ export class Runner {
|
|
|
225
255
|
} catch (err) {
|
|
226
256
|
logger()?.error({
|
|
227
257
|
msg: "error closing websocket for actor",
|
|
258
|
+
runnerId: this.runnerId,
|
|
228
259
|
actorId,
|
|
229
260
|
err,
|
|
230
261
|
});
|
|
@@ -241,8 +272,9 @@ export class Runner {
|
|
|
241
272
|
if (this.#started) throw new Error("Cannot call runner.start twice");
|
|
242
273
|
this.#started = true;
|
|
243
274
|
|
|
244
|
-
logger()?.info("starting runner");
|
|
275
|
+
logger()?.info({ msg: "starting runner" });
|
|
245
276
|
|
|
277
|
+
this.#tunnel = new Tunnel(this);
|
|
246
278
|
this.#tunnel.start();
|
|
247
279
|
|
|
248
280
|
try {
|
|
@@ -253,14 +285,46 @@ export class Runner {
|
|
|
253
285
|
}
|
|
254
286
|
|
|
255
287
|
if (!this.#config.noAutoShutdown) {
|
|
256
|
-
|
|
257
|
-
|
|
288
|
+
if (!SIGNAL_HANDLERS.length) {
|
|
289
|
+
process.on("SIGTERM", () => {
|
|
290
|
+
logger()?.debug("received SIGTERM");
|
|
291
|
+
|
|
292
|
+
for (const handler of SIGNAL_HANDLERS) {
|
|
293
|
+
handler();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
process.exit(0);
|
|
297
|
+
});
|
|
298
|
+
process.on("SIGINT", () => {
|
|
299
|
+
logger()?.debug("received SIGINT");
|
|
300
|
+
|
|
301
|
+
for (const handler of SIGNAL_HANDLERS) {
|
|
302
|
+
handler();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
process.exit(0);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
logger()?.debug({
|
|
309
|
+
msg: "added SIGTERM listeners",
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
SIGNAL_HANDLERS.push(() => {
|
|
314
|
+
const weak = new WeakRef(this);
|
|
315
|
+
weak.deref()?.shutdown(false, false);
|
|
316
|
+
});
|
|
258
317
|
}
|
|
259
318
|
}
|
|
260
319
|
|
|
261
320
|
// MARK: Shutdown
|
|
262
321
|
async shutdown(immediate: boolean, exit: boolean = false) {
|
|
263
|
-
logger()?.info({
|
|
322
|
+
logger()?.info({
|
|
323
|
+
msg: "starting shutdown",
|
|
324
|
+
runnerId: this.runnerId,
|
|
325
|
+
immediate,
|
|
326
|
+
exit,
|
|
327
|
+
});
|
|
264
328
|
this.#shutdown = true;
|
|
265
329
|
|
|
266
330
|
// Clear reconnect timeout
|
|
@@ -315,6 +379,7 @@ export class Runner {
|
|
|
315
379
|
try {
|
|
316
380
|
logger()?.info({
|
|
317
381
|
msg: "sending stopping message",
|
|
382
|
+
runnerId: this.runnerId,
|
|
318
383
|
readyState: pegboardWebSocket.readyState,
|
|
319
384
|
});
|
|
320
385
|
|
|
@@ -342,6 +407,7 @@ export class Runner {
|
|
|
342
407
|
pegboardWebSocket.addEventListener("close", (ev) => {
|
|
343
408
|
logger()?.info({
|
|
344
409
|
msg: "connection closed",
|
|
410
|
+
runnerId: this.runnerId,
|
|
345
411
|
code: ev.code,
|
|
346
412
|
reason: ev.reason.toString(),
|
|
347
413
|
});
|
|
@@ -351,28 +417,39 @@ export class Runner {
|
|
|
351
417
|
|
|
352
418
|
// TODO: Wait for all actors to stop before closing ws
|
|
353
419
|
|
|
354
|
-
logger()?.info(
|
|
420
|
+
logger()?.info({
|
|
421
|
+
msg: "closing WebSocket",
|
|
422
|
+
runnerId: this.runnerId,
|
|
423
|
+
});
|
|
355
424
|
pegboardWebSocket.close(1000, "Stopping");
|
|
356
425
|
|
|
357
426
|
await closePromise;
|
|
358
427
|
|
|
359
|
-
logger()?.info(
|
|
428
|
+
logger()?.info({
|
|
429
|
+
msg: "websocket shutdown completed",
|
|
430
|
+
runnerId: this.runnerId,
|
|
431
|
+
});
|
|
360
432
|
} catch (error) {
|
|
361
433
|
logger()?.error({
|
|
362
434
|
msg: "error during websocket shutdown:",
|
|
435
|
+
runnerId: this.runnerId,
|
|
363
436
|
error,
|
|
364
437
|
});
|
|
365
438
|
pegboardWebSocket.close();
|
|
366
439
|
}
|
|
367
440
|
}
|
|
368
441
|
} else {
|
|
369
|
-
logger()?.warn(
|
|
442
|
+
logger()?.warn({
|
|
443
|
+
msg: "no runner WebSocket to shutdown or already closed",
|
|
444
|
+
runnerId: this.runnerId,
|
|
445
|
+
readyState: this.#pegboardWebSocket?.readyState,
|
|
446
|
+
});
|
|
370
447
|
}
|
|
371
448
|
|
|
372
449
|
// Close tunnel
|
|
373
450
|
if (this.#tunnel) {
|
|
374
451
|
this.#tunnel.shutdown();
|
|
375
|
-
|
|
452
|
+
this.#tunnel = undefined;
|
|
376
453
|
}
|
|
377
454
|
|
|
378
455
|
if (exit) process.exit(0);
|
|
@@ -400,7 +477,7 @@ export class Runner {
|
|
|
400
477
|
this.#pegboardWebSocket = ws;
|
|
401
478
|
|
|
402
479
|
ws.addEventListener("open", () => {
|
|
403
|
-
logger()?.info("Connected");
|
|
480
|
+
logger()?.info({ msg: "Connected" });
|
|
404
481
|
|
|
405
482
|
// Reset reconnect attempt counter on successful connection
|
|
406
483
|
this.#reconnectAttempt = 0;
|
|
@@ -457,7 +534,10 @@ export class Runner {
|
|
|
457
534
|
});
|
|
458
535
|
} else {
|
|
459
536
|
clearInterval(pingLoop);
|
|
460
|
-
logger()?.info(
|
|
537
|
+
logger()?.info({
|
|
538
|
+
msg: "WebSocket not open, stopping ping loop",
|
|
539
|
+
runnerId: this.runnerId,
|
|
540
|
+
});
|
|
461
541
|
}
|
|
462
542
|
}, pingInterval);
|
|
463
543
|
this.#pingLoop = pingLoop;
|
|
@@ -469,7 +549,10 @@ export class Runner {
|
|
|
469
549
|
this.#sendCommandAcknowledgment();
|
|
470
550
|
} else {
|
|
471
551
|
clearInterval(ackLoop);
|
|
472
|
-
logger()?.info(
|
|
552
|
+
logger()?.info({
|
|
553
|
+
msg: "WebSocket not open, stopping ack loop",
|
|
554
|
+
runnerId: this.runnerId,
|
|
555
|
+
});
|
|
473
556
|
}
|
|
474
557
|
}, ackInterval);
|
|
475
558
|
this.#ackInterval = ackLoop;
|
|
@@ -492,7 +575,7 @@ export class Runner {
|
|
|
492
575
|
if (message.tag === "ToClientInit") {
|
|
493
576
|
const init = message.val;
|
|
494
577
|
|
|
495
|
-
if (this.runnerId
|
|
578
|
+
if (this.runnerId !== init.runnerId) {
|
|
496
579
|
this.runnerId = init.runnerId;
|
|
497
580
|
|
|
498
581
|
// Clear history if runner id changed
|
|
@@ -526,7 +609,7 @@ export class Runner {
|
|
|
526
609
|
} else if (message.tag === "ToClientTunnelMessage") {
|
|
527
610
|
this.#tunnel?.handleTunnelMessage(message.val);
|
|
528
611
|
} else if (message.tag === "ToClientClose") {
|
|
529
|
-
this.#tunnel
|
|
612
|
+
this.#tunnel?.shutdown();
|
|
530
613
|
ws.close(1000, "manual closure");
|
|
531
614
|
} else {
|
|
532
615
|
unreachable(message);
|
|
@@ -534,7 +617,10 @@ export class Runner {
|
|
|
534
617
|
});
|
|
535
618
|
|
|
536
619
|
ws.addEventListener("error", (ev) => {
|
|
537
|
-
logger()?.error(
|
|
620
|
+
logger()?.error({
|
|
621
|
+
msg: `WebSocket error: ${ev.error}`,
|
|
622
|
+
runnerId: this.runnerId,
|
|
623
|
+
});
|
|
538
624
|
|
|
539
625
|
if (!this.#shutdown) {
|
|
540
626
|
// Start runner lost timeout if we have a threshold and are not shutting down
|
|
@@ -545,6 +631,7 @@ export class Runner {
|
|
|
545
631
|
) {
|
|
546
632
|
logger()?.info({
|
|
547
633
|
msg: "starting runner lost timeout",
|
|
634
|
+
runnerId: this.runnerId,
|
|
548
635
|
seconds: this.#runnerLostThreshold / 1000,
|
|
549
636
|
});
|
|
550
637
|
this.#runnerLostTimeout = setTimeout(() => {
|
|
@@ -560,14 +647,18 @@ export class Runner {
|
|
|
560
647
|
ws.addEventListener("close", async (ev) => {
|
|
561
648
|
logger()?.info({
|
|
562
649
|
msg: "connection closed",
|
|
650
|
+
runnerId: this.runnerId,
|
|
563
651
|
code: ev.code,
|
|
564
652
|
reason: ev.reason.toString(),
|
|
565
653
|
});
|
|
566
654
|
|
|
567
655
|
this.#config.onDisconnected();
|
|
568
656
|
|
|
569
|
-
if (ev.reason.toString()
|
|
570
|
-
logger()?.info(
|
|
657
|
+
if (ev.reason.toString().startsWith("ws.eviction")) {
|
|
658
|
+
logger()?.info({
|
|
659
|
+
msg: "runner evicted",
|
|
660
|
+
runnerId: this.runnerId,
|
|
661
|
+
});
|
|
571
662
|
|
|
572
663
|
await this.shutdown(true);
|
|
573
664
|
}
|
|
@@ -593,6 +684,7 @@ export class Runner {
|
|
|
593
684
|
) {
|
|
594
685
|
logger()?.info({
|
|
595
686
|
msg: "starting runner lost timeout",
|
|
687
|
+
runnerId: this.runnerId,
|
|
596
688
|
seconds: this.#runnerLostThreshold / 1000,
|
|
597
689
|
});
|
|
598
690
|
this.#runnerLostTimeout = setTimeout(() => {
|
|
@@ -609,11 +701,16 @@ export class Runner {
|
|
|
609
701
|
#handleCommands(commands: protocol.ToClientCommands) {
|
|
610
702
|
logger()?.info({
|
|
611
703
|
msg: "received commands",
|
|
704
|
+
runnerId: this.runnerId,
|
|
612
705
|
commandCount: commands.length,
|
|
613
706
|
});
|
|
614
707
|
|
|
615
708
|
for (const commandWrapper of commands) {
|
|
616
|
-
logger()?.info({
|
|
709
|
+
logger()?.info({
|
|
710
|
+
msg: "received command",
|
|
711
|
+
runnerId: this.runnerId,
|
|
712
|
+
commandWrapper,
|
|
713
|
+
});
|
|
617
714
|
if (commandWrapper.inner.tag === "CommandStartActor") {
|
|
618
715
|
this.#handleCommandStartActor(commandWrapper);
|
|
619
716
|
} else if (commandWrapper.inner.tag === "CommandStopActor") {
|
|
@@ -638,6 +735,7 @@ export class Runner {
|
|
|
638
735
|
if (prunedCount > 0) {
|
|
639
736
|
logger()?.info({
|
|
640
737
|
msg: "pruned acknowledged events",
|
|
738
|
+
runnerId: this.runnerId,
|
|
641
739
|
lastAckedIdx: lastAckedIdx.toString(),
|
|
642
740
|
prunedCount,
|
|
643
741
|
});
|
|
@@ -659,6 +757,7 @@ export class Runner {
|
|
|
659
757
|
this.#eventBacklogWarned = true;
|
|
660
758
|
logger()?.warn({
|
|
661
759
|
msg: "unacknowledged event backlog exceeds threshold",
|
|
760
|
+
runnerId: this.runnerId,
|
|
662
761
|
backlogSize: this.#eventHistory.length,
|
|
663
762
|
threshold: EVENT_BACKLOG_WARN_THRESHOLD,
|
|
664
763
|
});
|
|
@@ -699,13 +798,14 @@ export class Runner {
|
|
|
699
798
|
.catch((err) => {
|
|
700
799
|
logger()?.error({
|
|
701
800
|
msg: "error in onactorstart for actor",
|
|
801
|
+
runnerId: this.runnerId,
|
|
702
802
|
actorId,
|
|
703
803
|
err,
|
|
704
804
|
});
|
|
705
805
|
|
|
706
806
|
// TODO: Mark as crashed
|
|
707
807
|
// Send stopped state update if start failed
|
|
708
|
-
this.
|
|
808
|
+
this.forceStopActor(actorId, generation);
|
|
709
809
|
});
|
|
710
810
|
}
|
|
711
811
|
|
|
@@ -716,7 +816,7 @@ export class Runner {
|
|
|
716
816
|
const actorId = stopCommand.actorId;
|
|
717
817
|
const generation = stopCommand.generation;
|
|
718
818
|
|
|
719
|
-
this.
|
|
819
|
+
this.forceStopActor(actorId, generation);
|
|
720
820
|
}
|
|
721
821
|
|
|
722
822
|
#sendActorIntent(
|
|
@@ -725,7 +825,10 @@ export class Runner {
|
|
|
725
825
|
intentType: "sleep" | "stop",
|
|
726
826
|
) {
|
|
727
827
|
if (this.#shutdown) {
|
|
728
|
-
logger()?.warn(
|
|
828
|
+
logger()?.warn({
|
|
829
|
+
msg: "Runner is shut down, cannot send actor intent",
|
|
830
|
+
runnerId: this.runnerId,
|
|
831
|
+
});
|
|
729
832
|
return;
|
|
730
833
|
}
|
|
731
834
|
let actorIntent: protocol.ActorIntent;
|
|
@@ -760,6 +863,7 @@ export class Runner {
|
|
|
760
863
|
|
|
761
864
|
logger()?.info({
|
|
762
865
|
msg: "sending event to server",
|
|
866
|
+
runnerId: this.runnerId,
|
|
763
867
|
index: eventWrapper.index,
|
|
764
868
|
tag: eventWrapper.inner.tag,
|
|
765
869
|
val: eventWrapper.inner.val,
|
|
@@ -777,9 +881,10 @@ export class Runner {
|
|
|
777
881
|
stateType: "running" | "stopped",
|
|
778
882
|
) {
|
|
779
883
|
if (this.#shutdown) {
|
|
780
|
-
logger()?.warn(
|
|
781
|
-
"Runner is shut down, cannot send actor state update",
|
|
782
|
-
|
|
884
|
+
logger()?.warn({
|
|
885
|
+
msg: "Runner is shut down, cannot send actor state update",
|
|
886
|
+
runnerId: this.runnerId,
|
|
887
|
+
});
|
|
783
888
|
return;
|
|
784
889
|
}
|
|
785
890
|
let actorState: protocol.ActorState;
|
|
@@ -817,6 +922,7 @@ export class Runner {
|
|
|
817
922
|
|
|
818
923
|
logger()?.info({
|
|
819
924
|
msg: "sending event to server",
|
|
925
|
+
runnerId: this.runnerId,
|
|
820
926
|
index: eventWrapper.index,
|
|
821
927
|
tag: eventWrapper.inner.tag,
|
|
822
928
|
val: eventWrapper.inner.val,
|
|
@@ -830,9 +936,10 @@ export class Runner {
|
|
|
830
936
|
|
|
831
937
|
#sendCommandAcknowledgment() {
|
|
832
938
|
if (this.#shutdown) {
|
|
833
|
-
logger()?.warn(
|
|
834
|
-
"Runner is shut down, cannot send command acknowledgment",
|
|
835
|
-
|
|
939
|
+
logger()?.warn({
|
|
940
|
+
msg: "Runner is shut down, cannot send command acknowledgment",
|
|
941
|
+
runnerId: this.runnerId,
|
|
942
|
+
});
|
|
836
943
|
return;
|
|
837
944
|
}
|
|
838
945
|
|
|
@@ -857,11 +964,7 @@ export class Runner {
|
|
|
857
964
|
|
|
858
965
|
if (!request) {
|
|
859
966
|
const msg = "received kv response for unknown request id";
|
|
860
|
-
|
|
861
|
-
logger()?.error({ msg, requestId });
|
|
862
|
-
} else {
|
|
863
|
-
logger()?.error({ msg, requestId });
|
|
864
|
-
}
|
|
967
|
+
logger()?.error({ msg, runnerId: this.runnerId, requestId });
|
|
865
968
|
return;
|
|
866
969
|
}
|
|
867
970
|
|
|
@@ -1266,9 +1369,10 @@ export class Runner {
|
|
|
1266
1369
|
|
|
1267
1370
|
__sendToServer(message: protocol.ToServer) {
|
|
1268
1371
|
if (this.#shutdown) {
|
|
1269
|
-
logger()?.warn(
|
|
1270
|
-
"Runner is shut down, cannot send message to server",
|
|
1271
|
-
|
|
1372
|
+
logger()?.warn({
|
|
1373
|
+
msg: "Runner is shut down, cannot send message to server",
|
|
1374
|
+
runnerId: this.runnerId,
|
|
1375
|
+
});
|
|
1272
1376
|
return;
|
|
1273
1377
|
}
|
|
1274
1378
|
|
|
@@ -1279,16 +1383,17 @@ export class Runner {
|
|
|
1279
1383
|
) {
|
|
1280
1384
|
this.#pegboardWebSocket.send(encoded);
|
|
1281
1385
|
} else {
|
|
1282
|
-
logger()?.error(
|
|
1283
|
-
"WebSocket not available or not open for sending data",
|
|
1284
|
-
|
|
1386
|
+
logger()?.error({
|
|
1387
|
+
msg: "WebSocket not available or not open for sending data",
|
|
1388
|
+
runnerId: this.runnerId,
|
|
1389
|
+
});
|
|
1285
1390
|
}
|
|
1286
1391
|
}
|
|
1287
1392
|
|
|
1288
1393
|
getServerlessInitPacket(): string | undefined {
|
|
1289
1394
|
if (!this.runnerId) return undefined;
|
|
1290
1395
|
|
|
1291
|
-
|
|
1396
|
+
const data = protocol.encodeToServerlessServer({
|
|
1292
1397
|
tag: "ToServerlessServerInit",
|
|
1293
1398
|
val: {
|
|
1294
1399
|
runnerId: this.runnerId,
|
|
@@ -1305,7 +1410,10 @@ export class Runner {
|
|
|
1305
1410
|
|
|
1306
1411
|
#scheduleReconnect() {
|
|
1307
1412
|
if (this.#shutdown) {
|
|
1308
|
-
logger()?.debug(
|
|
1413
|
+
logger()?.debug({
|
|
1414
|
+
msg: "Runner is shut down, not attempting reconnect",
|
|
1415
|
+
runnerId: this.runnerId,
|
|
1416
|
+
});
|
|
1309
1417
|
return;
|
|
1310
1418
|
}
|
|
1311
1419
|
|
|
@@ -1316,16 +1424,18 @@ export class Runner {
|
|
|
1316
1424
|
jitter: true,
|
|
1317
1425
|
});
|
|
1318
1426
|
|
|
1319
|
-
logger()?.debug(
|
|
1320
|
-
`Scheduling reconnect attempt ${this.#reconnectAttempt + 1} in ${delay}ms`,
|
|
1321
|
-
|
|
1427
|
+
logger()?.debug({
|
|
1428
|
+
msg: `Scheduling reconnect attempt ${this.#reconnectAttempt + 1} in ${delay}ms`,
|
|
1429
|
+
runnerId: this.runnerId,
|
|
1430
|
+
});
|
|
1322
1431
|
|
|
1323
1432
|
this.#reconnectTimeout = setTimeout(async () => {
|
|
1324
1433
|
if (!this.#shutdown) {
|
|
1325
1434
|
this.#reconnectAttempt++;
|
|
1326
|
-
logger()?.debug(
|
|
1327
|
-
`Attempting to reconnect (attempt ${this.#reconnectAttempt})...`,
|
|
1328
|
-
|
|
1435
|
+
logger()?.debug({
|
|
1436
|
+
msg: `Attempting to reconnect (attempt ${this.#reconnectAttempt})...`,
|
|
1437
|
+
runnerId: this.runnerId,
|
|
1438
|
+
});
|
|
1329
1439
|
await this.#openPegboardWebSocket();
|
|
1330
1440
|
}
|
|
1331
1441
|
}, delay);
|
package/src/tunnel.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import * as protocol from "@rivetkit/engine-runner-protocol";
|
|
2
|
-
import type {
|
|
3
|
-
import { WebSocketTunnelAdapter } from "./websocket-tunnel-adapter";
|
|
4
|
-
import type { Runner, ActorInstance } from "./mod";
|
|
1
|
+
import type * as protocol from "@rivetkit/engine-runner-protocol";
|
|
2
|
+
import type { MessageId, RequestId } from "@rivetkit/engine-runner-protocol";
|
|
5
3
|
import { v4 as uuidv4 } from "uuid";
|
|
6
4
|
import { logger } from "./log";
|
|
5
|
+
import type { ActorInstance, Runner } from "./mod";
|
|
7
6
|
import { unreachable } from "./utils";
|
|
7
|
+
import { WebSocketTunnelAdapter } from "./websocket-tunnel-adapter";
|
|
8
8
|
|
|
9
9
|
const GC_INTERVAL = 60000; // 60 seconds
|
|
10
10
|
const MESSAGE_ACK_TIMEOUT = 5000; // 5 seconds
|
|
@@ -48,8 +48,6 @@ export class Tunnel {
|
|
|
48
48
|
this.#gcInterval = undefined;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
// TODO: Should we use unregisterActor instead
|
|
52
|
-
|
|
53
51
|
// Reject all pending requests
|
|
54
52
|
for (const [_, request] of this.#actorPendingRequests) {
|
|
55
53
|
request.reject(new Error("Tunnel shutting down"));
|
|
@@ -212,7 +210,11 @@ export class Tunnel {
|
|
|
212
210
|
return new Response("Actor not found", { status: 404 });
|
|
213
211
|
}
|
|
214
212
|
|
|
215
|
-
const fetchHandler = this.#runner.config.fetch(
|
|
213
|
+
const fetchHandler = this.#runner.config.fetch(
|
|
214
|
+
this.#runner,
|
|
215
|
+
actorId,
|
|
216
|
+
request,
|
|
217
|
+
);
|
|
216
218
|
|
|
217
219
|
if (!fetchHandler) {
|
|
218
220
|
return new Response("Not Implemented", { status: 501 });
|
|
@@ -541,7 +543,12 @@ export class Tunnel {
|
|
|
541
543
|
});
|
|
542
544
|
|
|
543
545
|
// Call websocket handler
|
|
544
|
-
await websocketHandler(
|
|
546
|
+
await websocketHandler(
|
|
547
|
+
this.#runner,
|
|
548
|
+
open.actorId,
|
|
549
|
+
adapter,
|
|
550
|
+
request,
|
|
551
|
+
);
|
|
545
552
|
} catch (error) {
|
|
546
553
|
logger()?.error({ msg: "error handling websocket open", error });
|
|
547
554
|
// Send close on error
|
package/src/utils.ts
CHANGED
|
@@ -20,7 +20,7 @@ export function calculateBackoff(
|
|
|
20
20
|
jitter = true,
|
|
21
21
|
} = options;
|
|
22
22
|
|
|
23
|
-
let delay = Math.min(initialDelay *
|
|
23
|
+
let delay = Math.min(initialDelay * multiplier ** attempt, maxDelay);
|
|
24
24
|
|
|
25
25
|
if (jitter) {
|
|
26
26
|
// Add random jitter between 0% and 25% of the delay
|
|
@@ -18,7 +18,7 @@ export class WebSocketTunnelAdapter {
|
|
|
18
18
|
#url = "";
|
|
19
19
|
#sendCallback: (data: ArrayBuffer | string, isBinary: boolean) => void;
|
|
20
20
|
#closeCallback: (code?: number, reason?: string) => void;
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
// Event buffering for events fired before listeners are attached
|
|
23
23
|
#bufferedEvents: Array<{
|
|
24
24
|
type: string;
|
|
@@ -28,7 +28,7 @@ export class WebSocketTunnelAdapter {
|
|
|
28
28
|
constructor(
|
|
29
29
|
webSocketId: string,
|
|
30
30
|
sendCallback: (data: ArrayBuffer | string, isBinary: boolean) => void,
|
|
31
|
-
closeCallback: (code?: number, reason?: string) => void
|
|
31
|
+
closeCallback: (code?: number, reason?: string) => void,
|
|
32
32
|
) {
|
|
33
33
|
this.#webSocketId = webSocketId;
|
|
34
34
|
this.#sendCallback = sendCallback;
|
|
@@ -48,7 +48,11 @@ export class WebSocketTunnelAdapter {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
set binaryType(value: string) {
|
|
51
|
-
if (
|
|
51
|
+
if (
|
|
52
|
+
value === "nodebuffer" ||
|
|
53
|
+
value === "arraybuffer" ||
|
|
54
|
+
value === "blob"
|
|
55
|
+
) {
|
|
52
56
|
this.#binaryType = value;
|
|
53
57
|
}
|
|
54
58
|
}
|
|
@@ -114,7 +118,8 @@ export class WebSocketTunnelAdapter {
|
|
|
114
118
|
}
|
|
115
119
|
|
|
116
120
|
send(data: string | ArrayBuffer | ArrayBufferView | Blob | Buffer): void {
|
|
117
|
-
if (this.#readyState !== 1) {
|
|
121
|
+
if (this.#readyState !== 1) {
|
|
122
|
+
// OPEN
|
|
118
123
|
throw new Error("WebSocket is not open");
|
|
119
124
|
}
|
|
120
125
|
|
|
@@ -133,29 +138,43 @@ export class WebSocketTunnelAdapter {
|
|
|
133
138
|
// Check if it's a SharedArrayBuffer
|
|
134
139
|
if (view.buffer instanceof SharedArrayBuffer) {
|
|
135
140
|
// Copy SharedArrayBuffer to regular ArrayBuffer
|
|
136
|
-
const bytes = new Uint8Array(
|
|
137
|
-
|
|
141
|
+
const bytes = new Uint8Array(
|
|
142
|
+
view.buffer,
|
|
143
|
+
view.byteOffset,
|
|
144
|
+
view.byteLength,
|
|
145
|
+
);
|
|
146
|
+
messageData = bytes.buffer.slice(
|
|
147
|
+
bytes.byteOffset,
|
|
148
|
+
bytes.byteOffset + bytes.byteLength,
|
|
149
|
+
) as unknown as ArrayBuffer;
|
|
138
150
|
} else {
|
|
139
151
|
messageData = view.buffer.slice(
|
|
140
152
|
view.byteOffset,
|
|
141
|
-
view.byteOffset + view.byteLength
|
|
153
|
+
view.byteOffset + view.byteLength,
|
|
142
154
|
) as ArrayBuffer;
|
|
143
155
|
}
|
|
144
156
|
} else if (data instanceof Blob) {
|
|
145
157
|
throw new Error("Blob sending not implemented in tunnel adapter");
|
|
146
|
-
} else if (typeof Buffer !==
|
|
158
|
+
} else if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) {
|
|
147
159
|
isBinary = true;
|
|
148
160
|
// Convert Buffer to ArrayBuffer
|
|
149
161
|
const buf = data as Buffer;
|
|
150
162
|
// Check if it's a SharedArrayBuffer
|
|
151
163
|
if (buf.buffer instanceof SharedArrayBuffer) {
|
|
152
164
|
// Copy SharedArrayBuffer to regular ArrayBuffer
|
|
153
|
-
const bytes = new Uint8Array(
|
|
154
|
-
|
|
165
|
+
const bytes = new Uint8Array(
|
|
166
|
+
buf.buffer,
|
|
167
|
+
buf.byteOffset,
|
|
168
|
+
buf.byteLength,
|
|
169
|
+
);
|
|
170
|
+
messageData = bytes.buffer.slice(
|
|
171
|
+
bytes.byteOffset,
|
|
172
|
+
bytes.byteOffset + bytes.byteLength,
|
|
173
|
+
) as unknown as ArrayBuffer;
|
|
155
174
|
} else {
|
|
156
175
|
messageData = buf.buffer.slice(
|
|
157
176
|
buf.byteOffset,
|
|
158
|
-
buf.byteOffset + buf.byteLength
|
|
177
|
+
buf.byteOffset + buf.byteLength,
|
|
159
178
|
) as ArrayBuffer;
|
|
160
179
|
}
|
|
161
180
|
} else {
|
|
@@ -169,7 +188,7 @@ export class WebSocketTunnelAdapter {
|
|
|
169
188
|
close(code?: number, reason?: string): void {
|
|
170
189
|
if (
|
|
171
190
|
this.#readyState === 2 || // CLOSING
|
|
172
|
-
this.#readyState === 3
|
|
191
|
+
this.#readyState === 3 // CLOSED
|
|
173
192
|
) {
|
|
174
193
|
return;
|
|
175
194
|
}
|
|
@@ -181,7 +200,7 @@ export class WebSocketTunnelAdapter {
|
|
|
181
200
|
|
|
182
201
|
// Update state and fire event
|
|
183
202
|
this.#readyState = 3; // CLOSED
|
|
184
|
-
|
|
203
|
+
|
|
185
204
|
const closeEvent = {
|
|
186
205
|
wasClean: true,
|
|
187
206
|
code: code || 1000,
|
|
@@ -189,14 +208,14 @@ export class WebSocketTunnelAdapter {
|
|
|
189
208
|
type: "close",
|
|
190
209
|
target: this,
|
|
191
210
|
};
|
|
192
|
-
|
|
211
|
+
|
|
193
212
|
this.#fireEvent("close", closeEvent);
|
|
194
213
|
}
|
|
195
214
|
|
|
196
215
|
addEventListener(
|
|
197
216
|
type: string,
|
|
198
217
|
listener: (event: any) => void,
|
|
199
|
-
options?: boolean | any
|
|
218
|
+
options?: boolean | any,
|
|
200
219
|
): void {
|
|
201
220
|
if (typeof listener === "function") {
|
|
202
221
|
let listeners = this.#eventListeners.get(type);
|
|
@@ -214,7 +233,7 @@ export class WebSocketTunnelAdapter {
|
|
|
214
233
|
removeEventListener(
|
|
215
234
|
type: string,
|
|
216
235
|
listener: (event: any) => void,
|
|
217
|
-
options?: boolean | any
|
|
236
|
+
options?: boolean | any,
|
|
218
237
|
): void {
|
|
219
238
|
if (typeof listener === "function") {
|
|
220
239
|
const listeners = this.#eventListeners.get(type);
|
|
@@ -237,12 +256,16 @@ export class WebSocketTunnelAdapter {
|
|
|
237
256
|
if (listeners && listeners.size > 0) {
|
|
238
257
|
hasListeners = true;
|
|
239
258
|
for (const listener of listeners) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
259
|
+
try {
|
|
260
|
+
listener.call(this, event);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
logger()?.error({
|
|
263
|
+
msg: "error in websocket event listener",
|
|
264
|
+
error,
|
|
265
|
+
type,
|
|
266
|
+
});
|
|
245
267
|
}
|
|
268
|
+
}
|
|
246
269
|
}
|
|
247
270
|
|
|
248
271
|
// Call the onX property if set
|
|
@@ -253,7 +276,10 @@ export class WebSocketTunnelAdapter {
|
|
|
253
276
|
try {
|
|
254
277
|
this.#onopen.call(this, event);
|
|
255
278
|
} catch (error) {
|
|
256
|
-
logger()?.error({
|
|
279
|
+
logger()?.error({
|
|
280
|
+
msg: "error in onopen handler",
|
|
281
|
+
error,
|
|
282
|
+
});
|
|
257
283
|
}
|
|
258
284
|
}
|
|
259
285
|
break;
|
|
@@ -263,7 +289,10 @@ export class WebSocketTunnelAdapter {
|
|
|
263
289
|
try {
|
|
264
290
|
this.#onclose.call(this, event);
|
|
265
291
|
} catch (error) {
|
|
266
|
-
logger()?.error({
|
|
292
|
+
logger()?.error({
|
|
293
|
+
msg: "error in onclose handler",
|
|
294
|
+
error,
|
|
295
|
+
});
|
|
267
296
|
}
|
|
268
297
|
}
|
|
269
298
|
break;
|
|
@@ -273,7 +302,10 @@ export class WebSocketTunnelAdapter {
|
|
|
273
302
|
try {
|
|
274
303
|
this.#onerror.call(this, event);
|
|
275
304
|
} catch (error) {
|
|
276
|
-
logger()?.error({
|
|
305
|
+
logger()?.error({
|
|
306
|
+
msg: "error in onerror handler",
|
|
307
|
+
error,
|
|
308
|
+
});
|
|
277
309
|
}
|
|
278
310
|
}
|
|
279
311
|
break;
|
|
@@ -283,7 +315,10 @@ export class WebSocketTunnelAdapter {
|
|
|
283
315
|
try {
|
|
284
316
|
this.#onmessage.call(this, event);
|
|
285
317
|
} catch (error) {
|
|
286
|
-
logger()?.error({
|
|
318
|
+
logger()?.error({
|
|
319
|
+
msg: "error in onmessage handler",
|
|
320
|
+
error,
|
|
321
|
+
});
|
|
287
322
|
}
|
|
288
323
|
}
|
|
289
324
|
break;
|
|
@@ -297,10 +332,10 @@ export class WebSocketTunnelAdapter {
|
|
|
297
332
|
|
|
298
333
|
#flushBufferedEvents(type: string): void {
|
|
299
334
|
const eventsToFlush = this.#bufferedEvents.filter(
|
|
300
|
-
(buffered) => buffered.type === type
|
|
335
|
+
(buffered) => buffered.type === type,
|
|
301
336
|
);
|
|
302
337
|
this.#bufferedEvents = this.#bufferedEvents.filter(
|
|
303
|
-
(buffered) => buffered.type !== type
|
|
338
|
+
(buffered) => buffered.type !== type,
|
|
304
339
|
);
|
|
305
340
|
|
|
306
341
|
for (const { event } of eventsToFlush) {
|
|
@@ -327,7 +362,10 @@ export class WebSocketTunnelAdapter {
|
|
|
327
362
|
try {
|
|
328
363
|
this.#onopen.call(this, event);
|
|
329
364
|
} catch (error) {
|
|
330
|
-
logger()?.error({
|
|
365
|
+
logger()?.error({
|
|
366
|
+
msg: "error in onopen handler",
|
|
367
|
+
error,
|
|
368
|
+
});
|
|
331
369
|
}
|
|
332
370
|
}
|
|
333
371
|
break;
|
|
@@ -336,7 +374,10 @@ export class WebSocketTunnelAdapter {
|
|
|
336
374
|
try {
|
|
337
375
|
this.#onclose.call(this, event);
|
|
338
376
|
} catch (error) {
|
|
339
|
-
logger()?.error({
|
|
377
|
+
logger()?.error({
|
|
378
|
+
msg: "error in onclose handler",
|
|
379
|
+
error,
|
|
380
|
+
});
|
|
340
381
|
}
|
|
341
382
|
}
|
|
342
383
|
break;
|
|
@@ -345,7 +386,10 @@ export class WebSocketTunnelAdapter {
|
|
|
345
386
|
try {
|
|
346
387
|
this.#onerror.call(this, event);
|
|
347
388
|
} catch (error) {
|
|
348
|
-
logger()?.error({
|
|
389
|
+
logger()?.error({
|
|
390
|
+
msg: "error in onerror handler",
|
|
391
|
+
error,
|
|
392
|
+
});
|
|
349
393
|
}
|
|
350
394
|
}
|
|
351
395
|
break;
|
|
@@ -354,7 +398,10 @@ export class WebSocketTunnelAdapter {
|
|
|
354
398
|
try {
|
|
355
399
|
this.#onmessage.call(this, event);
|
|
356
400
|
} catch (error) {
|
|
357
|
-
logger()?.error({
|
|
401
|
+
logger()?.error({
|
|
402
|
+
msg: "error in onmessage handler",
|
|
403
|
+
error,
|
|
404
|
+
});
|
|
358
405
|
}
|
|
359
406
|
}
|
|
360
407
|
break;
|
|
@@ -364,27 +411,29 @@ export class WebSocketTunnelAdapter {
|
|
|
364
411
|
|
|
365
412
|
// Internal methods called by the Tunnel class
|
|
366
413
|
_handleOpen(): void {
|
|
367
|
-
if (this.#readyState !== 0) {
|
|
414
|
+
if (this.#readyState !== 0) {
|
|
415
|
+
// CONNECTING
|
|
368
416
|
return;
|
|
369
417
|
}
|
|
370
418
|
|
|
371
419
|
this.#readyState = 1; // OPEN
|
|
372
|
-
|
|
420
|
+
|
|
373
421
|
const event = {
|
|
374
422
|
type: "open",
|
|
375
423
|
target: this,
|
|
376
424
|
};
|
|
377
|
-
|
|
425
|
+
|
|
378
426
|
this.#fireEvent("open", event);
|
|
379
427
|
}
|
|
380
428
|
|
|
381
429
|
_handleMessage(data: string | Uint8Array, isBinary: boolean): void {
|
|
382
|
-
if (this.#readyState !== 1) {
|
|
430
|
+
if (this.#readyState !== 1) {
|
|
431
|
+
// OPEN
|
|
383
432
|
return;
|
|
384
433
|
}
|
|
385
434
|
|
|
386
435
|
let messageData: any;
|
|
387
|
-
|
|
436
|
+
|
|
388
437
|
if (isBinary) {
|
|
389
438
|
// Handle binary data based on binaryType
|
|
390
439
|
if (this.#binaryType === "nodebuffer") {
|
|
@@ -395,14 +444,16 @@ export class WebSocketTunnelAdapter {
|
|
|
395
444
|
if (data instanceof Uint8Array) {
|
|
396
445
|
messageData = data.buffer.slice(
|
|
397
446
|
data.byteOffset,
|
|
398
|
-
data.byteOffset + data.byteLength
|
|
447
|
+
data.byteOffset + data.byteLength,
|
|
399
448
|
);
|
|
400
449
|
} else {
|
|
401
450
|
messageData = data;
|
|
402
451
|
}
|
|
403
452
|
} else {
|
|
404
453
|
// Blob type - not commonly used in Node.js
|
|
405
|
-
throw new Error(
|
|
454
|
+
throw new Error(
|
|
455
|
+
"Blob binaryType not supported in tunnel adapter",
|
|
456
|
+
);
|
|
406
457
|
}
|
|
407
458
|
} else {
|
|
408
459
|
messageData = data;
|
|
@@ -418,7 +469,8 @@ export class WebSocketTunnelAdapter {
|
|
|
418
469
|
}
|
|
419
470
|
|
|
420
471
|
_handleClose(code?: number, reason?: string): void {
|
|
421
|
-
if (this.#readyState === 3) {
|
|
472
|
+
if (this.#readyState === 3) {
|
|
473
|
+
// CLOSED
|
|
422
474
|
return;
|
|
423
475
|
}
|
|
424
476
|
|
|
@@ -472,7 +524,7 @@ export class WebSocketTunnelAdapter {
|
|
|
472
524
|
// Immediate close without close frame
|
|
473
525
|
this.#readyState = 3; // CLOSED
|
|
474
526
|
this.#closeCallback(1006, "Abnormal Closure");
|
|
475
|
-
|
|
527
|
+
|
|
476
528
|
const event = {
|
|
477
529
|
wasClean: false,
|
|
478
530
|
code: 1006,
|
|
@@ -480,7 +532,7 @@ export class WebSocketTunnelAdapter {
|
|
|
480
532
|
type: "close",
|
|
481
533
|
target: this,
|
|
482
534
|
};
|
|
483
|
-
|
|
535
|
+
|
|
484
536
|
this.#fireEvent("close", event);
|
|
485
537
|
}
|
|
486
538
|
}
|
package/tsconfig.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extends": "
|
|
2
|
+
"extends": "../../../../tsconfig.base.json",
|
|
3
3
|
"compilerOptions": {
|
|
4
4
|
"types": ["node"],
|
|
5
5
|
"paths": {
|
|
@@ -7,7 +7,5 @@
|
|
|
7
7
|
}
|
|
8
8
|
},
|
|
9
9
|
"include": ["src/**/*", "tests/**/*", "benches/**/*"],
|
|
10
|
-
"exclude": [
|
|
11
|
-
"node_modules"
|
|
12
|
-
]
|
|
10
|
+
"exclude": ["node_modules"]
|
|
13
11
|
}
|
package/tsup.config.ts
CHANGED
package/turbo.json
CHANGED
package/vitest.config.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { resolve } from "path";
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
2
|
import { defineConfig } from "vitest/config";
|
|
3
|
-
import defaultConfig from "
|
|
3
|
+
import defaultConfig from "../../../../vitest.base.ts";
|
|
4
4
|
|
|
5
5
|
export default defineConfig({
|
|
6
6
|
...defaultConfig,
|
|
@@ -14,4 +14,3 @@ export default defineConfig({
|
|
|
14
14
|
include: ["tests/**/*.test.ts"],
|
|
15
15
|
},
|
|
16
16
|
});
|
|
17
|
-
|