@openclaw/gateway-client 2026.7.2-beta.7 → 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 +16 -3
- package/dist/browser-device-auth-Dz6H7eXy.d.mts +112 -0
- package/dist/browser.d.mts +6 -4
- package/dist/browser.mjs +3 -3
- package/dist/index.d.mts +5 -3
- package/dist/index.mjs +336 -147
- package/dist/{protocol-client-BfBHwA5H.d.mts → protocol-client-BbN-I582.d.mts} +21 -31
- package/dist/protocol-request-D4mVhLxE.d.mts +32 -0
- package/dist/{readiness-BJT_oUZE.d.mts → readiness--KX7i8Mq.d.mts} +29 -7
- 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-DrhzqL6v.d.mts → session-subscriptions-BTNpXybo.d.mts} +6 -110
- package/dist/{session-subscriptions-BV7MxYiL.mjs → session-subscriptions-uKmTyc3P.mjs} +280 -151
- 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) {
|
|
@@ -1036,7 +1050,7 @@ function isLoopbackIpAddress(raw) {
|
|
|
1036
1050
|
}
|
|
1037
1051
|
//#endregion
|
|
1038
1052
|
//#region packages/gateway-client/src/client-address-utils.ts
|
|
1039
|
-
function
|
|
1053
|
+
function normalizeGatewayErrorText(value) {
|
|
1040
1054
|
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
1041
1055
|
}
|
|
1042
1056
|
function isSensitiveUrlQueryParamName(key) {
|
|
@@ -1063,34 +1077,34 @@ function parseGatewayIpAddress(host) {
|
|
|
1063
1077
|
return normalized ? parseCanonicalIpAddress(normalized) : void 0;
|
|
1064
1078
|
}
|
|
1065
1079
|
//#endregion
|
|
1066
|
-
//#region packages/gateway-client/src/
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
}
|
|
1078
|
-
publicKeyRawBase64UrlFromPem: () => {
|
|
1079
|
-
throw new Error("GatewayClient public key dependency is not configured");
|
|
1080
|
-
},
|
|
1081
|
-
loadDeviceAuthToken: () => null,
|
|
1082
|
-
storeDeviceAuthToken: () => {},
|
|
1083
|
-
clearDeviceAuthToken: () => {},
|
|
1084
|
-
beforeConnect: () => {},
|
|
1085
|
-
registerGatewayLoopbackBypass: () => void 0,
|
|
1086
|
-
logDebug: () => {},
|
|
1087
|
-
logError: () => {},
|
|
1088
|
-
redactForLog: (message) => message,
|
|
1089
|
-
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
|
+
}
|
|
1090
1092
|
};
|
|
1091
|
-
|
|
1092
|
-
|
|
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:";
|
|
1093
1105
|
}
|
|
1106
|
+
//#endregion
|
|
1107
|
+
//#region packages/gateway-client/src/websocket-transport.ts
|
|
1094
1108
|
const PRIVATE_OR_LOOPBACK_IPV4_RANGES = /* @__PURE__ */ new Set([
|
|
1095
1109
|
"loopback",
|
|
1096
1110
|
"private",
|
|
@@ -1106,19 +1120,16 @@ const PRIVATE_OR_LOOPBACK_IPV6_RANGES = /* @__PURE__ */ new Set([
|
|
|
1106
1120
|
function isPrivateOrLoopbackIpAddress(address) {
|
|
1107
1121
|
return (address.kind() === "ipv4" ? PRIVATE_OR_LOOPBACK_IPV4_RANGES : PRIVATE_OR_LOOPBACK_IPV6_RANGES).has(address.range());
|
|
1108
1122
|
}
|
|
1109
|
-
function
|
|
1123
|
+
function isGatewayLoopbackHost(host) {
|
|
1110
1124
|
const parsed = parseHostForAddressChecks(host);
|
|
1111
|
-
|
|
1112
|
-
if (parsed.isLocalhost) return true;
|
|
1113
|
-
return isLoopbackIpAddress(parsed.unbracketedHost);
|
|
1125
|
+
return Boolean(parsed && (parsed.isLocalhost || isLoopbackIpAddress(parsed.unbracketedHost)));
|
|
1114
1126
|
}
|
|
1115
1127
|
function isPrivateOrLoopbackHost(host) {
|
|
1116
1128
|
const parsed = parseHostForAddressChecks(host);
|
|
1117
1129
|
if (!parsed) return false;
|
|
1118
1130
|
if (parsed.isLocalhost) return true;
|
|
1119
1131
|
const address = parseGatewayIpAddress(parsed.unbracketedHost);
|
|
1120
|
-
|
|
1121
|
-
return isPrivateOrLoopbackIpAddress(address);
|
|
1132
|
+
return Boolean(address && isPrivateOrLoopbackIpAddress(address));
|
|
1122
1133
|
}
|
|
1123
1134
|
function isTrustedPlaintextWebSocketHost(hostname) {
|
|
1124
1135
|
if (isPrivateOrLoopbackHost(hostname)) return true;
|
|
@@ -1131,7 +1142,7 @@ function isSecureWebSocketUrl(rawUrl, options) {
|
|
|
1131
1142
|
const protocol = url.protocol === "https:" ? "wss:" : url.protocol === "http:" ? "ws:" : url.protocol;
|
|
1132
1143
|
if (protocol === "wss:") return true;
|
|
1133
1144
|
if (protocol !== "ws:") return false;
|
|
1134
|
-
if (
|
|
1145
|
+
if (isGatewayLoopbackHost(url.hostname) || isTrustedPlaintextWebSocketHost(url.hostname)) return true;
|
|
1135
1146
|
if (options?.allowPrivateWs === true) {
|
|
1136
1147
|
const hostForIpCheck = url.hostname.startsWith("[") && url.hostname.endsWith("]") ? url.hostname.slice(1, -1) : url.hostname;
|
|
1137
1148
|
return isPrivateOrLoopbackHost(url.hostname) || parseGatewayIpAddress(hostForIpCheck) === void 0;
|
|
@@ -1141,28 +1152,117 @@ function isSecureWebSocketUrl(rawUrl, options) {
|
|
|
1141
1152
|
return false;
|
|
1142
1153
|
}
|
|
1143
1154
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
});
|
|
1155
|
-
|
|
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
|
+
};
|
|
1156
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
|
|
1157
1214
|
};
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
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 {
|
|
1260
|
+
constructor(params) {
|
|
1261
|
+
super(params, `gateway request timeout for ${params.method}`);
|
|
1262
|
+
this.name = "GatewayClientRequestTimeoutError";
|
|
1162
1263
|
}
|
|
1163
1264
|
};
|
|
1164
|
-
var
|
|
1165
|
-
var GatewayClientTransportPolicyError = class extends GatewayClientSocketFactoryConfigurationError {};
|
|
1265
|
+
var GatewayClientTransportPolicyError = class extends GatewayWebSocketTransportConfigurationError {};
|
|
1166
1266
|
const GATEWAY_CONNECT_ASSEMBLY_ERROR = Symbol("gateway.connectAssemblyError");
|
|
1167
1267
|
function markGatewayConnectAssemblyError(error) {
|
|
1168
1268
|
Object.defineProperty(error, GATEWAY_CONNECT_ASSEMBLY_ERROR, {
|
|
@@ -1184,10 +1284,20 @@ function formatGatewayClientErrorForLog(err) {
|
|
|
1184
1284
|
const FORCE_STOP_TERMINATE_GRACE_MS = 250;
|
|
1185
1285
|
const STOP_AND_WAIT_TIMEOUT_MS = 1e3;
|
|
1186
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
|
+
}
|
|
1187
1294
|
var GatewayClient = class {
|
|
1188
1295
|
constructor(opts) {
|
|
1189
1296
|
this.ws = null;
|
|
1190
1297
|
this.stopped = false;
|
|
1298
|
+
this.useLegacyNodeProtocolEnvelope = false;
|
|
1299
|
+
this.nodeProtocolTransitionPending = false;
|
|
1300
|
+
this.suppressNextHelloCallback = false;
|
|
1191
1301
|
this.pendingDeviceTokenRetry = false;
|
|
1192
1302
|
this.deviceTokenRetryBudgetUsed = false;
|
|
1193
1303
|
this.approvalRuntimeTokenCompatibilityDisabled = false;
|
|
@@ -1203,7 +1313,7 @@ var GatewayClient = class {
|
|
|
1203
1313
|
...opts,
|
|
1204
1314
|
deviceIdentity: opts.deviceIdentity === null ? void 0 : opts.deviceIdentity ?? this.deps.loadOrCreateDeviceIdentity()
|
|
1205
1315
|
};
|
|
1206
|
-
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;
|
|
1207
1317
|
const connectChallengeTimeoutMs = resolveConnectChallengeTimeoutMs(this.opts.connectChallengeTimeoutMs, {
|
|
1208
1318
|
env: this.opts.env,
|
|
1209
1319
|
configuredTimeoutMs: this.opts.preauthHandshakeTimeoutMs
|
|
@@ -1212,13 +1322,19 @@ var GatewayClient = class {
|
|
|
1212
1322
|
createSocket: (handlers) => this.createSocket(handlers),
|
|
1213
1323
|
createRequestId: randomUUID,
|
|
1214
1324
|
createRequestError: (error) => new GatewayClientRequestError(error),
|
|
1215
|
-
createRequestTimeoutError: (method) =>
|
|
1325
|
+
createRequestTimeoutError: (method, timeoutMs, requestSent) => new GatewayClientRequestTimeoutError({
|
|
1326
|
+
method,
|
|
1327
|
+
timeoutMs,
|
|
1328
|
+
requestSent
|
|
1329
|
+
}),
|
|
1216
1330
|
createRequestAbortError: createGatewayRequestAbortError,
|
|
1217
|
-
buildConnectPlan: ({ nonce }) => {
|
|
1331
|
+
buildConnectPlan: ({ nonce, challengeTs }) => {
|
|
1218
1332
|
if (!nonce) throw new Error("gateway connect challenge missing nonce");
|
|
1333
|
+
if (this.opts.deviceIdentity && challengeTs == null) throw new Error("gateway connect challenge timestamp invalid");
|
|
1219
1334
|
return this.assembleConnectParams({
|
|
1220
1335
|
role: this.opts.role ?? "operator",
|
|
1221
|
-
nonce
|
|
1336
|
+
nonce,
|
|
1337
|
+
signedAtMs: challengeTs ?? Date.now()
|
|
1222
1338
|
});
|
|
1223
1339
|
},
|
|
1224
1340
|
buildConnectParams: (assembled) => assembled.params,
|
|
@@ -1236,7 +1352,13 @@ var GatewayClient = class {
|
|
|
1236
1352
|
};
|
|
1237
1353
|
},
|
|
1238
1354
|
onConnectHello: (hello, context) => this.handleConnectHello(hello, context.plan),
|
|
1239
|
-
onHello: (hello) =>
|
|
1355
|
+
onHello: (hello) => {
|
|
1356
|
+
if (this.suppressNextHelloCallback) {
|
|
1357
|
+
this.suppressNextHelloCallback = false;
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
this.opts.onHelloOk?.(hello);
|
|
1361
|
+
},
|
|
1240
1362
|
onConnectFailure: (error, context) => this.handleConnectRequestFailure(error, context.plan),
|
|
1241
1363
|
resolveClose: (context) => this.resolveClose(context),
|
|
1242
1364
|
onClose: (context, decision) => {
|
|
@@ -1266,7 +1388,7 @@ var GatewayClient = class {
|
|
|
1266
1388
|
maxMs: 3e4
|
|
1267
1389
|
},
|
|
1268
1390
|
requestTimeoutMs: this.requestTimeoutMs,
|
|
1269
|
-
shouldRetrySocketFactoryError: (error) => !(error instanceof
|
|
1391
|
+
shouldRetrySocketFactoryError: (error) => !(error instanceof GatewayWebSocketTransportConfigurationError) && !(error instanceof SyntaxError) && !(error instanceof TypeError) && !(error instanceof RangeError),
|
|
1270
1392
|
rethrowSocketFactoryError: (error) => error instanceof GatewayClientTransportPolicyError
|
|
1271
1393
|
});
|
|
1272
1394
|
}
|
|
@@ -1282,7 +1404,9 @@ var GatewayClient = class {
|
|
|
1282
1404
|
this.opts = {
|
|
1283
1405
|
...this.opts,
|
|
1284
1406
|
caps: [...manifest.caps],
|
|
1285
|
-
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
|
|
1286
1410
|
};
|
|
1287
1411
|
if (!this.stopped) this.protocol.closeSocket(1012, "node manifest changed");
|
|
1288
1412
|
}
|
|
@@ -1292,35 +1416,22 @@ var GatewayClient = class {
|
|
|
1292
1416
|
}
|
|
1293
1417
|
createSocket(handlers) {
|
|
1294
1418
|
const url = this.opts.url ?? DEFAULT_GATEWAY_CLIENT_URL;
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
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
|
+
});
|
|
1304
1434
|
this.deps.beforeConnect();
|
|
1305
|
-
const wsOptions = {
|
|
1306
|
-
maxPayload: 25 * 1024 * 1024,
|
|
1307
|
-
handshakeTimeout: resolvePreauthHandshakeTimeoutMs({
|
|
1308
|
-
env: this.opts.env,
|
|
1309
|
-
configuredTimeoutMs: this.opts.preauthHandshakeTimeoutMs
|
|
1310
|
-
}),
|
|
1311
|
-
...this.opts.origin ? { origin: this.opts.origin } : {}
|
|
1312
|
-
};
|
|
1313
|
-
if (url.startsWith("wss://") && this.opts.tlsFingerprint) {
|
|
1314
|
-
wsOptions.rejectUnauthorized = false;
|
|
1315
|
-
wsOptions.checkServerIdentity = (_hostValue, cert) => {
|
|
1316
|
-
const fingerprintValue = typeof cert === "object" && cert && "fingerprint256" in cert ? cert.fingerprint256 ?? "" : "";
|
|
1317
|
-
const fingerprint = this.deps.normalizeTlsFingerprint(typeof fingerprintValue === "string" ? fingerprintValue : "");
|
|
1318
|
-
const expected = this.deps.normalizeTlsFingerprint(this.opts.tlsFingerprint ?? "");
|
|
1319
|
-
if (!expected) return;
|
|
1320
|
-
if (!fingerprint) return /* @__PURE__ */ new Error("Missing server TLS fingerprint");
|
|
1321
|
-
if (fingerprint !== expected) return /* @__PURE__ */ new Error("Server TLS fingerprint mismatch");
|
|
1322
|
-
};
|
|
1323
|
-
}
|
|
1324
1435
|
let ws;
|
|
1325
1436
|
let unregisterGatewayLoopbackBypass;
|
|
1326
1437
|
try {
|
|
@@ -1329,7 +1440,7 @@ var GatewayClient = class {
|
|
|
1329
1440
|
throw new GatewayClientTransportPolicyError(error instanceof Error ? error.message : String(error));
|
|
1330
1441
|
}
|
|
1331
1442
|
try {
|
|
1332
|
-
ws = new WebSocket(url,
|
|
1443
|
+
ws = new WebSocket(url, transport.options);
|
|
1333
1444
|
ws.binaryType = "nodebuffer";
|
|
1334
1445
|
} catch (error) {
|
|
1335
1446
|
throw error instanceof Error ? error : new Error(String(error));
|
|
@@ -1338,15 +1449,14 @@ var GatewayClient = class {
|
|
|
1338
1449
|
}
|
|
1339
1450
|
this.ws = ws;
|
|
1340
1451
|
this.transportValidated = false;
|
|
1452
|
+
let upgradeError;
|
|
1341
1453
|
ws.on("open", () => {
|
|
1342
1454
|
handlers.open();
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
return;
|
|
1349
|
-
}
|
|
1455
|
+
const tlsError = transport.validateSocket(ws);
|
|
1456
|
+
if (tlsError) {
|
|
1457
|
+
handlers.error(tlsError);
|
|
1458
|
+
ws.close(1008, tlsError.message);
|
|
1459
|
+
return;
|
|
1350
1460
|
}
|
|
1351
1461
|
this.transportValidated = true;
|
|
1352
1462
|
});
|
|
@@ -1357,7 +1467,25 @@ var GatewayClient = class {
|
|
|
1357
1467
|
this.resolvePendingStop(ws);
|
|
1358
1468
|
handlers.close(code, reasonText);
|
|
1359
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
|
+
});
|
|
1360
1487
|
ws.on("error", (err) => {
|
|
1488
|
+
if (upgradeError) return;
|
|
1361
1489
|
this.logDebug(`gateway client error: ${formatGatewayClientErrorForLog(err)}`);
|
|
1362
1490
|
handlers.error(err instanceof Error ? err : new Error(String(err)));
|
|
1363
1491
|
});
|
|
@@ -1446,34 +1574,45 @@ var GatewayClient = class {
|
|
|
1446
1574
|
this.deps.logError(this.deps.redactForLog(message));
|
|
1447
1575
|
}
|
|
1448
1576
|
assembleConnectParams(params) {
|
|
1449
|
-
const { role, nonce } = params;
|
|
1577
|
+
const { role, nonce, signedAtMs } = params;
|
|
1450
1578
|
const selectedAuth = this.selectConnectAuth(role);
|
|
1451
1579
|
const { authDeviceToken, authApprovalRuntimeToken, authAgentRuntimeIdentityToken, signatureToken, resolvedDeviceToken, storedToken, storedScopes, usingStoredDeviceToken } = selectedAuth;
|
|
1452
1580
|
if (this.pendingDeviceTokenRetry && authDeviceToken) this.pendingDeviceTokenRetry = false;
|
|
1453
1581
|
const auth = buildGatewayConnectAuth(selectedAuth);
|
|
1454
|
-
const signedAtMs = Date.now();
|
|
1455
1582
|
const scopes = resolveGatewayConnectScopes({
|
|
1456
1583
|
requestedScopes: this.opts.scopes,
|
|
1457
1584
|
usingStoredDeviceToken,
|
|
1458
1585
|
storedScopes,
|
|
1459
1586
|
defaultScopes: ["operator.admin"]
|
|
1460
1587
|
});
|
|
1461
|
-
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;
|
|
1462
1598
|
return {
|
|
1463
1599
|
params: {
|
|
1464
|
-
minProtocol
|
|
1465
|
-
maxProtocol
|
|
1600
|
+
minProtocol,
|
|
1601
|
+
maxProtocol,
|
|
1466
1602
|
client: {
|
|
1467
|
-
id:
|
|
1603
|
+
id: clientId,
|
|
1468
1604
|
displayName: this.opts.clientDisplayName,
|
|
1469
1605
|
version: this.opts.clientVersion ?? DEFAULT_CLIENT_VERSION,
|
|
1606
|
+
buildId: this.opts.clientBuildId,
|
|
1470
1607
|
platform,
|
|
1471
|
-
deviceFamily
|
|
1472
|
-
mode:
|
|
1608
|
+
deviceFamily,
|
|
1609
|
+
mode: clientMode,
|
|
1473
1610
|
instanceId: this.opts.instanceId
|
|
1474
1611
|
},
|
|
1475
1612
|
caps: Array.isArray(this.opts.caps) ? this.opts.caps : [],
|
|
1476
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,
|
|
1477
1616
|
permissions: this.opts.permissions && typeof this.opts.permissions === "object" ? this.opts.permissions : void 0,
|
|
1478
1617
|
pathEnv: this.opts.pathEnv,
|
|
1479
1618
|
auth,
|
|
@@ -1485,30 +1624,47 @@ var GatewayClient = class {
|
|
|
1485
1624
|
scopes,
|
|
1486
1625
|
signatureToken,
|
|
1487
1626
|
signedAtMs,
|
|
1488
|
-
platform
|
|
1627
|
+
platform,
|
|
1628
|
+
deviceFamily,
|
|
1629
|
+
clientMode
|
|
1489
1630
|
})
|
|
1490
1631
|
},
|
|
1491
1632
|
authApprovalRuntimeToken,
|
|
1492
1633
|
authAgentRuntimeIdentityToken,
|
|
1493
1634
|
resolvedDeviceToken,
|
|
1635
|
+
storedScopes,
|
|
1494
1636
|
storedToken,
|
|
1495
1637
|
usingStoredDeviceToken
|
|
1496
1638
|
};
|
|
1497
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
|
+
}
|
|
1498
1654
|
buildDeviceConnectParams(params) {
|
|
1499
1655
|
if (!this.opts.deviceIdentity) return;
|
|
1500
|
-
const { nonce, role, scopes, signatureToken, signedAtMs, platform } = params;
|
|
1656
|
+
const { nonce, role, scopes, signatureToken, signedAtMs, platform, deviceFamily, clientMode } = params;
|
|
1501
1657
|
const payload = buildDeviceAuthPayloadV3({
|
|
1502
1658
|
deviceId: this.opts.deviceIdentity.deviceId,
|
|
1503
1659
|
clientId: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
|
|
1504
|
-
clientMode
|
|
1660
|
+
clientMode,
|
|
1505
1661
|
role,
|
|
1506
1662
|
scopes,
|
|
1507
1663
|
signedAtMs,
|
|
1508
1664
|
token: signatureToken ?? null,
|
|
1509
1665
|
nonce,
|
|
1510
1666
|
platform,
|
|
1511
|
-
deviceFamily
|
|
1667
|
+
deviceFamily
|
|
1512
1668
|
});
|
|
1513
1669
|
const signature = this.deps.signDevicePayload(this.opts.deviceIdentity.privateKeyPem, payload);
|
|
1514
1670
|
return {
|
|
@@ -1520,24 +1676,66 @@ var GatewayClient = class {
|
|
|
1520
1676
|
};
|
|
1521
1677
|
}
|
|
1522
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;
|
|
1523
1682
|
this.pendingDeviceTokenRetry = false;
|
|
1524
1683
|
this.deviceTokenRetryBudgetUsed = false;
|
|
1525
1684
|
this.suppressedTransientPreHelloCleanCloses = 0;
|
|
1526
1685
|
const role = this.opts.role ?? "operator";
|
|
1527
1686
|
const authInfo = helloOk.auth;
|
|
1528
|
-
if (authInfo?.deviceToken && this.opts.deviceIdentity)
|
|
1529
|
-
|
|
1530
|
-
role: authInfo.
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
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
|
+
}
|
|
1535
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
|
+
}
|
|
1536
1711
|
this.lastTick = Date.now();
|
|
1537
1712
|
this.startTickWatch();
|
|
1538
1713
|
}
|
|
1539
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
|
+
}
|
|
1540
1737
|
const role = this.opts.role ?? "operator";
|
|
1738
|
+
const detailCode = error instanceof GatewayClientRequestError ? readConnectErrorDetailCode(error.details) : null;
|
|
1541
1739
|
const shouldRetryWithDeviceToken = shouldRetryGatewayWithDeviceToken({
|
|
1542
1740
|
retryBudgetUsed: this.deviceTokenRetryBudgetUsed,
|
|
1543
1741
|
currentDeviceToken: assembled.resolvedDeviceToken,
|
|
@@ -1546,7 +1744,7 @@ var GatewayClient = class {
|
|
|
1546
1744
|
trustedEndpoint: this.isTrustedDeviceRetryEndpoint(),
|
|
1547
1745
|
errorDetails: error instanceof GatewayClientRequestError ? error.details : void 0
|
|
1548
1746
|
});
|
|
1549
|
-
if (this.opts.deviceIdentity && assembled.usingStoredDeviceToken &&
|
|
1747
|
+
if (this.opts.deviceIdentity && assembled.usingStoredDeviceToken && detailCode === ConnectErrorDetailCodes.AUTH_DEVICE_TOKEN_MISMATCH) {
|
|
1550
1748
|
const deviceId = this.opts.deviceIdentity.deviceId;
|
|
1551
1749
|
try {
|
|
1552
1750
|
this.deps.clearDeviceAuthToken({
|
|
@@ -1602,7 +1800,7 @@ var GatewayClient = class {
|
|
|
1602
1800
|
}
|
|
1603
1801
|
this.notifyConnectError(error);
|
|
1604
1802
|
const message = `gateway connect failed: ${formatGatewayClientErrorForLog(error)}`;
|
|
1605
|
-
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);
|
|
1606
1804
|
else this.logError(message);
|
|
1607
1805
|
return {
|
|
1608
1806
|
closeCode: 1008,
|
|
@@ -1623,7 +1821,7 @@ var GatewayClient = class {
|
|
|
1623
1821
|
return {
|
|
1624
1822
|
retry: true,
|
|
1625
1823
|
notify: true,
|
|
1626
|
-
pendingError: new
|
|
1824
|
+
pendingError: /* @__PURE__ */ new Error("gateway transient pre-hello clean close")
|
|
1627
1825
|
};
|
|
1628
1826
|
}
|
|
1629
1827
|
if (info.transientPreHelloCleanClose || context.connectRequestSent && !context.helloReceived && !context.connectFailure) {
|
|
@@ -1659,11 +1857,13 @@ var GatewayClient = class {
|
|
|
1659
1857
|
phase: context.helloReceived ? "post-hello" : "pre-hello",
|
|
1660
1858
|
socketOpened: context.socketOpened,
|
|
1661
1859
|
transportValidated: this.transportValidated,
|
|
1662
|
-
|
|
1860
|
+
connectRequestSent: context.connectRequestSent,
|
|
1861
|
+
transientPreHelloCleanClose: !context.helloReceived && context.code === 1e3 && context.reason === "",
|
|
1862
|
+
...context.connectFailure?.error ? { connectError: context.connectFailure.error } : {}
|
|
1663
1863
|
};
|
|
1664
1864
|
}
|
|
1665
1865
|
clearStaleDeviceTokenForClose(code, reason) {
|
|
1666
|
-
if (code !== 1008 || !
|
|
1866
|
+
if (code !== 1008 || !normalizeGatewayErrorText(reason).includes("device token mismatch") || this.opts.token || this.opts.password || !this.opts.deviceIdentity) return;
|
|
1667
1867
|
const deviceId = this.opts.deviceIdentity.deviceId;
|
|
1668
1868
|
const role = this.opts.role ?? "operator";
|
|
1669
1869
|
try {
|
|
@@ -1696,14 +1896,14 @@ var GatewayClient = class {
|
|
|
1696
1896
|
if (!params.authApprovalRuntimeToken) return false;
|
|
1697
1897
|
if (!(params.error instanceof GatewayClientRequestError)) return false;
|
|
1698
1898
|
if (params.error.gatewayCode !== "INVALID_REQUEST") return false;
|
|
1699
|
-
const message =
|
|
1899
|
+
const message = normalizeGatewayErrorText(params.error.message);
|
|
1700
1900
|
return message.includes("invalid connect params") && message.includes("approvalruntimetoken");
|
|
1701
1901
|
}
|
|
1702
1902
|
shouldFailClosedForUnsupportedAgentRuntimeIdentity(params) {
|
|
1703
1903
|
if (!params.authAgentRuntimeIdentityToken) return false;
|
|
1704
1904
|
if (!(params.error instanceof GatewayClientRequestError)) return false;
|
|
1705
1905
|
if (params.error.gatewayCode !== "INVALID_REQUEST") return false;
|
|
1706
|
-
const message =
|
|
1906
|
+
const message = normalizeGatewayErrorText(params.error.message);
|
|
1707
1907
|
return message.includes("invalid connect params") && message.includes("agentruntimeidentitytoken");
|
|
1708
1908
|
}
|
|
1709
1909
|
isTrustedDeviceRetryEndpoint() {
|
|
@@ -1711,7 +1911,7 @@ var GatewayClient = class {
|
|
|
1711
1911
|
try {
|
|
1712
1912
|
const parsed = new URL(rawUrl);
|
|
1713
1913
|
const protocol = parsed.protocol === "https:" ? "wss:" : parsed.protocol === "http:" ? "ws:" : parsed.protocol;
|
|
1714
|
-
if (
|
|
1914
|
+
if (isGatewayLoopbackHost(parsed.hostname)) return true;
|
|
1715
1915
|
return protocol === "wss:" && Boolean(this.opts.tlsFingerprint?.trim());
|
|
1716
1916
|
} catch {
|
|
1717
1917
|
return false;
|
|
@@ -1726,6 +1926,7 @@ var GatewayClient = class {
|
|
|
1726
1926
|
return selectGatewayConnectAuth({
|
|
1727
1927
|
token: this.opts.token,
|
|
1728
1928
|
bootstrapToken: this.opts.bootstrapToken,
|
|
1929
|
+
preferBootstrapToken: this.opts.preferBootstrapToken,
|
|
1729
1930
|
deviceToken: this.opts.deviceToken,
|
|
1730
1931
|
password: this.opts.password,
|
|
1731
1932
|
approvalRuntimeToken: this.approvalRuntimeTokenCompatibilityDisabled ? void 0 : this.opts.approvalRuntimeToken,
|
|
@@ -1750,21 +1951,9 @@ var GatewayClient = class {
|
|
|
1750
1951
|
if (gap > (typeof rawTimeoutMs === "number" && Number.isFinite(rawTimeoutMs) ? Math.max(1, rawTimeoutMs) : this.tickIntervalMs * 2)) this.protocol.closeSocket(4e3, "tick timeout");
|
|
1751
1952
|
}, interval);
|
|
1752
1953
|
}
|
|
1753
|
-
validateTlsFingerprint() {
|
|
1754
|
-
if (!this.opts.tlsFingerprint || !this.ws) return null;
|
|
1755
|
-
const expected = this.deps.normalizeTlsFingerprint(this.opts.tlsFingerprint);
|
|
1756
|
-
if (!expected) return /* @__PURE__ */ new Error("gateway tls fingerprint missing");
|
|
1757
|
-
const socket = this.ws["_socket"];
|
|
1758
|
-
if (!socket || typeof socket.getPeerCertificate !== "function") return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
|
|
1759
|
-
const cert = socket.getPeerCertificate();
|
|
1760
|
-
const fingerprint = this.deps.normalizeTlsFingerprint(cert?.fingerprint256 ?? "");
|
|
1761
|
-
if (!fingerprint) return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
|
|
1762
|
-
if (fingerprint !== expected) return /* @__PURE__ */ new Error("gateway tls fingerprint mismatch");
|
|
1763
|
-
return null;
|
|
1764
|
-
}
|
|
1765
1954
|
async request(method, params, opts) {
|
|
1766
1955
|
const expectFinal = opts?.expectFinal === true;
|
|
1767
|
-
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;
|
|
1768
1957
|
return this.protocol.request(method, params, {
|
|
1769
1958
|
expectFinal,
|
|
1770
1959
|
timeoutMs,
|
|
@@ -1780,4 +1969,4 @@ function createGatewayRequestAbortError(method) {
|
|
|
1780
1969
|
return err;
|
|
1781
1970
|
}
|
|
1782
1971
|
//#endregion
|
|
1783
|
-
export { DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, GatewayBrowserDeviceAuthLifecycle, GatewayClient, GatewayClientRequestError, 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 };
|