@bivy/bivy 0.10.1-staging.366 → 0.10.1-staging.367
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/dist/remote/relay-client.js +43 -6
- package/package.json +1 -1
|
@@ -6,7 +6,16 @@ import { WebSocket } from "ws";
|
|
|
6
6
|
import { seal, sealFrame, openFrame, ReplayGuard } from "../e2e.js";
|
|
7
7
|
import { frameMessages, FrameReassembler } from "../relay-chunk.js";
|
|
8
8
|
import { soloCredentials, buildDialUrl } from "./solo.js";
|
|
9
|
-
|
|
9
|
+
// Relay admission/config rejections: the relay refused this node at connect time
|
|
10
|
+
// (bad/expired ticket, relay disabled, missing token). Crucially a routine
|
|
11
|
+
// control-plane/relay DEPLOY produces exactly these for a few seconds — a
|
|
12
|
+
// reconnecting node races the still-restarting control plane, its ticket
|
|
13
|
+
// introspection transiently fails, and the relay reports "Unauthorized node". So
|
|
14
|
+
// this is NOT permanently fatal: we keep retrying (on the slower admission
|
|
15
|
+
// ceiling below) and reconnect on our own once admission passes again.
|
|
16
|
+
// Previously these disabled the connector for good, so every routine update
|
|
17
|
+
// knocked every node offline until each was restarted by hand.
|
|
18
|
+
function isAdmissionRelayError(message) {
|
|
10
19
|
return /hosted relay is not enabled|unauthorized|missing token/i.test(message);
|
|
11
20
|
}
|
|
12
21
|
export function loadRelayConfig(appDir) {
|
|
@@ -50,12 +59,27 @@ const HEARTBEAT_TIMEOUT_MS = 75_000;
|
|
|
50
59
|
// rate-limited node reconnect every ~1s indefinitely — a storm that itself keeps
|
|
51
60
|
// tripping the limiter. Requiring a stable window lets backoff escalate normally.
|
|
52
61
|
const BACKOFF_STABLE_RESET_MS = 30_000;
|
|
62
|
+
// Reconnect backoff ceiling for a transient network drop.
|
|
63
|
+
const BACKOFF_MAX_MS = 30_000;
|
|
64
|
+
// Reconnect backoff ceiling once the relay has actively REJECTED admission (see
|
|
65
|
+
// isAdmissionRelayError). Slower than the network ceiling so a node that is
|
|
66
|
+
// genuinely deauthorized (removed, plan lost, relay turned off) retries gently
|
|
67
|
+
// instead of hammering the relay every 30s forever — while still recovering
|
|
68
|
+
// automatically, within this bound, the moment admission is restored (deploy
|
|
69
|
+
// finished, node re-authorized). No manual node restart required either way.
|
|
70
|
+
const ADMISSION_BACKOFF_MAX_MS = 5 * 60_000;
|
|
53
71
|
export class RelayConnector {
|
|
54
72
|
config;
|
|
55
73
|
onClientMessage;
|
|
56
74
|
ws;
|
|
57
75
|
closed = false;
|
|
58
76
|
backoff = 1000;
|
|
77
|
+
// Set when the relay actively rejected admission (isAdmissionRelayError).
|
|
78
|
+
// Raises the reconnect ceiling to ADMISSION_BACKOFF_MAX_MS so we retry gently
|
|
79
|
+
// rather than give up; cleared once we reconnect. Deliberately NOT `closed` —
|
|
80
|
+
// an admission rejection must never permanently stop the connector, or a
|
|
81
|
+
// routine deploy takes the node offline until someone restarts it by hand.
|
|
82
|
+
admissionRejected = false;
|
|
59
83
|
heartbeatTimer;
|
|
60
84
|
stableTimer;
|
|
61
85
|
lastPongAt = 0;
|
|
@@ -102,6 +126,7 @@ export class RelayConnector {
|
|
|
102
126
|
}
|
|
103
127
|
start() {
|
|
104
128
|
this.closed = false;
|
|
129
|
+
this.admissionRejected = false;
|
|
105
130
|
void this.connect();
|
|
106
131
|
}
|
|
107
132
|
stop() {
|
|
@@ -315,6 +340,9 @@ export class RelayConnector {
|
|
|
315
340
|
if (env.t === "ready") {
|
|
316
341
|
this.ready = true;
|
|
317
342
|
this.lastErrorMessage = undefined;
|
|
343
|
+
// Admission passed — drop back to the fast network ceiling for any
|
|
344
|
+
// future drop (scheduleBackoffReset restores the 1s floor once stable).
|
|
345
|
+
this.admissionRejected = false;
|
|
318
346
|
this.startHeartbeat(ws);
|
|
319
347
|
this.scheduleBackoffReset(ws);
|
|
320
348
|
console.log("[relay] connected");
|
|
@@ -352,9 +380,14 @@ export class RelayConnector {
|
|
|
352
380
|
const message = env.error || "Relay error";
|
|
353
381
|
this.lastErrorMessage = message;
|
|
354
382
|
console.warn("[relay] error:", message);
|
|
355
|
-
if (
|
|
356
|
-
|
|
357
|
-
this
|
|
383
|
+
if (isAdmissionRelayError(message)) {
|
|
384
|
+
// Do NOT disable the connector: a deploy rejects a reconnecting node
|
|
385
|
+
// this way for a few seconds, and permanently stopping meant a routine
|
|
386
|
+
// update took the node offline until it was manually restarted. Raise
|
|
387
|
+
// the backoff ceiling and let the close handler reconnect us; we
|
|
388
|
+
// recover on our own once admission passes again.
|
|
389
|
+
this.admissionRejected = true;
|
|
390
|
+
console.warn("[relay] admission rejected; retrying on a slow backoff (auto-recovers, no restart needed)");
|
|
358
391
|
ws.close();
|
|
359
392
|
}
|
|
360
393
|
}
|
|
@@ -376,8 +409,12 @@ export class RelayConnector {
|
|
|
376
409
|
scheduleReconnect() {
|
|
377
410
|
if (this.closed)
|
|
378
411
|
return;
|
|
379
|
-
|
|
380
|
-
|
|
412
|
+
// A relay that actively rejected admission retries on a much slower ceiling
|
|
413
|
+
// than a transient network drop, so a genuinely-unauthorized node doesn't
|
|
414
|
+
// hammer the relay while still auto-recovering when admission is restored.
|
|
415
|
+
const cap = this.admissionRejected ? ADMISSION_BACKOFF_MAX_MS : BACKOFF_MAX_MS;
|
|
416
|
+
const wait = Math.min(this.backoff, cap);
|
|
417
|
+
this.backoff = Math.min(this.backoff * 2, cap);
|
|
381
418
|
setTimeout(() => {
|
|
382
419
|
if (!this.closed)
|
|
383
420
|
void this.connect();
|
package/package.json
CHANGED