@buildautomaton/cli 0.1.39 → 0.1.40
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/cli.js +124 -10
- package/dist/cli.js.map +4 -4
- package/dist/index.js +124 -10
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22093,9 +22093,9 @@ function attachWebSocketClientPing(ws, intervalMs) {
|
|
|
22093
22093
|
return clear;
|
|
22094
22094
|
}
|
|
22095
22095
|
function buildCliWebSocketClientOptions(wsUrl) {
|
|
22096
|
-
const wsOptions = { perMessageDeflate: false
|
|
22096
|
+
const wsOptions = { perMessageDeflate: false };
|
|
22097
22097
|
if (wsUrl.startsWith("wss://")) {
|
|
22098
|
-
wsOptions.agent = new https.Agent({ rejectUnauthorized: false
|
|
22098
|
+
wsOptions.agent = new https.Agent({ rejectUnauthorized: false });
|
|
22099
22099
|
}
|
|
22100
22100
|
return wsOptions;
|
|
22101
22101
|
}
|
|
@@ -22130,11 +22130,47 @@ function safeSendWebSocketBinary(ws, data) {
|
|
|
22130
22130
|
}
|
|
22131
22131
|
}
|
|
22132
22132
|
|
|
22133
|
+
// src/connection/parse-compact-heartbeat-ack.ts
|
|
22134
|
+
function tryParseCompactHeartbeatAck(raw) {
|
|
22135
|
+
let str = null;
|
|
22136
|
+
if (typeof raw === "string") {
|
|
22137
|
+
str = raw;
|
|
22138
|
+
} else if (Buffer.isBuffer(raw)) {
|
|
22139
|
+
str = raw.toString("utf8");
|
|
22140
|
+
} else if (raw instanceof ArrayBuffer) {
|
|
22141
|
+
str = Buffer.from(raw).toString("utf8");
|
|
22142
|
+
} else {
|
|
22143
|
+
return null;
|
|
22144
|
+
}
|
|
22145
|
+
if (str.length > 64 || !str.includes('"ha"')) return null;
|
|
22146
|
+
try {
|
|
22147
|
+
const parsed = JSON.parse(str);
|
|
22148
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
22149
|
+
const o = parsed;
|
|
22150
|
+
if (o.t !== "ha") return null;
|
|
22151
|
+
const s = o.s;
|
|
22152
|
+
if (typeof s !== "number" || !Number.isFinite(s)) return null;
|
|
22153
|
+
return Math.trunc(s);
|
|
22154
|
+
} catch {
|
|
22155
|
+
return null;
|
|
22156
|
+
}
|
|
22157
|
+
}
|
|
22158
|
+
|
|
22133
22159
|
// src/connection/create-ws-bridge.ts
|
|
22134
22160
|
var BRIDGE_AUTH_ERROR_HEADER = "x-bridge-auth-error";
|
|
22135
22161
|
var BRIDGE_AUTH_ERROR_TOKEN_INVALID = "token_invalid";
|
|
22136
22162
|
function createWsBridge(options) {
|
|
22137
|
-
const {
|
|
22163
|
+
const {
|
|
22164
|
+
url: url2,
|
|
22165
|
+
onMessage,
|
|
22166
|
+
onOpen,
|
|
22167
|
+
onClose,
|
|
22168
|
+
onError: onError2,
|
|
22169
|
+
onAuthInvalid,
|
|
22170
|
+
clientPingIntervalMs,
|
|
22171
|
+
onCompactHeartbeatAck,
|
|
22172
|
+
isActiveSocket
|
|
22173
|
+
} = options;
|
|
22138
22174
|
applyCliOutboundNetworkPreferences();
|
|
22139
22175
|
const ws = new wrapper_default(url2, buildCliWebSocketClientOptions(url2));
|
|
22140
22176
|
let clearClientPing = null;
|
|
@@ -22158,7 +22194,13 @@ function createWsBridge(options) {
|
|
|
22158
22194
|
onOpen?.();
|
|
22159
22195
|
});
|
|
22160
22196
|
ws.on("message", (raw) => {
|
|
22197
|
+
const ackSeq = tryParseCompactHeartbeatAck(raw);
|
|
22198
|
+
if (ackSeq != null) {
|
|
22199
|
+
onCompactHeartbeatAck?.(ackSeq);
|
|
22200
|
+
return;
|
|
22201
|
+
}
|
|
22161
22202
|
setImmediate(() => {
|
|
22203
|
+
if (isActiveSocket && !isActiveSocket(ws)) return;
|
|
22162
22204
|
try {
|
|
22163
22205
|
let data;
|
|
22164
22206
|
if (typeof raw === "string") {
|
|
@@ -22169,9 +22211,9 @@ function createWsBridge(options) {
|
|
|
22169
22211
|
} else {
|
|
22170
22212
|
data = raw;
|
|
22171
22213
|
}
|
|
22172
|
-
onMessage?.(data);
|
|
22214
|
+
onMessage?.(data, ws);
|
|
22173
22215
|
} catch {
|
|
22174
|
-
onMessage?.(raw);
|
|
22216
|
+
onMessage?.(raw, ws);
|
|
22175
22217
|
}
|
|
22176
22218
|
});
|
|
22177
22219
|
});
|
|
@@ -24076,7 +24118,7 @@ function installBridgeProcessResilience() {
|
|
|
24076
24118
|
}
|
|
24077
24119
|
|
|
24078
24120
|
// src/cli-version.ts
|
|
24079
|
-
var CLI_VERSION = "0.1.
|
|
24121
|
+
var CLI_VERSION = "0.1.40".length > 0 ? "0.1.40" : "0.0.0-dev";
|
|
24080
24122
|
|
|
24081
24123
|
// src/connection/heartbeat/constants.ts
|
|
24082
24124
|
var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
|
|
@@ -24965,6 +25007,61 @@ function scheduleMainBridgeReconnect(state, connect, log2, closeMeta) {
|
|
|
24965
25007
|
});
|
|
24966
25008
|
}
|
|
24967
25009
|
|
|
25010
|
+
// src/connection/reconnect/duplicate-bridge-connection-detect.ts
|
|
25011
|
+
var BRIDGE_SUPERSEDED_CLOSE_REASON_SNIPPET = "superseded";
|
|
25012
|
+
var DUPLICATE_BRIDGE_SHORT_SESSION_MS = 25e3;
|
|
25013
|
+
var DUPLICATE_BRIDGE_FLAP_MIN_COUNT = 2;
|
|
25014
|
+
var DUPLICATE_BRIDGE_FLAP_WINDOW_MS = 9e4;
|
|
25015
|
+
var DUPLICATE_BRIDGE_STABLE_SESSION_MS = BRIDGE_APP_HEARTBEAT_INTERVAL_MS * BRIDGE_HEARTBEAT_MISSED_ACKS_BEFORE_RECONNECT + 5e3;
|
|
25016
|
+
var DUPLICATE_BRIDGE_WARNING_MESSAGE = "[Bridge service] It looks like two bridges are using the same token and disconnecting each other. Stop any duplicate @buildautomaton/cli process for this workspace/token (only one bridge should run).";
|
|
25017
|
+
function createEmptyDuplicateBridgeFlapTracker() {
|
|
25018
|
+
return {
|
|
25019
|
+
sessionOpenedAtMs: null,
|
|
25020
|
+
shortDisconnectAtMs: []
|
|
25021
|
+
};
|
|
25022
|
+
}
|
|
25023
|
+
function isSupersededByNewBridgeClose(code, reason) {
|
|
25024
|
+
if (code !== 1e3) return false;
|
|
25025
|
+
return reason.toLowerCase().includes(BRIDGE_SUPERSEDED_CLOSE_REASON_SNIPPET);
|
|
25026
|
+
}
|
|
25027
|
+
function pruneShortDisconnects(tracker, nowMs) {
|
|
25028
|
+
const cutoff = nowMs - DUPLICATE_BRIDGE_FLAP_WINDOW_MS;
|
|
25029
|
+
tracker.shortDisconnectAtMs = tracker.shortDisconnectAtMs.filter((t) => t >= cutoff);
|
|
25030
|
+
}
|
|
25031
|
+
function recordBridgeSessionOpened(tracker, nowMs) {
|
|
25032
|
+
tracker.sessionOpenedAtMs = nowMs;
|
|
25033
|
+
}
|
|
25034
|
+
function evaluateDuplicateBridgeDisconnect(tracker, nowMs, code, reason) {
|
|
25035
|
+
if (isSupersededByNewBridgeClose(code, reason)) {
|
|
25036
|
+
return { kind: "warn", trigger: "superseded_close" };
|
|
25037
|
+
}
|
|
25038
|
+
const openedAt = tracker.sessionOpenedAtMs;
|
|
25039
|
+
tracker.sessionOpenedAtMs = null;
|
|
25040
|
+
if (openedAt == null) {
|
|
25041
|
+
return { kind: "none" };
|
|
25042
|
+
}
|
|
25043
|
+
const sessionMs = nowMs - openedAt;
|
|
25044
|
+
if (sessionMs >= DUPLICATE_BRIDGE_STABLE_SESSION_MS) {
|
|
25045
|
+
tracker.shortDisconnectAtMs = [];
|
|
25046
|
+
return { kind: "none" };
|
|
25047
|
+
}
|
|
25048
|
+
if (sessionMs < DUPLICATE_BRIDGE_SHORT_SESSION_MS) {
|
|
25049
|
+
pruneShortDisconnects(tracker, nowMs);
|
|
25050
|
+
tracker.shortDisconnectAtMs.push(nowMs);
|
|
25051
|
+
if (tracker.shortDisconnectAtMs.length >= DUPLICATE_BRIDGE_FLAP_MIN_COUNT) {
|
|
25052
|
+
return { kind: "warn", trigger: "rapid_short_disconnects" };
|
|
25053
|
+
}
|
|
25054
|
+
}
|
|
25055
|
+
return { kind: "none" };
|
|
25056
|
+
}
|
|
25057
|
+
function logDuplicateBridgeWarning(log2, result) {
|
|
25058
|
+
if (result.kind !== "warn") return;
|
|
25059
|
+
try {
|
|
25060
|
+
log2(DUPLICATE_BRIDGE_WARNING_MESSAGE);
|
|
25061
|
+
} catch {
|
|
25062
|
+
}
|
|
25063
|
+
}
|
|
25064
|
+
|
|
24968
25065
|
// src/connection/reconnect/firehose-reconnect.ts
|
|
24969
25066
|
function beginFirehoseDeferredDisconnect(ctx, code, reason, log2) {
|
|
24970
25067
|
beginTieredSilentReconnectDisconnect({
|
|
@@ -37310,8 +37407,10 @@ function normalizeInboundBridgeWebSocketJson(data) {
|
|
|
37310
37407
|
}
|
|
37311
37408
|
return data;
|
|
37312
37409
|
}
|
|
37313
|
-
function handleBridgeMessage(data, deps) {
|
|
37314
|
-
|
|
37410
|
+
function handleBridgeMessage(data, deps, sourceWs) {
|
|
37411
|
+
const active = deps.getWs();
|
|
37412
|
+
if (!active) return;
|
|
37413
|
+
if (sourceWs != null && sourceWs !== active) return;
|
|
37315
37414
|
const msg = parseApiToBridgeMessage(normalizeInboundBridgeWebSocketJson(data), deps.log);
|
|
37316
37415
|
if (!msg) return;
|
|
37317
37416
|
dispatchBridgeMessage(msg, deps);
|
|
@@ -37363,6 +37462,7 @@ function createMainBridgeWebSocketLifecycle(params) {
|
|
|
37363
37462
|
} = params;
|
|
37364
37463
|
let authRefreshInFlight = false;
|
|
37365
37464
|
function handleOpen() {
|
|
37465
|
+
recordBridgeSessionOpened(state.duplicateBridgeFlap, Date.now());
|
|
37366
37466
|
const logOpenAsPostRefreshReconnect = state.logBridgeOpenAsReconnect;
|
|
37367
37467
|
clearMainBridgeReconnectQuietOnOpen(state, logFn);
|
|
37368
37468
|
state.reconnectAttempt = 0;
|
|
@@ -37397,6 +37497,15 @@ function createMainBridgeWebSocketLifecycle(params) {
|
|
|
37397
37497
|
state.currentWs = null;
|
|
37398
37498
|
if (was) was.removeAllListeners();
|
|
37399
37499
|
const willReconnect = !state.closedByUser;
|
|
37500
|
+
if (willReconnect) {
|
|
37501
|
+
const duplicateResult = evaluateDuplicateBridgeDisconnect(
|
|
37502
|
+
state.duplicateBridgeFlap,
|
|
37503
|
+
Date.now(),
|
|
37504
|
+
code,
|
|
37505
|
+
reason
|
|
37506
|
+
);
|
|
37507
|
+
logDuplicateBridgeWarning(logFn, duplicateResult);
|
|
37508
|
+
}
|
|
37400
37509
|
beginMainBridgeDeferredDisconnect(state, code, reason, logFn, willReconnect);
|
|
37401
37510
|
if (willReconnect) {
|
|
37402
37511
|
state.lastReconnectCloseMeta = { code, reason };
|
|
@@ -37446,6 +37555,10 @@ function createMainBridgeWebSocketLifecycle(params) {
|
|
|
37446
37555
|
state.currentWs = createWsBridge({
|
|
37447
37556
|
url: url2,
|
|
37448
37557
|
clientPingIntervalMs: CLI_WEBSOCKET_CLIENT_PING_MS,
|
|
37558
|
+
isActiveSocket: (socket) => state.currentWs === socket,
|
|
37559
|
+
onCompactHeartbeatAck: (seq) => {
|
|
37560
|
+
messageDeps.onBridgeHeartbeatAck?.(seq);
|
|
37561
|
+
},
|
|
37449
37562
|
onAuthInvalid: () => {
|
|
37450
37563
|
if (authRefreshInFlight) return;
|
|
37451
37564
|
void (async () => {
|
|
@@ -37499,9 +37612,9 @@ function createMainBridgeWebSocketLifecycle(params) {
|
|
|
37499
37612
|
} catch {
|
|
37500
37613
|
}
|
|
37501
37614
|
},
|
|
37502
|
-
onMessage: (data) => {
|
|
37615
|
+
onMessage: (data, sourceWs) => {
|
|
37503
37616
|
try {
|
|
37504
|
-
handleBridgeMessage(data, messageDeps);
|
|
37617
|
+
handleBridgeMessage(data, messageDeps, sourceWs);
|
|
37505
37618
|
} catch {
|
|
37506
37619
|
}
|
|
37507
37620
|
}
|
|
@@ -37945,6 +38058,7 @@ async function createBridgeConnection(options) {
|
|
|
37945
38058
|
currentWs: null,
|
|
37946
38059
|
mainQuiet: createEmptyReconnectQuietSlot(),
|
|
37947
38060
|
mainOutage: createEmptyReconnectOutageTracker(),
|
|
38061
|
+
duplicateBridgeFlap: createEmptyDuplicateBridgeFlapTracker(),
|
|
37948
38062
|
firehoseHandle: null,
|
|
37949
38063
|
lastFirehoseParams: null,
|
|
37950
38064
|
firehoseReconnectTimeout: null,
|