@openclaw/gateway-client 2026.8.1-beta.1 → 2026.8.1-beta.2
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 +15 -3
- package/dist/browser-device-auth-Dz6H7eXy.d.mts +112 -0
- package/dist/browser.d.mts +5 -3
- package/dist/browser.mjs +2 -2
- package/dist/index.d.mts +5 -3
- package/dist/index.mjs +324 -163
- package/dist/{protocol-client-U142L0j3.d.mts → protocol-client-BbN-I582.d.mts} +15 -30
- package/dist/protocol-request-D4mVhLxE.d.mts +32 -0
- package/dist/{readiness-uSDrqxDM.d.mts → readiness--KX7i8Mq.d.mts} +24 -12
- package/dist/readiness.d.mts +1 -1
- package/dist/scope-upgrade.d.mts +38 -0
- package/dist/scope-upgrade.mjs +73 -0
- package/dist/{session-subscriptions-Das1PATe.d.mts → session-subscriptions-BTNpXybo.d.mts} +6 -111
- package/dist/{session-subscriptions-D4Xv60cN.mjs → session-subscriptions-uKmTyc3P.mjs} +270 -152
- package/dist/timeouts.mjs +3 -3
- package/dist/websocket-data.d.mts +6 -0
- package/dist/websocket-data.mjs +8 -0
- package/package.json +13 -3
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { C as buildDeviceAuthPayload, S as
|
|
1
|
+
import { C as resolveGatewayConnectScopes, D as buildDeviceAuthPayloadV3, E as buildDeviceAuthPayload, O as normalizeDeviceMetadataForAuth, S as buildGatewayConnectAuth, T as shouldRetryGatewayWithDeviceToken, _ as GatewayProtocolRequestError, a as createSessionProjection, b as gatewayOriginScope, c as normalizeSessionProjectionRunId, d as readSessionMessageSequence, f as reconcileSessionProjectionSnapshot, g as GatewayProtocolClient, h as shouldPauseGatewayReconnect, i as resetGatewaySessionMessageSubscriptionCoordinator, l as projectLiveSessionMessage, m as reduceSessionProjectionRunEvent, n as getGatewaySessionMessageSubscriptionCoordinator, o as hasSessionProjectionAcceptedFinal, p as reduceSessionProjection, r as releaseGatewaySessionMessageSubscription, s as isLocallyOptimisticSessionMessage, t as GatewaySessionMessageSubscriptionCoordinator, u as readSessionMessageIdentity, v as GatewayProtocolRequestTimeoutError, w as selectGatewayConnectAuth, x as GatewayBrowserDeviceAuthLifecycle, y as gatewayCredentialScope } from "./session-subscriptions-uKmTyc3P.mjs";
|
|
2
2
|
import { DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, MAX_CONNECT_CHALLENGE_TIMEOUT_MS, MAX_SAFE_TIMEOUT_DELAY_MS, MIN_CONNECT_CHALLENGE_TIMEOUT_MS, addSafeTimeoutDelayGraceMs, clampConnectChallengeTimeoutMs, clearGatewayConnectTimeout, getConnectChallengeTimeoutMsFromEnv, resolveConnectChallengeTimeoutMs, resolveFiniteTimeoutDelayMs, resolvePreauthHandshakeTimeoutMs, resolveSafeTimeoutDelayMs, startGatewayConnectTimeout } from "./timeouts.mjs";
|
|
3
|
+
import { rawDataToString } from "./websocket-data.mjs";
|
|
3
4
|
import { n as startGatewayClientWithReadinessWait, r as waitForEventLoopReady, t as startGatewayClientWhenEventLoopReady } from "./readiness-CrWxd4TI.mjs";
|
|
5
|
+
import { GatewayScopeUpgrade } from "./scope-upgrade.mjs";
|
|
4
6
|
import { ConnectErrorDetailCodes, formatConnectErrorMessage, readConnectErrorDetailCode } from "@openclaw/gateway-protocol/connect-error-details";
|
|
5
7
|
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "@openclaw/gateway-protocol/client-info";
|
|
6
8
|
import { resolveGatewayStartupRetryAfterMs } from "@openclaw/gateway-protocol/startup-unavailable";
|
|
7
|
-
import { MIN_CLIENT_PROTOCOL_VERSION, PROTOCOL_VERSION } from "@openclaw/gateway-protocol/version";
|
|
9
|
+
import { MIN_CLIENT_PROTOCOL_VERSION, MIN_NODE_PROTOCOL_VERSION, MIN_PROBE_PROTOCOL_VERSION, PROTOCOL_VERSION } from "@openclaw/gateway-protocol/version";
|
|
8
10
|
import { randomUUID } from "node:crypto";
|
|
9
11
|
import { WebSocket } from "ws";
|
|
10
|
-
import { Buffer as Buffer$1 } from "node:buffer";
|
|
11
12
|
//#region \0rolldown/runtime.js
|
|
12
13
|
var __create = Object.create;
|
|
13
14
|
var __defProp = Object.defineProperty;
|
|
@@ -31,6 +32,26 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
31
32
|
enumerable: true
|
|
32
33
|
}) : target, mod));
|
|
33
34
|
//#endregion
|
|
35
|
+
//#region packages/normalization-core/src/string-coerce.ts
|
|
36
|
+
/** Trims string input and returns null for non-strings or empty strings. */
|
|
37
|
+
function normalizeNullableString(value) {
|
|
38
|
+
if (typeof value !== "string") return null;
|
|
39
|
+
const trimmed = value.trim();
|
|
40
|
+
return trimmed ? trimmed : null;
|
|
41
|
+
}
|
|
42
|
+
/** Trims string input and returns undefined for non-strings or empty strings. */
|
|
43
|
+
function normalizeOptionalString(value) {
|
|
44
|
+
return normalizeNullableString(value) ?? void 0;
|
|
45
|
+
}
|
|
46
|
+
/** Lowercases a normalized optional string. */
|
|
47
|
+
function normalizeOptionalLowercaseString(value) {
|
|
48
|
+
return normalizeOptionalString(value)?.toLowerCase();
|
|
49
|
+
}
|
|
50
|
+
/** Lowercases a normalized string or returns an empty string when absent. */
|
|
51
|
+
function normalizeLowercaseStringOrEmpty(value) {
|
|
52
|
+
return normalizeOptionalLowercaseString(value) ?? "";
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
34
55
|
//#region packages/net-policy/src/ip.ts
|
|
35
56
|
var import_ipaddr = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
36
57
|
(function(root) {
|
|
@@ -990,13 +1011,6 @@ var import_ipaddr = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
990
1011
|
else root.ipaddr = ipaddr;
|
|
991
1012
|
})(exports);
|
|
992
1013
|
})))(), 1);
|
|
993
|
-
function normalizeOptionalString(value) {
|
|
994
|
-
if (typeof value !== "string") return;
|
|
995
|
-
return value.trim() || void 0;
|
|
996
|
-
}
|
|
997
|
-
function normalizeLowercaseStringOrEmpty$1(value) {
|
|
998
|
-
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
999
|
-
}
|
|
1000
1014
|
import_ipaddr.default.IPv4.parse("198.18.0.0");
|
|
1001
1015
|
function stripIpv6Brackets(value) {
|
|
1002
1016
|
if (value.startsWith("[") && value.endsWith("]")) return value.slice(1, -1);
|
|
@@ -1026,7 +1040,7 @@ function parseCanonicalIpAddress(raw) {
|
|
|
1026
1040
|
function normalizeIpAddress(raw) {
|
|
1027
1041
|
const parsed = parseCanonicalIpAddress(raw);
|
|
1028
1042
|
if (!parsed) return;
|
|
1029
|
-
return normalizeLowercaseStringOrEmpty
|
|
1043
|
+
return normalizeLowercaseStringOrEmpty(normalizeIpv4MappedAddress(parsed).toString());
|
|
1030
1044
|
}
|
|
1031
1045
|
/** True when a canonical IP literal is loopback, including IPv4-mapped IPv6. */
|
|
1032
1046
|
function isLoopbackIpAddress(raw) {
|
|
@@ -1035,21 +1049,8 @@ function isLoopbackIpAddress(raw) {
|
|
|
1035
1049
|
return normalizeIpv4MappedAddress(parsed).range() === "loopback";
|
|
1036
1050
|
}
|
|
1037
1051
|
//#endregion
|
|
1038
|
-
//#region packages/net-policy/src/url-protocol.ts
|
|
1039
|
-
function parseUrl(value) {
|
|
1040
|
-
if (value instanceof URL) return value;
|
|
1041
|
-
try {
|
|
1042
|
-
return new URL(value);
|
|
1043
|
-
} catch {
|
|
1044
|
-
return null;
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
function isWssUrl(value) {
|
|
1048
|
-
return parseUrl(value)?.protocol === "wss:";
|
|
1049
|
-
}
|
|
1050
|
-
//#endregion
|
|
1051
1052
|
//#region packages/gateway-client/src/client-address-utils.ts
|
|
1052
|
-
function
|
|
1053
|
+
function normalizeGatewayErrorText(value) {
|
|
1053
1054
|
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
1054
1055
|
}
|
|
1055
1056
|
function isSensitiveUrlQueryParamName(key) {
|
|
@@ -1076,34 +1077,34 @@ function parseGatewayIpAddress(host) {
|
|
|
1076
1077
|
return normalized ? parseCanonicalIpAddress(normalized) : void 0;
|
|
1077
1078
|
}
|
|
1078
1079
|
//#endregion
|
|
1079
|
-
//#region packages/gateway-client/src/
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
}
|
|
1091
|
-
publicKeyRawBase64UrlFromPem: () => {
|
|
1092
|
-
throw new Error("GatewayClient public key dependency is not configured");
|
|
1093
|
-
},
|
|
1094
|
-
loadDeviceAuthToken: () => null,
|
|
1095
|
-
storeDeviceAuthToken: () => {},
|
|
1096
|
-
clearDeviceAuthToken: () => {},
|
|
1097
|
-
beforeConnect: () => {},
|
|
1098
|
-
registerGatewayLoopbackBypass: () => void 0,
|
|
1099
|
-
logDebug: () => {},
|
|
1100
|
-
logError: () => {},
|
|
1101
|
-
redactForLog: (message) => message,
|
|
1102
|
-
normalizeTlsFingerprint: normalizeFingerprint
|
|
1080
|
+
//#region packages/gateway-client/src/request-error.ts
|
|
1081
|
+
var GatewayClientRequestError = class extends GatewayProtocolRequestError {
|
|
1082
|
+
constructor(error) {
|
|
1083
|
+
super({
|
|
1084
|
+
...error,
|
|
1085
|
+
message: formatConnectErrorMessage({
|
|
1086
|
+
message: error.message,
|
|
1087
|
+
details: error.details
|
|
1088
|
+
})
|
|
1089
|
+
});
|
|
1090
|
+
this.name = "GatewayClientRequestError";
|
|
1091
|
+
}
|
|
1103
1092
|
};
|
|
1104
|
-
|
|
1105
|
-
|
|
1093
|
+
//#endregion
|
|
1094
|
+
//#region packages/net-policy/src/url-protocol.ts
|
|
1095
|
+
function parseUrl(value) {
|
|
1096
|
+
if (value instanceof URL) return value;
|
|
1097
|
+
try {
|
|
1098
|
+
return new URL(value);
|
|
1099
|
+
} catch {
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function isWssUrl(value) {
|
|
1104
|
+
return parseUrl(value)?.protocol === "wss:";
|
|
1106
1105
|
}
|
|
1106
|
+
//#endregion
|
|
1107
|
+
//#region packages/gateway-client/src/websocket-transport.ts
|
|
1107
1108
|
const PRIVATE_OR_LOOPBACK_IPV4_RANGES = /* @__PURE__ */ new Set([
|
|
1108
1109
|
"loopback",
|
|
1109
1110
|
"private",
|
|
@@ -1119,19 +1120,16 @@ const PRIVATE_OR_LOOPBACK_IPV6_RANGES = /* @__PURE__ */ new Set([
|
|
|
1119
1120
|
function isPrivateOrLoopbackIpAddress(address) {
|
|
1120
1121
|
return (address.kind() === "ipv4" ? PRIVATE_OR_LOOPBACK_IPV4_RANGES : PRIVATE_OR_LOOPBACK_IPV6_RANGES).has(address.range());
|
|
1121
1122
|
}
|
|
1122
|
-
function
|
|
1123
|
+
function isGatewayLoopbackHost(host) {
|
|
1123
1124
|
const parsed = parseHostForAddressChecks(host);
|
|
1124
|
-
|
|
1125
|
-
if (parsed.isLocalhost) return true;
|
|
1126
|
-
return isLoopbackIpAddress(parsed.unbracketedHost);
|
|
1125
|
+
return Boolean(parsed && (parsed.isLocalhost || isLoopbackIpAddress(parsed.unbracketedHost)));
|
|
1127
1126
|
}
|
|
1128
1127
|
function isPrivateOrLoopbackHost(host) {
|
|
1129
1128
|
const parsed = parseHostForAddressChecks(host);
|
|
1130
1129
|
if (!parsed) return false;
|
|
1131
1130
|
if (parsed.isLocalhost) return true;
|
|
1132
1131
|
const address = parseGatewayIpAddress(parsed.unbracketedHost);
|
|
1133
|
-
|
|
1134
|
-
return isPrivateOrLoopbackIpAddress(address);
|
|
1132
|
+
return Boolean(address && isPrivateOrLoopbackIpAddress(address));
|
|
1135
1133
|
}
|
|
1136
1134
|
function isTrustedPlaintextWebSocketHost(hostname) {
|
|
1137
1135
|
if (isPrivateOrLoopbackHost(hostname)) return true;
|
|
@@ -1144,7 +1142,7 @@ function isSecureWebSocketUrl(rawUrl, options) {
|
|
|
1144
1142
|
const protocol = url.protocol === "https:" ? "wss:" : url.protocol === "http:" ? "ws:" : url.protocol;
|
|
1145
1143
|
if (protocol === "wss:") return true;
|
|
1146
1144
|
if (protocol !== "ws:") return false;
|
|
1147
|
-
if (
|
|
1145
|
+
if (isGatewayLoopbackHost(url.hostname) || isTrustedPlaintextWebSocketHost(url.hostname)) return true;
|
|
1148
1146
|
if (options?.allowPrivateWs === true) {
|
|
1149
1147
|
const hostForIpCheck = url.hostname.startsWith("[") && url.hostname.endsWith("]") ? url.hostname.slice(1, -1) : url.hostname;
|
|
1150
1148
|
return isPrivateOrLoopbackHost(url.hostname) || parseGatewayIpAddress(hostForIpCheck) === void 0;
|
|
@@ -1154,37 +1152,117 @@ function isSecureWebSocketUrl(rawUrl, options) {
|
|
|
1154
1152
|
return false;
|
|
1155
1153
|
}
|
|
1156
1154
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
});
|
|
1168
|
-
|
|
1155
|
+
var GatewayWebSocketTransportConfigurationError = class extends Error {};
|
|
1156
|
+
function resolveGatewayWebSocketTransport(params) {
|
|
1157
|
+
const usesTls = isWssUrl(params.url);
|
|
1158
|
+
if (params.tlsFingerprint && !usesTls) throw new GatewayWebSocketTransportConfigurationError("gateway tls fingerprint requires wss:// gateway url");
|
|
1159
|
+
const allowPrivateWs = (params.env ?? process.env).OPENCLAW_ALLOW_INSECURE_PRIVATE_WS === "1";
|
|
1160
|
+
if (!isSecureWebSocketUrl(params.url, { allowPrivateWs })) {
|
|
1161
|
+
let displayHost = params.url;
|
|
1162
|
+
try {
|
|
1163
|
+
displayHost = new URL(params.url).hostname || params.url;
|
|
1164
|
+
} catch {}
|
|
1165
|
+
throw new GatewayWebSocketTransportConfigurationError(`SECURITY ERROR: Cannot connect to "${displayHost}" over plaintext ws://. Both credentials and chat data would be exposed to network interception. Use wss:// for remote URLs. Safe defaults: keep gateway.bind=loopback and connect via SSH tunnel (ssh -N -L 18789:127.0.0.1:18789 user@gateway-host), or use Tailscale Serve/Funnel. ` + (allowPrivateWs ? "" : "Break-glass (trusted private networks only): set OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1. ") + "Run `openclaw doctor --fix` for guidance.");
|
|
1166
|
+
}
|
|
1167
|
+
const normalize = params.normalizeTlsFingerprint ?? normalizeFingerprint;
|
|
1168
|
+
const options = { ...params.options };
|
|
1169
|
+
if (usesTls && params.tlsFingerprint) {
|
|
1170
|
+
options.rejectUnauthorized = false;
|
|
1171
|
+
options.checkServerIdentity = (_hostValue, cert) => {
|
|
1172
|
+
const fingerprintValue = typeof cert === "object" && cert && "fingerprint256" in cert ? cert.fingerprint256 ?? "" : "";
|
|
1173
|
+
const fingerprint = normalize(typeof fingerprintValue === "string" ? fingerprintValue : "");
|
|
1174
|
+
const expected = normalize(params.tlsFingerprint);
|
|
1175
|
+
if (!expected) return;
|
|
1176
|
+
if (!fingerprint) return /* @__PURE__ */ new Error("Missing server TLS fingerprint");
|
|
1177
|
+
if (fingerprint !== expected) return /* @__PURE__ */ new Error("Server TLS fingerprint mismatch");
|
|
1178
|
+
};
|
|
1169
1179
|
}
|
|
1180
|
+
return {
|
|
1181
|
+
options,
|
|
1182
|
+
validateSocket: (socket) => {
|
|
1183
|
+
if (!params.tlsFingerprint) return null;
|
|
1184
|
+
const expected = normalize(params.tlsFingerprint);
|
|
1185
|
+
if (!expected) return /* @__PURE__ */ new Error("gateway tls fingerprint missing");
|
|
1186
|
+
const rawSocket = socket["_socket"];
|
|
1187
|
+
if (!rawSocket || typeof rawSocket.getPeerCertificate !== "function") return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
|
|
1188
|
+
const cert = rawSocket.getPeerCertificate();
|
|
1189
|
+
const fingerprint = normalize(cert?.fingerprint256 ?? "");
|
|
1190
|
+
if (!fingerprint) return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
|
|
1191
|
+
return fingerprint === expected ? null : /* @__PURE__ */ new Error("gateway tls fingerprint mismatch");
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
//#endregion
|
|
1196
|
+
//#region packages/gateway-client/src/client.ts
|
|
1197
|
+
const DEFAULT_HOST_DEPS = {
|
|
1198
|
+
loadOrCreateDeviceIdentity: () => void 0,
|
|
1199
|
+
signDevicePayload: () => {
|
|
1200
|
+
throw new Error("GatewayClient device signature dependency is not configured");
|
|
1201
|
+
},
|
|
1202
|
+
publicKeyRawBase64UrlFromPem: () => {
|
|
1203
|
+
throw new Error("GatewayClient public key dependency is not configured");
|
|
1204
|
+
},
|
|
1205
|
+
loadDeviceAuthToken: () => null,
|
|
1206
|
+
storeDeviceAuthToken: () => {},
|
|
1207
|
+
clearDeviceAuthToken: () => {},
|
|
1208
|
+
beforeConnect: () => {},
|
|
1209
|
+
registerGatewayLoopbackBypass: () => void 0,
|
|
1210
|
+
logDebug: () => {},
|
|
1211
|
+
logError: () => {},
|
|
1212
|
+
redactForLog: (message) => message,
|
|
1213
|
+
normalizeTlsFingerprint: normalizeFingerprint
|
|
1170
1214
|
};
|
|
1171
|
-
|
|
1215
|
+
function resolveHostDeps(overrides) {
|
|
1216
|
+
return Object.fromEntries(Object.entries(DEFAULT_HOST_DEPS).map(([key, fallback]) => [key, overrides?.[key] ?? fallback]));
|
|
1217
|
+
}
|
|
1218
|
+
const DEFAULT_GATEWAY_CLIENT_URL = "ws://127.0.0.1:18789";
|
|
1219
|
+
const DEFAULT_CLIENT_VERSION = "0.0.0";
|
|
1220
|
+
const MAX_UPGRADE_ERROR_BODY_BYTES = 2 * 1024;
|
|
1221
|
+
const UPGRADE_ERROR_BODY_TIMEOUT_MS = 1e3;
|
|
1222
|
+
async function readUpgradeErrorBody(response) {
|
|
1223
|
+
return await new Promise((resolve) => {
|
|
1224
|
+
const chunks = [];
|
|
1225
|
+
let totalBytes = 0;
|
|
1226
|
+
let settled = false;
|
|
1227
|
+
const finish = () => {
|
|
1228
|
+
if (settled) return;
|
|
1229
|
+
settled = true;
|
|
1230
|
+
clearTimeout(timer);
|
|
1231
|
+
response.off("data", onData);
|
|
1232
|
+
response.off("end", finish);
|
|
1233
|
+
response.off("error", finish);
|
|
1234
|
+
response.off("aborted", finish);
|
|
1235
|
+
resolve(Buffer.concat(chunks, totalBytes).toString("utf8").replace(/\s+/gu, " ").trim());
|
|
1236
|
+
};
|
|
1237
|
+
const stop = () => {
|
|
1238
|
+
finish();
|
|
1239
|
+
response.destroy();
|
|
1240
|
+
};
|
|
1241
|
+
const onData = (chunk) => {
|
|
1242
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1243
|
+
const remaining = MAX_UPGRADE_ERROR_BODY_BYTES - totalBytes;
|
|
1244
|
+
if (remaining > 0) {
|
|
1245
|
+
const prefix = buffer.subarray(0, remaining);
|
|
1246
|
+
chunks.push(prefix);
|
|
1247
|
+
totalBytes += prefix.byteLength;
|
|
1248
|
+
}
|
|
1249
|
+
if (buffer.byteLength >= remaining) stop();
|
|
1250
|
+
};
|
|
1251
|
+
const timer = setTimeout(stop, UPGRADE_ERROR_BODY_TIMEOUT_MS);
|
|
1252
|
+
timer.unref?.();
|
|
1253
|
+
response.on("data", onData);
|
|
1254
|
+
response.once("end", finish);
|
|
1255
|
+
response.once("error", finish);
|
|
1256
|
+
response.once("aborted", finish);
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
var GatewayClientRequestTimeoutError = class extends GatewayProtocolRequestTimeoutError {
|
|
1172
1260
|
constructor(params) {
|
|
1173
|
-
super(`gateway request timeout for ${params.method}`);
|
|
1261
|
+
super(params, `gateway request timeout for ${params.method}`);
|
|
1174
1262
|
this.name = "GatewayClientRequestTimeoutError";
|
|
1175
|
-
this.method = params.method;
|
|
1176
|
-
this.timeoutMs = params.timeoutMs;
|
|
1177
|
-
this.requestSent = params.requestSent;
|
|
1178
|
-
}
|
|
1179
|
-
};
|
|
1180
|
-
var GatewayClientTransientPreHelloCloseError = class extends Error {
|
|
1181
|
-
constructor() {
|
|
1182
|
-
super("gateway transient pre-hello clean close");
|
|
1183
|
-
this.name = "GatewayClientTransientPreHelloCloseError";
|
|
1184
1263
|
}
|
|
1185
1264
|
};
|
|
1186
|
-
var
|
|
1187
|
-
var GatewayClientTransportPolicyError = class extends GatewayClientSocketFactoryConfigurationError {};
|
|
1265
|
+
var GatewayClientTransportPolicyError = class extends GatewayWebSocketTransportConfigurationError {};
|
|
1188
1266
|
const GATEWAY_CONNECT_ASSEMBLY_ERROR = Symbol("gateway.connectAssemblyError");
|
|
1189
1267
|
function markGatewayConnectAssemblyError(error) {
|
|
1190
1268
|
Object.defineProperty(error, GATEWAY_CONNECT_ASSEMBLY_ERROR, {
|
|
@@ -1206,10 +1284,20 @@ function formatGatewayClientErrorForLog(err) {
|
|
|
1206
1284
|
const FORCE_STOP_TERMINATE_GRACE_MS = 250;
|
|
1207
1285
|
const STOP_AND_WAIT_TIMEOUT_MS = 1e3;
|
|
1208
1286
|
const MAX_SUPPRESSED_TRANSIENT_PRE_HELLO_CLEAN_CLOSES = 1;
|
|
1287
|
+
function resolveLegacyNodePlatform(platform) {
|
|
1288
|
+
switch (platform) {
|
|
1289
|
+
case "macos": return "darwin";
|
|
1290
|
+
case "windows": return "win32";
|
|
1291
|
+
default: return;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1209
1294
|
var GatewayClient = class {
|
|
1210
1295
|
constructor(opts) {
|
|
1211
1296
|
this.ws = null;
|
|
1212
1297
|
this.stopped = false;
|
|
1298
|
+
this.useLegacyNodeProtocolEnvelope = false;
|
|
1299
|
+
this.nodeProtocolTransitionPending = false;
|
|
1300
|
+
this.suppressNextHelloCallback = false;
|
|
1213
1301
|
this.pendingDeviceTokenRetry = false;
|
|
1214
1302
|
this.deviceTokenRetryBudgetUsed = false;
|
|
1215
1303
|
this.approvalRuntimeTokenCompatibilityDisabled = false;
|
|
@@ -1225,7 +1313,7 @@ var GatewayClient = class {
|
|
|
1225
1313
|
...opts,
|
|
1226
1314
|
deviceIdentity: opts.deviceIdentity === null ? void 0 : opts.deviceIdentity ?? this.deps.loadOrCreateDeviceIdentity()
|
|
1227
1315
|
};
|
|
1228
|
-
this.requestTimeoutMs = typeof opts.requestTimeoutMs === "number" && Number.isFinite(opts.requestTimeoutMs) ?
|
|
1316
|
+
this.requestTimeoutMs = typeof opts.requestTimeoutMs === "number" && Number.isFinite(opts.requestTimeoutMs) ? opts.requestTimeoutMs : DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS;
|
|
1229
1317
|
const connectChallengeTimeoutMs = resolveConnectChallengeTimeoutMs(this.opts.connectChallengeTimeoutMs, {
|
|
1230
1318
|
env: this.opts.env,
|
|
1231
1319
|
configuredTimeoutMs: this.opts.preauthHandshakeTimeoutMs
|
|
@@ -1264,7 +1352,13 @@ var GatewayClient = class {
|
|
|
1264
1352
|
};
|
|
1265
1353
|
},
|
|
1266
1354
|
onConnectHello: (hello, context) => this.handleConnectHello(hello, context.plan),
|
|
1267
|
-
onHello: (hello) =>
|
|
1355
|
+
onHello: (hello) => {
|
|
1356
|
+
if (this.suppressNextHelloCallback) {
|
|
1357
|
+
this.suppressNextHelloCallback = false;
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
this.opts.onHelloOk?.(hello);
|
|
1361
|
+
},
|
|
1268
1362
|
onConnectFailure: (error, context) => this.handleConnectRequestFailure(error, context.plan),
|
|
1269
1363
|
resolveClose: (context) => this.resolveClose(context),
|
|
1270
1364
|
onClose: (context, decision) => {
|
|
@@ -1294,7 +1388,7 @@ var GatewayClient = class {
|
|
|
1294
1388
|
maxMs: 3e4
|
|
1295
1389
|
},
|
|
1296
1390
|
requestTimeoutMs: this.requestTimeoutMs,
|
|
1297
|
-
shouldRetrySocketFactoryError: (error) => !(error instanceof
|
|
1391
|
+
shouldRetrySocketFactoryError: (error) => !(error instanceof GatewayWebSocketTransportConfigurationError) && !(error instanceof SyntaxError) && !(error instanceof TypeError) && !(error instanceof RangeError),
|
|
1298
1392
|
rethrowSocketFactoryError: (error) => error instanceof GatewayClientTransportPolicyError
|
|
1299
1393
|
});
|
|
1300
1394
|
}
|
|
@@ -1310,7 +1404,9 @@ var GatewayClient = class {
|
|
|
1310
1404
|
this.opts = {
|
|
1311
1405
|
...this.opts,
|
|
1312
1406
|
caps: [...manifest.caps],
|
|
1313
|
-
commands: [...manifest.commands]
|
|
1407
|
+
commands: [...manifest.commands],
|
|
1408
|
+
computerUse: manifest.computerUse === void 0 ? void 0 : structuredClone(manifest.computerUse),
|
|
1409
|
+
workerRuns: manifest.workerRuns ? structuredClone(manifest.workerRuns) : void 0
|
|
1314
1410
|
};
|
|
1315
1411
|
if (!this.stopped) this.protocol.closeSocket(1012, "node manifest changed");
|
|
1316
1412
|
}
|
|
@@ -1320,36 +1416,22 @@ var GatewayClient = class {
|
|
|
1320
1416
|
}
|
|
1321
1417
|
createSocket(handlers) {
|
|
1322
1418
|
const url = this.opts.url ?? DEFAULT_GATEWAY_CLIENT_URL;
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1419
|
+
const handshakeTimeoutMs = resolvePreauthHandshakeTimeoutMs({
|
|
1420
|
+
env: this.opts.env,
|
|
1421
|
+
configuredTimeoutMs: this.opts.preauthHandshakeTimeoutMs
|
|
1422
|
+
});
|
|
1423
|
+
const transport = resolveGatewayWebSocketTransport({
|
|
1424
|
+
url,
|
|
1425
|
+
tlsFingerprint: this.opts.tlsFingerprint,
|
|
1426
|
+
env: this.opts.env,
|
|
1427
|
+
normalizeTlsFingerprint: this.deps.normalizeTlsFingerprint,
|
|
1428
|
+
options: {
|
|
1429
|
+
maxPayload: 25 * 1024 * 1024,
|
|
1430
|
+
handshakeTimeout: handshakeTimeoutMs,
|
|
1431
|
+
...this.opts.origin ? { origin: this.opts.origin } : {}
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1333
1434
|
this.deps.beforeConnect();
|
|
1334
|
-
const wsOptions = {
|
|
1335
|
-
maxPayload: 25 * 1024 * 1024,
|
|
1336
|
-
handshakeTimeout: resolvePreauthHandshakeTimeoutMs({
|
|
1337
|
-
env: this.opts.env,
|
|
1338
|
-
configuredTimeoutMs: this.opts.preauthHandshakeTimeoutMs
|
|
1339
|
-
}),
|
|
1340
|
-
...this.opts.origin ? { origin: this.opts.origin } : {}
|
|
1341
|
-
};
|
|
1342
|
-
if (usesTls && this.opts.tlsFingerprint) {
|
|
1343
|
-
wsOptions.rejectUnauthorized = false;
|
|
1344
|
-
wsOptions.checkServerIdentity = (_hostValue, cert) => {
|
|
1345
|
-
const fingerprintValue = typeof cert === "object" && cert && "fingerprint256" in cert ? cert.fingerprint256 ?? "" : "";
|
|
1346
|
-
const fingerprint = this.deps.normalizeTlsFingerprint(typeof fingerprintValue === "string" ? fingerprintValue : "");
|
|
1347
|
-
const expected = this.deps.normalizeTlsFingerprint(this.opts.tlsFingerprint ?? "");
|
|
1348
|
-
if (!expected) return;
|
|
1349
|
-
if (!fingerprint) return /* @__PURE__ */ new Error("Missing server TLS fingerprint");
|
|
1350
|
-
if (fingerprint !== expected) return /* @__PURE__ */ new Error("Server TLS fingerprint mismatch");
|
|
1351
|
-
};
|
|
1352
|
-
}
|
|
1353
1435
|
let ws;
|
|
1354
1436
|
let unregisterGatewayLoopbackBypass;
|
|
1355
1437
|
try {
|
|
@@ -1358,7 +1440,7 @@ var GatewayClient = class {
|
|
|
1358
1440
|
throw new GatewayClientTransportPolicyError(error instanceof Error ? error.message : String(error));
|
|
1359
1441
|
}
|
|
1360
1442
|
try {
|
|
1361
|
-
ws = new WebSocket(url,
|
|
1443
|
+
ws = new WebSocket(url, transport.options);
|
|
1362
1444
|
ws.binaryType = "nodebuffer";
|
|
1363
1445
|
} catch (error) {
|
|
1364
1446
|
throw error instanceof Error ? error : new Error(String(error));
|
|
@@ -1367,15 +1449,14 @@ var GatewayClient = class {
|
|
|
1367
1449
|
}
|
|
1368
1450
|
this.ws = ws;
|
|
1369
1451
|
this.transportValidated = false;
|
|
1452
|
+
let upgradeError;
|
|
1370
1453
|
ws.on("open", () => {
|
|
1371
1454
|
handlers.open();
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
return;
|
|
1378
|
-
}
|
|
1455
|
+
const tlsError = transport.validateSocket(ws);
|
|
1456
|
+
if (tlsError) {
|
|
1457
|
+
handlers.error(tlsError);
|
|
1458
|
+
ws.close(1008, tlsError.message);
|
|
1459
|
+
return;
|
|
1379
1460
|
}
|
|
1380
1461
|
this.transportValidated = true;
|
|
1381
1462
|
});
|
|
@@ -1386,7 +1467,25 @@ var GatewayClient = class {
|
|
|
1386
1467
|
this.resolvePendingStop(ws);
|
|
1387
1468
|
handlers.close(code, reasonText);
|
|
1388
1469
|
});
|
|
1470
|
+
ws.on("unexpected-response", (request, response) => {
|
|
1471
|
+
readUpgradeErrorBody(response).then((body) => {
|
|
1472
|
+
const statusCode = response.statusCode;
|
|
1473
|
+
upgradeError = new GatewayClientRequestError({
|
|
1474
|
+
code: "UNAVAILABLE",
|
|
1475
|
+
message: `gateway rejected websocket upgrade (HTTP ${statusCode ?? "unknown"})${body ? `: ${body}` : ""}`,
|
|
1476
|
+
retryable: true,
|
|
1477
|
+
details: {
|
|
1478
|
+
reason: "websocket-upgrade-rejected",
|
|
1479
|
+
...statusCode === void 0 ? {} : { httpStatus: statusCode }
|
|
1480
|
+
}
|
|
1481
|
+
});
|
|
1482
|
+
handlers.error(upgradeError);
|
|
1483
|
+
request.destroy();
|
|
1484
|
+
ws.close();
|
|
1485
|
+
});
|
|
1486
|
+
});
|
|
1389
1487
|
ws.on("error", (err) => {
|
|
1488
|
+
if (upgradeError) return;
|
|
1390
1489
|
this.logDebug(`gateway client error: ${formatGatewayClientErrorForLog(err)}`);
|
|
1391
1490
|
handlers.error(err instanceof Error ? err : new Error(String(err)));
|
|
1392
1491
|
});
|
|
@@ -1486,22 +1585,34 @@ var GatewayClient = class {
|
|
|
1486
1585
|
storedScopes,
|
|
1487
1586
|
defaultScopes: ["operator.admin"]
|
|
1488
1587
|
});
|
|
1489
|
-
const
|
|
1588
|
+
const clientMode = this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND;
|
|
1589
|
+
const clientId = this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT;
|
|
1590
|
+
const isBuiltInNodeHost = role === "node" && clientMode === GATEWAY_CLIENT_MODES.NODE && clientId === GATEWAY_CLIENT_NAMES.NODE_HOST;
|
|
1591
|
+
const negotiatesNodeProtocol = this.shouldNegotiateLegacyNodeProtocol();
|
|
1592
|
+
const useLegacyNodeProtocolEnvelope = isBuiltInNodeHost && (this.useLegacyNodeProtocolEnvelope || this.opts.maxProtocol === MIN_NODE_PROTOCOL_VERSION && (this.opts.minProtocol ?? MIN_NODE_PROTOCOL_VERSION) <= MIN_NODE_PROTOCOL_VERSION);
|
|
1593
|
+
const minProtocol = useLegacyNodeProtocolEnvelope ? MIN_NODE_PROTOCOL_VERSION : negotiatesNodeProtocol ? PROTOCOL_VERSION : this.opts.minProtocol ?? (clientMode === GATEWAY_CLIENT_MODES.PROBE ? MIN_PROBE_PROTOCOL_VERSION : role === "node" && clientMode === GATEWAY_CLIENT_MODES.NODE ? MIN_NODE_PROTOCOL_VERSION : MIN_CLIENT_PROTOCOL_VERSION);
|
|
1594
|
+
const maxProtocol = useLegacyNodeProtocolEnvelope ? MIN_NODE_PROTOCOL_VERSION : negotiatesNodeProtocol ? PROTOCOL_VERSION : this.opts.maxProtocol ?? PROTOCOL_VERSION;
|
|
1595
|
+
const configuredPlatform = this.opts.platform ?? process.platform;
|
|
1596
|
+
const platform = useLegacyNodeProtocolEnvelope ? resolveLegacyNodePlatform(configuredPlatform) ?? configuredPlatform : configuredPlatform;
|
|
1597
|
+
const deviceFamily = useLegacyNodeProtocolEnvelope ? void 0 : this.opts.deviceFamily;
|
|
1490
1598
|
return {
|
|
1491
1599
|
params: {
|
|
1492
|
-
minProtocol
|
|
1493
|
-
maxProtocol
|
|
1600
|
+
minProtocol,
|
|
1601
|
+
maxProtocol,
|
|
1494
1602
|
client: {
|
|
1495
|
-
id:
|
|
1603
|
+
id: clientId,
|
|
1496
1604
|
displayName: this.opts.clientDisplayName,
|
|
1497
1605
|
version: this.opts.clientVersion ?? DEFAULT_CLIENT_VERSION,
|
|
1606
|
+
buildId: this.opts.clientBuildId,
|
|
1498
1607
|
platform,
|
|
1499
|
-
deviceFamily
|
|
1500
|
-
mode:
|
|
1608
|
+
deviceFamily,
|
|
1609
|
+
mode: clientMode,
|
|
1501
1610
|
instanceId: this.opts.instanceId
|
|
1502
1611
|
},
|
|
1503
1612
|
caps: Array.isArray(this.opts.caps) ? this.opts.caps : [],
|
|
1504
1613
|
commands: Array.isArray(this.opts.commands) ? this.opts.commands : void 0,
|
|
1614
|
+
computerUse: useLegacyNodeProtocolEnvelope ? void 0 : this.opts.computerUse,
|
|
1615
|
+
workerRuns: useLegacyNodeProtocolEnvelope ? void 0 : this.opts.workerRuns,
|
|
1505
1616
|
permissions: this.opts.permissions && typeof this.opts.permissions === "object" ? this.opts.permissions : void 0,
|
|
1506
1617
|
pathEnv: this.opts.pathEnv,
|
|
1507
1618
|
auth,
|
|
@@ -1513,30 +1624,47 @@ var GatewayClient = class {
|
|
|
1513
1624
|
scopes,
|
|
1514
1625
|
signatureToken,
|
|
1515
1626
|
signedAtMs,
|
|
1516
|
-
platform
|
|
1627
|
+
platform,
|
|
1628
|
+
deviceFamily,
|
|
1629
|
+
clientMode
|
|
1517
1630
|
})
|
|
1518
1631
|
},
|
|
1519
1632
|
authApprovalRuntimeToken,
|
|
1520
1633
|
authAgentRuntimeIdentityToken,
|
|
1521
1634
|
resolvedDeviceToken,
|
|
1635
|
+
storedScopes,
|
|
1522
1636
|
storedToken,
|
|
1523
1637
|
usingStoredDeviceToken
|
|
1524
1638
|
};
|
|
1525
1639
|
}
|
|
1640
|
+
shouldNegotiateLegacyNodeProtocol() {
|
|
1641
|
+
if (this.opts.role !== "node" || this.opts.mode !== GATEWAY_CLIENT_MODES.NODE || this.opts.clientName !== GATEWAY_CLIENT_NAMES.NODE_HOST) return false;
|
|
1642
|
+
return (this.opts.minProtocol ?? MIN_NODE_PROTOCOL_VERSION) === MIN_NODE_PROTOCOL_VERSION && (this.opts.maxProtocol ?? PROTOCOL_VERSION) === PROTOCOL_VERSION;
|
|
1643
|
+
}
|
|
1644
|
+
shouldRetryWithLegacyNodeProtocol(error) {
|
|
1645
|
+
if (this.useLegacyNodeProtocolEnvelope || !this.shouldNegotiateLegacyNodeProtocol() || !(error instanceof GatewayClientRequestError)) return false;
|
|
1646
|
+
const detailCode = readConnectErrorDetailCode(error.details);
|
|
1647
|
+
return error.details?.expectedProtocol === MIN_NODE_PROTOCOL_VERSION && (detailCode === ConnectErrorDetailCodes.PROTOCOL_MISMATCH || normalizeGatewayErrorText(error.message).includes("protocol mismatch"));
|
|
1648
|
+
}
|
|
1649
|
+
shouldRetryWithCurrentNodeProtocol(error) {
|
|
1650
|
+
if (!this.useLegacyNodeProtocolEnvelope || !this.shouldNegotiateLegacyNodeProtocol() || !(error instanceof GatewayClientRequestError)) return false;
|
|
1651
|
+
const detailCode = readConnectErrorDetailCode(error.details);
|
|
1652
|
+
return error.details?.expectedProtocol === PROTOCOL_VERSION && (detailCode === ConnectErrorDetailCodes.PROTOCOL_MISMATCH || normalizeGatewayErrorText(error.message).includes("protocol mismatch"));
|
|
1653
|
+
}
|
|
1526
1654
|
buildDeviceConnectParams(params) {
|
|
1527
1655
|
if (!this.opts.deviceIdentity) return;
|
|
1528
|
-
const { nonce, role, scopes, signatureToken, signedAtMs, platform } = params;
|
|
1656
|
+
const { nonce, role, scopes, signatureToken, signedAtMs, platform, deviceFamily, clientMode } = params;
|
|
1529
1657
|
const payload = buildDeviceAuthPayloadV3({
|
|
1530
1658
|
deviceId: this.opts.deviceIdentity.deviceId,
|
|
1531
1659
|
clientId: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
|
|
1532
|
-
clientMode
|
|
1660
|
+
clientMode,
|
|
1533
1661
|
role,
|
|
1534
1662
|
scopes,
|
|
1535
1663
|
signedAtMs,
|
|
1536
1664
|
token: signatureToken ?? null,
|
|
1537
1665
|
nonce,
|
|
1538
1666
|
platform,
|
|
1539
|
-
deviceFamily
|
|
1667
|
+
deviceFamily
|
|
1540
1668
|
});
|
|
1541
1669
|
const signature = this.deps.signDevicePayload(this.opts.deviceIdentity.privateKeyPem, payload);
|
|
1542
1670
|
return {
|
|
@@ -1548,24 +1676,66 @@ var GatewayClient = class {
|
|
|
1548
1676
|
};
|
|
1549
1677
|
}
|
|
1550
1678
|
handleConnectHello(helloOk, assembled) {
|
|
1679
|
+
const reconnectWithCurrentNodeProtocol = this.useLegacyNodeProtocolEnvelope && this.shouldNegotiateLegacyNodeProtocol() && helloOk.protocol > MIN_NODE_PROTOCOL_VERSION;
|
|
1680
|
+
if (reconnectWithCurrentNodeProtocol) this.useLegacyNodeProtocolEnvelope = false;
|
|
1681
|
+
this.nodeProtocolTransitionPending = false;
|
|
1551
1682
|
this.pendingDeviceTokenRetry = false;
|
|
1552
1683
|
this.deviceTokenRetryBudgetUsed = false;
|
|
1553
1684
|
this.suppressedTransientPreHelloCleanCloses = 0;
|
|
1554
1685
|
const role = this.opts.role ?? "operator";
|
|
1555
1686
|
const authInfo = helloOk.auth;
|
|
1556
|
-
if (authInfo?.deviceToken && this.opts.deviceIdentity)
|
|
1557
|
-
|
|
1558
|
-
role: authInfo.
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1687
|
+
if (authInfo?.deviceToken && this.opts.deviceIdentity) {
|
|
1688
|
+
const tokenRole = authInfo.role ?? role;
|
|
1689
|
+
const scopes = tokenRole === role && authInfo.deviceToken === assembled.storedToken ? assembled.storedScopes ?? authInfo.scopes ?? [] : authInfo.scopes ?? [];
|
|
1690
|
+
this.deps.storeDeviceAuthToken({
|
|
1691
|
+
deviceId: this.opts.deviceIdentity.deviceId,
|
|
1692
|
+
role: tokenRole,
|
|
1693
|
+
token: authInfo.deviceToken,
|
|
1694
|
+
scopes,
|
|
1695
|
+
env: this.opts.env
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
if (this.opts.preferBootstrapToken) {
|
|
1699
|
+
this.opts.token = void 0;
|
|
1700
|
+
this.opts.bootstrapToken = void 0;
|
|
1701
|
+
this.opts.password = void 0;
|
|
1702
|
+
this.opts.preferBootstrapToken = false;
|
|
1703
|
+
}
|
|
1563
1704
|
this.tickIntervalMs = typeof helloOk.policy?.tickIntervalMs === "number" ? helloOk.policy.tickIntervalMs : 3e4;
|
|
1705
|
+
if (reconnectWithCurrentNodeProtocol) {
|
|
1706
|
+
this.suppressNextHelloCallback = true;
|
|
1707
|
+
this.protocol.resetReconnectBackoff(250);
|
|
1708
|
+
this.protocol.closeSocket(1012, "gateway protocol upgraded");
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1564
1711
|
this.lastTick = Date.now();
|
|
1565
1712
|
this.startTickWatch();
|
|
1566
1713
|
}
|
|
1567
1714
|
handleConnectRequestFailure(error, assembled) {
|
|
1715
|
+
if (this.shouldRetryWithCurrentNodeProtocol(error)) {
|
|
1716
|
+
const resetBackoff = !this.nodeProtocolTransitionPending;
|
|
1717
|
+
this.useLegacyNodeProtocolEnvelope = false;
|
|
1718
|
+
this.nodeProtocolTransitionPending = true;
|
|
1719
|
+
if (resetBackoff) this.protocol.resetReconnectBackoff(250);
|
|
1720
|
+
this.logDebug("gateway rejected protocol v3; retrying node host with protocol v4");
|
|
1721
|
+
return {
|
|
1722
|
+
closeCode: 1008,
|
|
1723
|
+
closeReason: "connect retry"
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
if (this.shouldRetryWithLegacyNodeProtocol(error)) {
|
|
1727
|
+
const resetBackoff = !this.nodeProtocolTransitionPending;
|
|
1728
|
+
this.useLegacyNodeProtocolEnvelope = true;
|
|
1729
|
+
this.nodeProtocolTransitionPending = true;
|
|
1730
|
+
if (resetBackoff) this.protocol.resetReconnectBackoff(250);
|
|
1731
|
+
this.logDebug("gateway rejected protocol v4; retrying node host with protocol v3");
|
|
1732
|
+
return {
|
|
1733
|
+
closeCode: 1008,
|
|
1734
|
+
closeReason: "connect retry"
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1568
1737
|
const role = this.opts.role ?? "operator";
|
|
1738
|
+
const detailCode = error instanceof GatewayClientRequestError ? readConnectErrorDetailCode(error.details) : null;
|
|
1569
1739
|
const shouldRetryWithDeviceToken = shouldRetryGatewayWithDeviceToken({
|
|
1570
1740
|
retryBudgetUsed: this.deviceTokenRetryBudgetUsed,
|
|
1571
1741
|
currentDeviceToken: assembled.resolvedDeviceToken,
|
|
@@ -1574,7 +1744,7 @@ var GatewayClient = class {
|
|
|
1574
1744
|
trustedEndpoint: this.isTrustedDeviceRetryEndpoint(),
|
|
1575
1745
|
errorDetails: error instanceof GatewayClientRequestError ? error.details : void 0
|
|
1576
1746
|
});
|
|
1577
|
-
if (this.opts.deviceIdentity && assembled.usingStoredDeviceToken &&
|
|
1747
|
+
if (this.opts.deviceIdentity && assembled.usingStoredDeviceToken && detailCode === ConnectErrorDetailCodes.AUTH_DEVICE_TOKEN_MISMATCH) {
|
|
1578
1748
|
const deviceId = this.opts.deviceIdentity.deviceId;
|
|
1579
1749
|
try {
|
|
1580
1750
|
this.deps.clearDeviceAuthToken({
|
|
@@ -1630,7 +1800,7 @@ var GatewayClient = class {
|
|
|
1630
1800
|
}
|
|
1631
1801
|
this.notifyConnectError(error);
|
|
1632
1802
|
const message = `gateway connect failed: ${formatGatewayClientErrorForLog(error)}`;
|
|
1633
|
-
if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE || isGatewayClientStoppedError(error)) this.logDebug(message);
|
|
1803
|
+
if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE || isGatewayClientStoppedError(error) || detailCode === ConnectErrorDetailCodes.AUTH_RATE_LIMITED) this.logDebug(message);
|
|
1634
1804
|
else this.logError(message);
|
|
1635
1805
|
return {
|
|
1636
1806
|
closeCode: 1008,
|
|
@@ -1651,7 +1821,7 @@ var GatewayClient = class {
|
|
|
1651
1821
|
return {
|
|
1652
1822
|
retry: true,
|
|
1653
1823
|
notify: true,
|
|
1654
|
-
pendingError: new
|
|
1824
|
+
pendingError: /* @__PURE__ */ new Error("gateway transient pre-hello clean close")
|
|
1655
1825
|
};
|
|
1656
1826
|
}
|
|
1657
1827
|
if (info.transientPreHelloCleanClose || context.connectRequestSent && !context.helloReceived && !context.connectFailure) {
|
|
@@ -1687,11 +1857,13 @@ var GatewayClient = class {
|
|
|
1687
1857
|
phase: context.helloReceived ? "post-hello" : "pre-hello",
|
|
1688
1858
|
socketOpened: context.socketOpened,
|
|
1689
1859
|
transportValidated: this.transportValidated,
|
|
1690
|
-
|
|
1860
|
+
connectRequestSent: context.connectRequestSent,
|
|
1861
|
+
transientPreHelloCleanClose: !context.helloReceived && context.code === 1e3 && context.reason === "",
|
|
1862
|
+
...context.connectFailure?.error ? { connectError: context.connectFailure.error } : {}
|
|
1691
1863
|
};
|
|
1692
1864
|
}
|
|
1693
1865
|
clearStaleDeviceTokenForClose(code, reason) {
|
|
1694
|
-
if (code !== 1008 || !
|
|
1866
|
+
if (code !== 1008 || !normalizeGatewayErrorText(reason).includes("device token mismatch") || this.opts.token || this.opts.password || !this.opts.deviceIdentity) return;
|
|
1695
1867
|
const deviceId = this.opts.deviceIdentity.deviceId;
|
|
1696
1868
|
const role = this.opts.role ?? "operator";
|
|
1697
1869
|
try {
|
|
@@ -1724,14 +1896,14 @@ var GatewayClient = class {
|
|
|
1724
1896
|
if (!params.authApprovalRuntimeToken) return false;
|
|
1725
1897
|
if (!(params.error instanceof GatewayClientRequestError)) return false;
|
|
1726
1898
|
if (params.error.gatewayCode !== "INVALID_REQUEST") return false;
|
|
1727
|
-
const message =
|
|
1899
|
+
const message = normalizeGatewayErrorText(params.error.message);
|
|
1728
1900
|
return message.includes("invalid connect params") && message.includes("approvalruntimetoken");
|
|
1729
1901
|
}
|
|
1730
1902
|
shouldFailClosedForUnsupportedAgentRuntimeIdentity(params) {
|
|
1731
1903
|
if (!params.authAgentRuntimeIdentityToken) return false;
|
|
1732
1904
|
if (!(params.error instanceof GatewayClientRequestError)) return false;
|
|
1733
1905
|
if (params.error.gatewayCode !== "INVALID_REQUEST") return false;
|
|
1734
|
-
const message =
|
|
1906
|
+
const message = normalizeGatewayErrorText(params.error.message);
|
|
1735
1907
|
return message.includes("invalid connect params") && message.includes("agentruntimeidentitytoken");
|
|
1736
1908
|
}
|
|
1737
1909
|
isTrustedDeviceRetryEndpoint() {
|
|
@@ -1739,7 +1911,7 @@ var GatewayClient = class {
|
|
|
1739
1911
|
try {
|
|
1740
1912
|
const parsed = new URL(rawUrl);
|
|
1741
1913
|
const protocol = parsed.protocol === "https:" ? "wss:" : parsed.protocol === "http:" ? "ws:" : parsed.protocol;
|
|
1742
|
-
if (
|
|
1914
|
+
if (isGatewayLoopbackHost(parsed.hostname)) return true;
|
|
1743
1915
|
return protocol === "wss:" && Boolean(this.opts.tlsFingerprint?.trim());
|
|
1744
1916
|
} catch {
|
|
1745
1917
|
return false;
|
|
@@ -1754,6 +1926,7 @@ var GatewayClient = class {
|
|
|
1754
1926
|
return selectGatewayConnectAuth({
|
|
1755
1927
|
token: this.opts.token,
|
|
1756
1928
|
bootstrapToken: this.opts.bootstrapToken,
|
|
1929
|
+
preferBootstrapToken: this.opts.preferBootstrapToken,
|
|
1757
1930
|
deviceToken: this.opts.deviceToken,
|
|
1758
1931
|
password: this.opts.password,
|
|
1759
1932
|
approvalRuntimeToken: this.approvalRuntimeTokenCompatibilityDisabled ? void 0 : this.opts.approvalRuntimeToken,
|
|
@@ -1778,21 +1951,9 @@ var GatewayClient = class {
|
|
|
1778
1951
|
if (gap > (typeof rawTimeoutMs === "number" && Number.isFinite(rawTimeoutMs) ? Math.max(1, rawTimeoutMs) : this.tickIntervalMs * 2)) this.protocol.closeSocket(4e3, "tick timeout");
|
|
1779
1952
|
}, interval);
|
|
1780
1953
|
}
|
|
1781
|
-
validateTlsFingerprint() {
|
|
1782
|
-
if (!this.opts.tlsFingerprint || !this.ws) return null;
|
|
1783
|
-
const expected = this.deps.normalizeTlsFingerprint(this.opts.tlsFingerprint);
|
|
1784
|
-
if (!expected) return /* @__PURE__ */ new Error("gateway tls fingerprint missing");
|
|
1785
|
-
const socket = this.ws["_socket"];
|
|
1786
|
-
if (!socket || typeof socket.getPeerCertificate !== "function") return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
|
|
1787
|
-
const cert = socket.getPeerCertificate();
|
|
1788
|
-
const fingerprint = this.deps.normalizeTlsFingerprint(cert?.fingerprint256 ?? "");
|
|
1789
|
-
if (!fingerprint) return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
|
|
1790
|
-
if (fingerprint !== expected) return /* @__PURE__ */ new Error("gateway tls fingerprint mismatch");
|
|
1791
|
-
return null;
|
|
1792
|
-
}
|
|
1793
1954
|
async request(method, params, opts) {
|
|
1794
1955
|
const expectFinal = opts?.expectFinal === true;
|
|
1795
|
-
const timeoutMs = opts?.timeoutMs === null ? null : typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) ?
|
|
1956
|
+
const timeoutMs = opts?.timeoutMs === null ? null : typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : expectFinal ? null : this.requestTimeoutMs;
|
|
1796
1957
|
return this.protocol.request(method, params, {
|
|
1797
1958
|
expectFinal,
|
|
1798
1959
|
timeoutMs,
|
|
@@ -1808,4 +1969,4 @@ function createGatewayRequestAbortError(method) {
|
|
|
1808
1969
|
return err;
|
|
1809
1970
|
}
|
|
1810
1971
|
//#endregion
|
|
1811
|
-
export { DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, GatewayBrowserDeviceAuthLifecycle, GatewayClient, GatewayClientRequestError, GatewayClientRequestTimeoutError, GatewaySessionMessageSubscriptionCoordinator, MAX_CONNECT_CHALLENGE_TIMEOUT_MS, MAX_SAFE_TIMEOUT_DELAY_MS, MIN_CONNECT_CHALLENGE_TIMEOUT_MS, addSafeTimeoutDelayGraceMs, buildDeviceAuthPayload, buildDeviceAuthPayloadV3, buildGatewayConnectAuth, clampConnectChallengeTimeoutMs, clearGatewayConnectTimeout, createSessionProjection, getConnectChallengeTimeoutMsFromEnv, getGatewaySessionMessageSubscriptionCoordinator, hasSessionProjectionAcceptedFinal, isGatewayConnectAssemblyError, isLocallyOptimisticSessionMessage, normalizeDeviceMetadataForAuth, normalizeSessionProjectionRunId, projectLiveSessionMessage, readSessionMessageIdentity, readSessionMessageSequence, reconcileSessionProjectionSnapshot, reduceSessionProjection, reduceSessionProjectionRunEvent, releaseGatewaySessionMessageSubscription, resetGatewaySessionMessageSubscriptionCoordinator, resolveConnectChallengeTimeoutMs, resolveFiniteTimeoutDelayMs, resolveGatewayConnectScopes, resolvePreauthHandshakeTimeoutMs, resolveSafeTimeoutDelayMs, selectGatewayConnectAuth, shouldRetryGatewayWithDeviceToken, startGatewayClientWhenEventLoopReady, startGatewayClientWithReadinessWait, startGatewayConnectTimeout, waitForEventLoopReady };
|
|
1972
|
+
export { DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, GatewayBrowserDeviceAuthLifecycle, GatewayClient, GatewayClientRequestError, GatewayClientRequestTimeoutError, GatewayScopeUpgrade, GatewaySessionMessageSubscriptionCoordinator, MAX_CONNECT_CHALLENGE_TIMEOUT_MS, MAX_SAFE_TIMEOUT_DELAY_MS, MIN_CONNECT_CHALLENGE_TIMEOUT_MS, addSafeTimeoutDelayGraceMs, buildDeviceAuthPayload, buildDeviceAuthPayloadV3, buildGatewayConnectAuth, clampConnectChallengeTimeoutMs, clearGatewayConnectTimeout, createSessionProjection, gatewayCredentialScope, gatewayOriginScope, getConnectChallengeTimeoutMsFromEnv, getGatewaySessionMessageSubscriptionCoordinator, hasSessionProjectionAcceptedFinal, isGatewayConnectAssemblyError, isLocallyOptimisticSessionMessage, normalizeDeviceMetadataForAuth, normalizeSessionProjectionRunId, projectLiveSessionMessage, readSessionMessageIdentity, readSessionMessageSequence, reconcileSessionProjectionSnapshot, reduceSessionProjection, reduceSessionProjectionRunEvent, releaseGatewaySessionMessageSubscription, resetGatewaySessionMessageSubscriptionCoordinator, resolveConnectChallengeTimeoutMs, resolveFiniteTimeoutDelayMs, resolveGatewayConnectScopes, resolvePreauthHandshakeTimeoutMs, resolveSafeTimeoutDelayMs, selectGatewayConnectAuth, shouldRetryGatewayWithDeviceToken, startGatewayClientWhenEventLoopReady, startGatewayClientWithReadinessWait, startGatewayConnectTimeout, waitForEventLoopReady };
|