@openclaw/gateway-client 0.0.0 → 2026.7.2-beta.4
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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +133 -2
- package/dist/browser-device-auth-Bp297Rtf.d.mts +130 -0
- package/dist/browser.d.mts +20 -0
- package/dist/browser.mjs +8 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +1781 -0
- package/dist/protocol-client-Cj1WAoMt.d.mts +180 -0
- package/dist/readiness-CPiKyi1s.d.mts +201 -0
- package/dist/readiness-CrWxd4TI.mjs +105 -0
- package/dist/readiness.d.mts +2 -0
- package/dist/readiness.mjs +3 -0
- package/dist/reconnect-policy-CkzLe1_K.mjs +850 -0
- package/dist/timeouts.d.mts +39 -0
- package/dist/timeouts.mjs +77 -0
- package/npm-shrinkwrap.json +50 -0
- package/package.json +69 -6
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1781 @@
|
|
|
1
|
+
import { a as buildGatewayConnectAuth, c as shouldRetryGatewayWithDeviceToken, d as normalizeDeviceMetadataForAuth, i as GatewayBrowserDeviceAuthLifecycle, l as buildDeviceAuthPayload, n as GatewayProtocolClient, o as resolveGatewayConnectScopes, r as GatewayProtocolRequestError, s as selectGatewayConnectAuth, t as shouldPauseGatewayReconnect, u as buildDeviceAuthPayloadV3 } from "./reconnect-policy-CkzLe1_K.mjs";
|
|
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, getConnectChallengeTimeoutMsFromEnv, resolveConnectChallengeTimeoutMs, resolveFiniteTimeoutDelayMs, resolvePreauthHandshakeTimeoutMs, resolveSafeTimeoutDelayMs } from "./timeouts.mjs";
|
|
3
|
+
import { n as startGatewayClientWithReadinessWait, r as waitForEventLoopReady, t as startGatewayClientWhenEventLoopReady } from "./readiness-CrWxd4TI.mjs";
|
|
4
|
+
import { ConnectErrorDetailCodes, formatConnectErrorMessage, readConnectErrorDetailCode } from "@openclaw/gateway-protocol/connect-error-details";
|
|
5
|
+
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "@openclaw/gateway-protocol/client-info";
|
|
6
|
+
import { resolveGatewayStartupRetryAfterMs } from "@openclaw/gateway-protocol/startup-unavailable";
|
|
7
|
+
import { MIN_CLIENT_PROTOCOL_VERSION, PROTOCOL_VERSION } from "@openclaw/gateway-protocol/version";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { WebSocket } from "ws";
|
|
10
|
+
import { Buffer as Buffer$1 } from "node:buffer";
|
|
11
|
+
//#region \0rolldown/runtime.js
|
|
12
|
+
var __create = Object.create;
|
|
13
|
+
var __defProp = Object.defineProperty;
|
|
14
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
15
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
16
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
17
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
18
|
+
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
21
|
+
key = keys[i];
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
23
|
+
get: ((k) => from[k]).bind(null, key),
|
|
24
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
30
|
+
value: mod,
|
|
31
|
+
enumerable: true
|
|
32
|
+
}) : target, mod));
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region packages/net-policy/src/ip.ts
|
|
35
|
+
var import_ipaddr = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
36
|
+
(function(root) {
|
|
37
|
+
"use strict";
|
|
38
|
+
const ipv4Part = "(0?\\d+|0x[a-f0-9]+)";
|
|
39
|
+
const ipv4Regexes = {
|
|
40
|
+
fourOctet: new RegExp(`^${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}$`, "i"),
|
|
41
|
+
threeOctet: new RegExp(`^${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}$`, "i"),
|
|
42
|
+
twoOctet: new RegExp(`^${ipv4Part}\\.${ipv4Part}$`, "i"),
|
|
43
|
+
longValue: new RegExp(`^${ipv4Part}$`, "i")
|
|
44
|
+
};
|
|
45
|
+
const octalRegex = new RegExp(`^0[0-7]+$`, "i");
|
|
46
|
+
const hexRegex = new RegExp(`^0x[a-f0-9]+$`, "i");
|
|
47
|
+
const zoneIndex = "%[0-9a-z]{1,}";
|
|
48
|
+
const ipv6Part = "(?:[0-9a-f]+::?)+";
|
|
49
|
+
const ipv6Regexes = {
|
|
50
|
+
zoneIndex: new RegExp(zoneIndex, "i"),
|
|
51
|
+
"native": new RegExp(`^(::)?(${ipv6Part})?([0-9a-f]+)?(::)?(${zoneIndex})?$`, "i"),
|
|
52
|
+
deprecatedTransitional: new RegExp(`^(?:::)(${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}(${zoneIndex})?)$`, "i"),
|
|
53
|
+
transitional: new RegExp(`^((?:${ipv6Part})|(?:::)(?:${ipv6Part})?)${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}(${zoneIndex})?$`, "i")
|
|
54
|
+
};
|
|
55
|
+
function expandIPv6(string, parts) {
|
|
56
|
+
if (string.indexOf("::") !== string.lastIndexOf("::")) return null;
|
|
57
|
+
let colonCount = 0;
|
|
58
|
+
let lastColon = -1;
|
|
59
|
+
let zoneId = (string.match(ipv6Regexes.zoneIndex) || [])[0];
|
|
60
|
+
let replacement, replacementCount;
|
|
61
|
+
if (zoneId) {
|
|
62
|
+
zoneId = zoneId.substring(1);
|
|
63
|
+
string = string.replace(/%.+$/, "");
|
|
64
|
+
}
|
|
65
|
+
while ((lastColon = string.indexOf(":", lastColon + 1)) >= 0) colonCount++;
|
|
66
|
+
if (string.substr(0, 2) === "::") colonCount--;
|
|
67
|
+
if (string.substr(-2, 2) === "::") colonCount--;
|
|
68
|
+
if (colonCount > parts) return null;
|
|
69
|
+
replacementCount = parts - colonCount;
|
|
70
|
+
replacement = ":";
|
|
71
|
+
while (replacementCount--) replacement += "0:";
|
|
72
|
+
string = string.replace("::", replacement);
|
|
73
|
+
if (string[0] === ":") string = string.slice(1);
|
|
74
|
+
if (string[string.length - 1] === ":") string = string.slice(0, -1);
|
|
75
|
+
parts = (function() {
|
|
76
|
+
const ref = string.split(":");
|
|
77
|
+
const results = [];
|
|
78
|
+
for (let i = 0; i < ref.length; i++) results.push(parseInt(ref[i], 16));
|
|
79
|
+
return results;
|
|
80
|
+
})();
|
|
81
|
+
return {
|
|
82
|
+
parts,
|
|
83
|
+
zoneId
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function matchCIDR(first, second, partSize, cidrBits) {
|
|
87
|
+
if (first.length !== second.length) throw new Error("ipaddr: cannot match CIDR for objects with different lengths");
|
|
88
|
+
let part = 0;
|
|
89
|
+
let shift;
|
|
90
|
+
while (cidrBits > 0) {
|
|
91
|
+
shift = partSize - cidrBits;
|
|
92
|
+
if (shift < 0) shift = 0;
|
|
93
|
+
if (first[part] >> shift !== second[part] >> shift) return false;
|
|
94
|
+
cidrBits -= partSize;
|
|
95
|
+
part += 1;
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
function parseIntAuto(string) {
|
|
100
|
+
if (hexRegex.test(string)) return parseInt(string, 16);
|
|
101
|
+
if (string[0] === "0" && !isNaN(parseInt(string[1], 10))) {
|
|
102
|
+
if (octalRegex.test(string)) return parseInt(string, 8);
|
|
103
|
+
throw new Error(`ipaddr: cannot parse ${string} as octal`);
|
|
104
|
+
}
|
|
105
|
+
return parseInt(string, 10);
|
|
106
|
+
}
|
|
107
|
+
function padPart(part, length) {
|
|
108
|
+
while (part.length < length) part = `0${part}`;
|
|
109
|
+
return part;
|
|
110
|
+
}
|
|
111
|
+
const ipaddr = {};
|
|
112
|
+
ipaddr.IPv4 = (function() {
|
|
113
|
+
function IPv4(octets) {
|
|
114
|
+
if (octets.length !== 4) throw new Error("ipaddr: ipv4 octet count should be 4");
|
|
115
|
+
let i, octet;
|
|
116
|
+
for (i = 0; i < octets.length; i++) {
|
|
117
|
+
octet = octets[i];
|
|
118
|
+
if (!(0 <= octet && octet <= 255)) throw new Error("ipaddr: ipv4 octet should fit in 8 bits");
|
|
119
|
+
}
|
|
120
|
+
this.octets = octets;
|
|
121
|
+
}
|
|
122
|
+
IPv4.prototype.SpecialRanges = {
|
|
123
|
+
unspecified: [[new IPv4([
|
|
124
|
+
0,
|
|
125
|
+
0,
|
|
126
|
+
0,
|
|
127
|
+
0
|
|
128
|
+
]), 8]],
|
|
129
|
+
broadcast: [[new IPv4([
|
|
130
|
+
255,
|
|
131
|
+
255,
|
|
132
|
+
255,
|
|
133
|
+
255
|
|
134
|
+
]), 32]],
|
|
135
|
+
multicast: [[new IPv4([
|
|
136
|
+
224,
|
|
137
|
+
0,
|
|
138
|
+
0,
|
|
139
|
+
0
|
|
140
|
+
]), 4]],
|
|
141
|
+
linkLocal: [[new IPv4([
|
|
142
|
+
169,
|
|
143
|
+
254,
|
|
144
|
+
0,
|
|
145
|
+
0
|
|
146
|
+
]), 16]],
|
|
147
|
+
loopback: [[new IPv4([
|
|
148
|
+
127,
|
|
149
|
+
0,
|
|
150
|
+
0,
|
|
151
|
+
0
|
|
152
|
+
]), 8]],
|
|
153
|
+
carrierGradeNat: [[new IPv4([
|
|
154
|
+
100,
|
|
155
|
+
64,
|
|
156
|
+
0,
|
|
157
|
+
0
|
|
158
|
+
]), 10]],
|
|
159
|
+
"private": [
|
|
160
|
+
[new IPv4([
|
|
161
|
+
10,
|
|
162
|
+
0,
|
|
163
|
+
0,
|
|
164
|
+
0
|
|
165
|
+
]), 8],
|
|
166
|
+
[new IPv4([
|
|
167
|
+
172,
|
|
168
|
+
16,
|
|
169
|
+
0,
|
|
170
|
+
0
|
|
171
|
+
]), 12],
|
|
172
|
+
[new IPv4([
|
|
173
|
+
192,
|
|
174
|
+
168,
|
|
175
|
+
0,
|
|
176
|
+
0
|
|
177
|
+
]), 16]
|
|
178
|
+
],
|
|
179
|
+
reserved: [
|
|
180
|
+
[new IPv4([
|
|
181
|
+
192,
|
|
182
|
+
0,
|
|
183
|
+
0,
|
|
184
|
+
0
|
|
185
|
+
]), 24],
|
|
186
|
+
[new IPv4([
|
|
187
|
+
192,
|
|
188
|
+
0,
|
|
189
|
+
2,
|
|
190
|
+
0
|
|
191
|
+
]), 24],
|
|
192
|
+
[new IPv4([
|
|
193
|
+
192,
|
|
194
|
+
88,
|
|
195
|
+
99,
|
|
196
|
+
0
|
|
197
|
+
]), 24],
|
|
198
|
+
[new IPv4([
|
|
199
|
+
198,
|
|
200
|
+
18,
|
|
201
|
+
0,
|
|
202
|
+
0
|
|
203
|
+
]), 15],
|
|
204
|
+
[new IPv4([
|
|
205
|
+
198,
|
|
206
|
+
51,
|
|
207
|
+
100,
|
|
208
|
+
0
|
|
209
|
+
]), 24],
|
|
210
|
+
[new IPv4([
|
|
211
|
+
203,
|
|
212
|
+
0,
|
|
213
|
+
113,
|
|
214
|
+
0
|
|
215
|
+
]), 24],
|
|
216
|
+
[new IPv4([
|
|
217
|
+
240,
|
|
218
|
+
0,
|
|
219
|
+
0,
|
|
220
|
+
0
|
|
221
|
+
]), 4]
|
|
222
|
+
],
|
|
223
|
+
as112: [[new IPv4([
|
|
224
|
+
192,
|
|
225
|
+
175,
|
|
226
|
+
48,
|
|
227
|
+
0
|
|
228
|
+
]), 24], [new IPv4([
|
|
229
|
+
192,
|
|
230
|
+
31,
|
|
231
|
+
196,
|
|
232
|
+
0
|
|
233
|
+
]), 24]],
|
|
234
|
+
amt: [[new IPv4([
|
|
235
|
+
192,
|
|
236
|
+
52,
|
|
237
|
+
193,
|
|
238
|
+
0
|
|
239
|
+
]), 24]]
|
|
240
|
+
};
|
|
241
|
+
IPv4.prototype.kind = function() {
|
|
242
|
+
return "ipv4";
|
|
243
|
+
};
|
|
244
|
+
IPv4.prototype.match = function(other, cidrRange) {
|
|
245
|
+
let ref;
|
|
246
|
+
if (cidrRange === void 0) {
|
|
247
|
+
ref = other;
|
|
248
|
+
other = ref[0];
|
|
249
|
+
cidrRange = ref[1];
|
|
250
|
+
}
|
|
251
|
+
if (other.kind() !== "ipv4") throw new Error("ipaddr: cannot match ipv4 address with non-ipv4 one");
|
|
252
|
+
return matchCIDR(this.octets, other.octets, 8, cidrRange);
|
|
253
|
+
};
|
|
254
|
+
IPv4.prototype.prefixLengthFromSubnetMask = function() {
|
|
255
|
+
let cidr = 0;
|
|
256
|
+
let stop = false;
|
|
257
|
+
const zerotable = {
|
|
258
|
+
0: 8,
|
|
259
|
+
128: 7,
|
|
260
|
+
192: 6,
|
|
261
|
+
224: 5,
|
|
262
|
+
240: 4,
|
|
263
|
+
248: 3,
|
|
264
|
+
252: 2,
|
|
265
|
+
254: 1,
|
|
266
|
+
255: 0
|
|
267
|
+
};
|
|
268
|
+
let i, octet, zeros;
|
|
269
|
+
for (i = 3; i >= 0; i -= 1) {
|
|
270
|
+
octet = this.octets[i];
|
|
271
|
+
if (octet in zerotable) {
|
|
272
|
+
zeros = zerotable[octet];
|
|
273
|
+
if (stop && zeros !== 0) return null;
|
|
274
|
+
if (zeros !== 8) stop = true;
|
|
275
|
+
cidr += zeros;
|
|
276
|
+
} else return null;
|
|
277
|
+
}
|
|
278
|
+
return 32 - cidr;
|
|
279
|
+
};
|
|
280
|
+
IPv4.prototype.range = function() {
|
|
281
|
+
return ipaddr.subnetMatch(this, this.SpecialRanges);
|
|
282
|
+
};
|
|
283
|
+
IPv4.prototype.toByteArray = function() {
|
|
284
|
+
return this.octets.slice(0);
|
|
285
|
+
};
|
|
286
|
+
IPv4.prototype.toIPv4MappedAddress = function() {
|
|
287
|
+
return ipaddr.IPv6.parse(`::ffff:${this.toString()}`);
|
|
288
|
+
};
|
|
289
|
+
IPv4.prototype.toNormalizedString = function() {
|
|
290
|
+
return this.toString();
|
|
291
|
+
};
|
|
292
|
+
IPv4.prototype.toString = function() {
|
|
293
|
+
return this.octets.join(".");
|
|
294
|
+
};
|
|
295
|
+
return IPv4;
|
|
296
|
+
})();
|
|
297
|
+
ipaddr.IPv4.broadcastAddressFromCIDR = function(string) {
|
|
298
|
+
try {
|
|
299
|
+
const cidr = this.parseCIDR(string);
|
|
300
|
+
const ipInterfaceOctets = cidr[0].toByteArray();
|
|
301
|
+
const subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray();
|
|
302
|
+
const octets = [];
|
|
303
|
+
let i = 0;
|
|
304
|
+
while (i < 4) {
|
|
305
|
+
octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255);
|
|
306
|
+
i++;
|
|
307
|
+
}
|
|
308
|
+
return new this(octets);
|
|
309
|
+
} catch (e) {
|
|
310
|
+
throw new Error("ipaddr: the address does not have IPv4 CIDR format");
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
ipaddr.IPv4.isIPv4 = function(string) {
|
|
314
|
+
return this.parser(string) !== null;
|
|
315
|
+
};
|
|
316
|
+
ipaddr.IPv4.isValid = function(string) {
|
|
317
|
+
try {
|
|
318
|
+
new this(this.parser(string));
|
|
319
|
+
return true;
|
|
320
|
+
} catch (e) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
ipaddr.IPv4.isValidCIDR = function(string) {
|
|
325
|
+
try {
|
|
326
|
+
this.parseCIDR(string);
|
|
327
|
+
return true;
|
|
328
|
+
} catch (e) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
ipaddr.IPv4.isValidFourPartDecimal = function(string) {
|
|
333
|
+
if (ipaddr.IPv4.isValid(string) && string.match(/^(0|[1-9]\d*)(\.(0|[1-9]\d*)){3}$/)) return true;
|
|
334
|
+
else return false;
|
|
335
|
+
};
|
|
336
|
+
ipaddr.IPv4.isValidCIDRFourPartDecimal = function(string) {
|
|
337
|
+
const match = string.match(/^(.+)\/(\d+)$/);
|
|
338
|
+
if (!ipaddr.IPv4.isValidCIDR(string) || !match) return false;
|
|
339
|
+
return ipaddr.IPv4.isValidFourPartDecimal(match[1]);
|
|
340
|
+
};
|
|
341
|
+
ipaddr.IPv4.networkAddressFromCIDR = function(string) {
|
|
342
|
+
let cidr, i, ipInterfaceOctets, octets, subnetMaskOctets;
|
|
343
|
+
try {
|
|
344
|
+
cidr = this.parseCIDR(string);
|
|
345
|
+
ipInterfaceOctets = cidr[0].toByteArray();
|
|
346
|
+
subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray();
|
|
347
|
+
octets = [];
|
|
348
|
+
i = 0;
|
|
349
|
+
while (i < 4) {
|
|
350
|
+
octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10));
|
|
351
|
+
i++;
|
|
352
|
+
}
|
|
353
|
+
return new this(octets);
|
|
354
|
+
} catch (e) {
|
|
355
|
+
throw new Error("ipaddr: the address does not have IPv4 CIDR format");
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
ipaddr.IPv4.parse = function(string) {
|
|
359
|
+
const parts = this.parser(string);
|
|
360
|
+
if (parts === null) throw new Error("ipaddr: string is not formatted like an IPv4 Address");
|
|
361
|
+
return new this(parts);
|
|
362
|
+
};
|
|
363
|
+
ipaddr.IPv4.parseCIDR = function(string) {
|
|
364
|
+
let match;
|
|
365
|
+
if (match = string.match(/^(.+)\/(\d+)$/)) {
|
|
366
|
+
const maskLength = parseInt(match[2]);
|
|
367
|
+
if (maskLength >= 0 && maskLength <= 32) {
|
|
368
|
+
const parsed = [this.parse(match[1]), maskLength];
|
|
369
|
+
Object.defineProperty(parsed, "toString", { value: function() {
|
|
370
|
+
return this.join("/");
|
|
371
|
+
} });
|
|
372
|
+
return parsed;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
throw new Error("ipaddr: string is not formatted like an IPv4 CIDR range");
|
|
376
|
+
};
|
|
377
|
+
ipaddr.IPv4.parser = function(string) {
|
|
378
|
+
let match, part, value;
|
|
379
|
+
if (match = string.match(ipv4Regexes.fourOctet)) return (function() {
|
|
380
|
+
const ref = match.slice(1, 6);
|
|
381
|
+
const results = [];
|
|
382
|
+
for (let i = 0; i < ref.length; i++) {
|
|
383
|
+
part = ref[i];
|
|
384
|
+
results.push(parseIntAuto(part));
|
|
385
|
+
}
|
|
386
|
+
return results;
|
|
387
|
+
})();
|
|
388
|
+
else if (match = string.match(ipv4Regexes.longValue)) {
|
|
389
|
+
value = parseIntAuto(match[1]);
|
|
390
|
+
if (value > 4294967295 || value < 0) throw new Error("ipaddr: address outside defined range");
|
|
391
|
+
return (function() {
|
|
392
|
+
const results = [];
|
|
393
|
+
let shift;
|
|
394
|
+
for (shift = 0; shift <= 24; shift += 8) results.push(value >> shift & 255);
|
|
395
|
+
return results;
|
|
396
|
+
})().reverse();
|
|
397
|
+
} else if (match = string.match(ipv4Regexes.twoOctet)) return (function() {
|
|
398
|
+
const ref = match.slice(1, 4);
|
|
399
|
+
const results = [];
|
|
400
|
+
value = parseIntAuto(ref[1]);
|
|
401
|
+
if (value > 16777215 || value < 0) throw new Error("ipaddr: address outside defined range");
|
|
402
|
+
results.push(parseIntAuto(ref[0]));
|
|
403
|
+
results.push(value >> 16 & 255);
|
|
404
|
+
results.push(value >> 8 & 255);
|
|
405
|
+
results.push(value & 255);
|
|
406
|
+
return results;
|
|
407
|
+
})();
|
|
408
|
+
else if (match = string.match(ipv4Regexes.threeOctet)) return (function() {
|
|
409
|
+
const ref = match.slice(1, 5);
|
|
410
|
+
const results = [];
|
|
411
|
+
value = parseIntAuto(ref[2]);
|
|
412
|
+
if (value > 65535 || value < 0) throw new Error("ipaddr: address outside defined range");
|
|
413
|
+
results.push(parseIntAuto(ref[0]));
|
|
414
|
+
results.push(parseIntAuto(ref[1]));
|
|
415
|
+
results.push(value >> 8 & 255);
|
|
416
|
+
results.push(value & 255);
|
|
417
|
+
return results;
|
|
418
|
+
})();
|
|
419
|
+
else return null;
|
|
420
|
+
};
|
|
421
|
+
ipaddr.IPv4.subnetMaskFromPrefixLength = function(prefix) {
|
|
422
|
+
prefix = parseInt(prefix);
|
|
423
|
+
if (prefix < 0 || prefix > 32) throw new Error("ipaddr: invalid IPv4 prefix length");
|
|
424
|
+
const octets = [
|
|
425
|
+
0,
|
|
426
|
+
0,
|
|
427
|
+
0,
|
|
428
|
+
0
|
|
429
|
+
];
|
|
430
|
+
let j = 0;
|
|
431
|
+
const filledOctetCount = Math.floor(prefix / 8);
|
|
432
|
+
while (j < filledOctetCount) {
|
|
433
|
+
octets[j] = 255;
|
|
434
|
+
j++;
|
|
435
|
+
}
|
|
436
|
+
if (filledOctetCount < 4) octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - prefix % 8;
|
|
437
|
+
return new this(octets);
|
|
438
|
+
};
|
|
439
|
+
ipaddr.IPv6 = (function() {
|
|
440
|
+
function IPv6(parts, zoneId) {
|
|
441
|
+
let i, part;
|
|
442
|
+
if (parts.length === 16) {
|
|
443
|
+
this.parts = [];
|
|
444
|
+
for (i = 0; i <= 14; i += 2) this.parts.push(parts[i] << 8 | parts[i + 1]);
|
|
445
|
+
} else if (parts.length === 8) this.parts = parts;
|
|
446
|
+
else throw new Error("ipaddr: ipv6 part count should be 8 or 16");
|
|
447
|
+
for (i = 0; i < this.parts.length; i++) {
|
|
448
|
+
part = this.parts[i];
|
|
449
|
+
if (!(0 <= part && part <= 65535)) throw new Error("ipaddr: ipv6 part should fit in 16 bits");
|
|
450
|
+
}
|
|
451
|
+
if (zoneId) this.zoneId = zoneId;
|
|
452
|
+
}
|
|
453
|
+
IPv6.prototype.SpecialRanges = {
|
|
454
|
+
unspecified: [new IPv6([
|
|
455
|
+
0,
|
|
456
|
+
0,
|
|
457
|
+
0,
|
|
458
|
+
0,
|
|
459
|
+
0,
|
|
460
|
+
0,
|
|
461
|
+
0,
|
|
462
|
+
0
|
|
463
|
+
]), 128],
|
|
464
|
+
linkLocal: [new IPv6([
|
|
465
|
+
65152,
|
|
466
|
+
0,
|
|
467
|
+
0,
|
|
468
|
+
0,
|
|
469
|
+
0,
|
|
470
|
+
0,
|
|
471
|
+
0,
|
|
472
|
+
0
|
|
473
|
+
]), 10],
|
|
474
|
+
multicast: [new IPv6([
|
|
475
|
+
65280,
|
|
476
|
+
0,
|
|
477
|
+
0,
|
|
478
|
+
0,
|
|
479
|
+
0,
|
|
480
|
+
0,
|
|
481
|
+
0,
|
|
482
|
+
0
|
|
483
|
+
]), 8],
|
|
484
|
+
loopback: [new IPv6([
|
|
485
|
+
0,
|
|
486
|
+
0,
|
|
487
|
+
0,
|
|
488
|
+
0,
|
|
489
|
+
0,
|
|
490
|
+
0,
|
|
491
|
+
0,
|
|
492
|
+
1
|
|
493
|
+
]), 128],
|
|
494
|
+
uniqueLocal: [new IPv6([
|
|
495
|
+
64512,
|
|
496
|
+
0,
|
|
497
|
+
0,
|
|
498
|
+
0,
|
|
499
|
+
0,
|
|
500
|
+
0,
|
|
501
|
+
0,
|
|
502
|
+
0
|
|
503
|
+
]), 7],
|
|
504
|
+
ipv4Mapped: [new IPv6([
|
|
505
|
+
0,
|
|
506
|
+
0,
|
|
507
|
+
0,
|
|
508
|
+
0,
|
|
509
|
+
0,
|
|
510
|
+
65535,
|
|
511
|
+
0,
|
|
512
|
+
0
|
|
513
|
+
]), 96],
|
|
514
|
+
deprecatedSiteLocal: [new IPv6([
|
|
515
|
+
65216,
|
|
516
|
+
0,
|
|
517
|
+
0,
|
|
518
|
+
0,
|
|
519
|
+
0,
|
|
520
|
+
0,
|
|
521
|
+
0,
|
|
522
|
+
0
|
|
523
|
+
]), 10],
|
|
524
|
+
discard: [new IPv6([
|
|
525
|
+
256,
|
|
526
|
+
0,
|
|
527
|
+
0,
|
|
528
|
+
0,
|
|
529
|
+
0,
|
|
530
|
+
0,
|
|
531
|
+
0,
|
|
532
|
+
0
|
|
533
|
+
]), 64],
|
|
534
|
+
rfc6145: [new IPv6([
|
|
535
|
+
0,
|
|
536
|
+
0,
|
|
537
|
+
0,
|
|
538
|
+
0,
|
|
539
|
+
65535,
|
|
540
|
+
0,
|
|
541
|
+
0,
|
|
542
|
+
0
|
|
543
|
+
]), 96],
|
|
544
|
+
rfc6052: [[new IPv6([
|
|
545
|
+
100,
|
|
546
|
+
65435,
|
|
547
|
+
0,
|
|
548
|
+
0,
|
|
549
|
+
0,
|
|
550
|
+
0,
|
|
551
|
+
0,
|
|
552
|
+
0
|
|
553
|
+
]), 96], [new IPv6([
|
|
554
|
+
100,
|
|
555
|
+
65435,
|
|
556
|
+
1,
|
|
557
|
+
0,
|
|
558
|
+
0,
|
|
559
|
+
0,
|
|
560
|
+
0,
|
|
561
|
+
0
|
|
562
|
+
]), 48]],
|
|
563
|
+
"6to4": [new IPv6([
|
|
564
|
+
8194,
|
|
565
|
+
0,
|
|
566
|
+
0,
|
|
567
|
+
0,
|
|
568
|
+
0,
|
|
569
|
+
0,
|
|
570
|
+
0,
|
|
571
|
+
0
|
|
572
|
+
]), 16],
|
|
573
|
+
teredo: [new IPv6([
|
|
574
|
+
8193,
|
|
575
|
+
0,
|
|
576
|
+
0,
|
|
577
|
+
0,
|
|
578
|
+
0,
|
|
579
|
+
0,
|
|
580
|
+
0,
|
|
581
|
+
0
|
|
582
|
+
]), 32],
|
|
583
|
+
benchmarking: [new IPv6([
|
|
584
|
+
8193,
|
|
585
|
+
2,
|
|
586
|
+
0,
|
|
587
|
+
0,
|
|
588
|
+
0,
|
|
589
|
+
0,
|
|
590
|
+
0,
|
|
591
|
+
0
|
|
592
|
+
]), 48],
|
|
593
|
+
amt: [new IPv6([
|
|
594
|
+
8193,
|
|
595
|
+
3,
|
|
596
|
+
0,
|
|
597
|
+
0,
|
|
598
|
+
0,
|
|
599
|
+
0,
|
|
600
|
+
0,
|
|
601
|
+
0
|
|
602
|
+
]), 32],
|
|
603
|
+
as112v6: [[new IPv6([
|
|
604
|
+
8193,
|
|
605
|
+
4,
|
|
606
|
+
274,
|
|
607
|
+
0,
|
|
608
|
+
0,
|
|
609
|
+
0,
|
|
610
|
+
0,
|
|
611
|
+
0
|
|
612
|
+
]), 48], [new IPv6([
|
|
613
|
+
9760,
|
|
614
|
+
79,
|
|
615
|
+
32768,
|
|
616
|
+
0,
|
|
617
|
+
0,
|
|
618
|
+
0,
|
|
619
|
+
0,
|
|
620
|
+
0
|
|
621
|
+
]), 48]],
|
|
622
|
+
deprecatedOrchid: [new IPv6([
|
|
623
|
+
8193,
|
|
624
|
+
16,
|
|
625
|
+
0,
|
|
626
|
+
0,
|
|
627
|
+
0,
|
|
628
|
+
0,
|
|
629
|
+
0,
|
|
630
|
+
0
|
|
631
|
+
]), 28],
|
|
632
|
+
orchid2: [new IPv6([
|
|
633
|
+
8193,
|
|
634
|
+
32,
|
|
635
|
+
0,
|
|
636
|
+
0,
|
|
637
|
+
0,
|
|
638
|
+
0,
|
|
639
|
+
0,
|
|
640
|
+
0
|
|
641
|
+
]), 28],
|
|
642
|
+
droneRemoteIdProtocolEntityTags: [new IPv6([
|
|
643
|
+
8193,
|
|
644
|
+
48,
|
|
645
|
+
0,
|
|
646
|
+
0,
|
|
647
|
+
0,
|
|
648
|
+
0,
|
|
649
|
+
0,
|
|
650
|
+
0
|
|
651
|
+
]), 28],
|
|
652
|
+
segmentRouting: [new IPv6([
|
|
653
|
+
24320,
|
|
654
|
+
0,
|
|
655
|
+
0,
|
|
656
|
+
0,
|
|
657
|
+
0,
|
|
658
|
+
0,
|
|
659
|
+
0,
|
|
660
|
+
0
|
|
661
|
+
]), 16],
|
|
662
|
+
reserved: [
|
|
663
|
+
[new IPv6([
|
|
664
|
+
8193,
|
|
665
|
+
0,
|
|
666
|
+
0,
|
|
667
|
+
0,
|
|
668
|
+
0,
|
|
669
|
+
0,
|
|
670
|
+
0,
|
|
671
|
+
0
|
|
672
|
+
]), 23],
|
|
673
|
+
[new IPv6([
|
|
674
|
+
8193,
|
|
675
|
+
3512,
|
|
676
|
+
0,
|
|
677
|
+
0,
|
|
678
|
+
0,
|
|
679
|
+
0,
|
|
680
|
+
0,
|
|
681
|
+
0
|
|
682
|
+
]), 32],
|
|
683
|
+
[new IPv6([
|
|
684
|
+
16383,
|
|
685
|
+
0,
|
|
686
|
+
0,
|
|
687
|
+
0,
|
|
688
|
+
0,
|
|
689
|
+
0,
|
|
690
|
+
0,
|
|
691
|
+
0
|
|
692
|
+
]), 20]
|
|
693
|
+
]
|
|
694
|
+
};
|
|
695
|
+
IPv6.prototype.isIPv4MappedAddress = function() {
|
|
696
|
+
return this.range() === "ipv4Mapped";
|
|
697
|
+
};
|
|
698
|
+
IPv6.prototype.kind = function() {
|
|
699
|
+
return "ipv6";
|
|
700
|
+
};
|
|
701
|
+
IPv6.prototype.match = function(other, cidrRange) {
|
|
702
|
+
let ref;
|
|
703
|
+
if (cidrRange === void 0) {
|
|
704
|
+
ref = other;
|
|
705
|
+
other = ref[0];
|
|
706
|
+
cidrRange = ref[1];
|
|
707
|
+
}
|
|
708
|
+
if (other.kind() !== "ipv6") throw new Error("ipaddr: cannot match ipv6 address with non-ipv6 one");
|
|
709
|
+
return matchCIDR(this.parts, other.parts, 16, cidrRange);
|
|
710
|
+
};
|
|
711
|
+
IPv6.prototype.prefixLengthFromSubnetMask = function() {
|
|
712
|
+
let cidr = 0;
|
|
713
|
+
let stop = false;
|
|
714
|
+
const zerotable = {
|
|
715
|
+
0: 16,
|
|
716
|
+
32768: 15,
|
|
717
|
+
49152: 14,
|
|
718
|
+
57344: 13,
|
|
719
|
+
61440: 12,
|
|
720
|
+
63488: 11,
|
|
721
|
+
64512: 10,
|
|
722
|
+
65024: 9,
|
|
723
|
+
65280: 8,
|
|
724
|
+
65408: 7,
|
|
725
|
+
65472: 6,
|
|
726
|
+
65504: 5,
|
|
727
|
+
65520: 4,
|
|
728
|
+
65528: 3,
|
|
729
|
+
65532: 2,
|
|
730
|
+
65534: 1,
|
|
731
|
+
65535: 0
|
|
732
|
+
};
|
|
733
|
+
let part, zeros;
|
|
734
|
+
for (let i = 7; i >= 0; i -= 1) {
|
|
735
|
+
part = this.parts[i];
|
|
736
|
+
if (part in zerotable) {
|
|
737
|
+
zeros = zerotable[part];
|
|
738
|
+
if (stop && zeros !== 0) return null;
|
|
739
|
+
if (zeros !== 16) stop = true;
|
|
740
|
+
cidr += zeros;
|
|
741
|
+
} else return null;
|
|
742
|
+
}
|
|
743
|
+
return 128 - cidr;
|
|
744
|
+
};
|
|
745
|
+
IPv6.prototype.range = function() {
|
|
746
|
+
return ipaddr.subnetMatch(this, this.SpecialRanges);
|
|
747
|
+
};
|
|
748
|
+
IPv6.prototype.toByteArray = function() {
|
|
749
|
+
let part;
|
|
750
|
+
const bytes = [];
|
|
751
|
+
const ref = this.parts;
|
|
752
|
+
for (let i = 0; i < ref.length; i++) {
|
|
753
|
+
part = ref[i];
|
|
754
|
+
bytes.push(part >> 8);
|
|
755
|
+
bytes.push(part & 255);
|
|
756
|
+
}
|
|
757
|
+
return bytes;
|
|
758
|
+
};
|
|
759
|
+
IPv6.prototype.toFixedLengthString = function() {
|
|
760
|
+
const addr = (function() {
|
|
761
|
+
const results = [];
|
|
762
|
+
for (let i = 0; i < this.parts.length; i++) results.push(padPart(this.parts[i].toString(16), 4));
|
|
763
|
+
return results;
|
|
764
|
+
}).call(this).join(":");
|
|
765
|
+
let suffix = "";
|
|
766
|
+
if (this.zoneId) suffix = `%${this.zoneId}`;
|
|
767
|
+
return addr + suffix;
|
|
768
|
+
};
|
|
769
|
+
IPv6.prototype.toIPv4Address = function() {
|
|
770
|
+
if (!this.isIPv4MappedAddress()) throw new Error("ipaddr: trying to convert a generic ipv6 address to ipv4");
|
|
771
|
+
const ref = this.parts.slice(-2);
|
|
772
|
+
const high = ref[0];
|
|
773
|
+
const low = ref[1];
|
|
774
|
+
return new ipaddr.IPv4([
|
|
775
|
+
high >> 8,
|
|
776
|
+
high & 255,
|
|
777
|
+
low >> 8,
|
|
778
|
+
low & 255
|
|
779
|
+
]);
|
|
780
|
+
};
|
|
781
|
+
IPv6.prototype.toNormalizedString = function() {
|
|
782
|
+
const addr = (function() {
|
|
783
|
+
const results = [];
|
|
784
|
+
for (let i = 0; i < this.parts.length; i++) results.push(this.parts[i].toString(16));
|
|
785
|
+
return results;
|
|
786
|
+
}).call(this).join(":");
|
|
787
|
+
let suffix = "";
|
|
788
|
+
if (this.zoneId) suffix = `%${this.zoneId}`;
|
|
789
|
+
return addr + suffix;
|
|
790
|
+
};
|
|
791
|
+
IPv6.prototype.toRFC5952String = function() {
|
|
792
|
+
const regex = /((^|:)(0(:|$)){2,})/g;
|
|
793
|
+
const string = this.toNormalizedString();
|
|
794
|
+
let bestMatchIndex = 0;
|
|
795
|
+
let bestMatchLength = -1;
|
|
796
|
+
let match;
|
|
797
|
+
while (match = regex.exec(string)) if (match[0].length > bestMatchLength) {
|
|
798
|
+
bestMatchIndex = match.index;
|
|
799
|
+
bestMatchLength = match[0].length;
|
|
800
|
+
}
|
|
801
|
+
if (bestMatchLength < 0) return string;
|
|
802
|
+
return `${string.substring(0, bestMatchIndex)}::${string.substring(bestMatchIndex + bestMatchLength)}`;
|
|
803
|
+
};
|
|
804
|
+
IPv6.prototype.toString = function() {
|
|
805
|
+
return this.toRFC5952String();
|
|
806
|
+
};
|
|
807
|
+
return IPv6;
|
|
808
|
+
})();
|
|
809
|
+
ipaddr.IPv6.broadcastAddressFromCIDR = function(string) {
|
|
810
|
+
try {
|
|
811
|
+
const cidr = this.parseCIDR(string);
|
|
812
|
+
const ipInterfaceOctets = cidr[0].toByteArray();
|
|
813
|
+
const subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray();
|
|
814
|
+
const octets = [];
|
|
815
|
+
let i = 0;
|
|
816
|
+
while (i < 16) {
|
|
817
|
+
octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255);
|
|
818
|
+
i++;
|
|
819
|
+
}
|
|
820
|
+
return new this(octets);
|
|
821
|
+
} catch (e) {
|
|
822
|
+
throw new Error(`ipaddr: the address does not have IPv6 CIDR format (${e})`);
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
ipaddr.IPv6.isIPv6 = function(string) {
|
|
826
|
+
return this.parser(string) !== null;
|
|
827
|
+
};
|
|
828
|
+
ipaddr.IPv6.isValid = function(string) {
|
|
829
|
+
if (typeof string === "string" && string.indexOf(":") === -1) return false;
|
|
830
|
+
try {
|
|
831
|
+
const addr = this.parser(string);
|
|
832
|
+
new this(addr.parts, addr.zoneId);
|
|
833
|
+
return true;
|
|
834
|
+
} catch (e) {
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
ipaddr.IPv6.isValidCIDR = function(string) {
|
|
839
|
+
if (typeof string === "string" && string.indexOf(":") === -1) return false;
|
|
840
|
+
try {
|
|
841
|
+
this.parseCIDR(string);
|
|
842
|
+
return true;
|
|
843
|
+
} catch (e) {
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
ipaddr.IPv6.networkAddressFromCIDR = function(string) {
|
|
848
|
+
let cidr, i, ipInterfaceOctets, octets, subnetMaskOctets;
|
|
849
|
+
try {
|
|
850
|
+
cidr = this.parseCIDR(string);
|
|
851
|
+
ipInterfaceOctets = cidr[0].toByteArray();
|
|
852
|
+
subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray();
|
|
853
|
+
octets = [];
|
|
854
|
+
i = 0;
|
|
855
|
+
while (i < 16) {
|
|
856
|
+
octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10));
|
|
857
|
+
i++;
|
|
858
|
+
}
|
|
859
|
+
return new this(octets);
|
|
860
|
+
} catch (e) {
|
|
861
|
+
throw new Error(`ipaddr: the address does not have IPv6 CIDR format (${e})`);
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
ipaddr.IPv6.parse = function(string) {
|
|
865
|
+
const addr = this.parser(string);
|
|
866
|
+
if (addr.parts === null) throw new Error("ipaddr: string is not formatted like an IPv6 Address");
|
|
867
|
+
return new this(addr.parts, addr.zoneId);
|
|
868
|
+
};
|
|
869
|
+
ipaddr.IPv6.parseCIDR = function(string) {
|
|
870
|
+
let maskLength, match, parsed;
|
|
871
|
+
if (match = string.match(/^(.+)\/(\d+)$/)) {
|
|
872
|
+
maskLength = parseInt(match[2]);
|
|
873
|
+
if (maskLength >= 0 && maskLength <= 128) {
|
|
874
|
+
parsed = [this.parse(match[1]), maskLength];
|
|
875
|
+
Object.defineProperty(parsed, "toString", { value: function() {
|
|
876
|
+
return this.join("/");
|
|
877
|
+
} });
|
|
878
|
+
return parsed;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
throw new Error("ipaddr: string is not formatted like an IPv6 CIDR range");
|
|
882
|
+
};
|
|
883
|
+
ipaddr.IPv6.parser = function(string) {
|
|
884
|
+
let addr, i, match, octet, octets, zoneId;
|
|
885
|
+
if (match = string.match(ipv6Regexes.deprecatedTransitional)) return this.parser(`::ffff:${match[1]}`);
|
|
886
|
+
if (ipv6Regexes.native.test(string)) return expandIPv6(string, 8);
|
|
887
|
+
if (match = string.match(ipv6Regexes.transitional)) {
|
|
888
|
+
zoneId = match[6] || "";
|
|
889
|
+
addr = match[1];
|
|
890
|
+
if (!match[1].endsWith("::")) addr = addr.slice(0, -1);
|
|
891
|
+
addr = expandIPv6(addr + zoneId, 6);
|
|
892
|
+
if (addr.parts) {
|
|
893
|
+
octets = [
|
|
894
|
+
parseInt(match[2]),
|
|
895
|
+
parseInt(match[3]),
|
|
896
|
+
parseInt(match[4]),
|
|
897
|
+
parseInt(match[5])
|
|
898
|
+
];
|
|
899
|
+
for (i = 0; i < octets.length; i++) {
|
|
900
|
+
octet = octets[i];
|
|
901
|
+
if (!(0 <= octet && octet <= 255)) return null;
|
|
902
|
+
}
|
|
903
|
+
addr.parts.push(octets[0] << 8 | octets[1]);
|
|
904
|
+
addr.parts.push(octets[2] << 8 | octets[3]);
|
|
905
|
+
return {
|
|
906
|
+
parts: addr.parts,
|
|
907
|
+
zoneId: addr.zoneId
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return null;
|
|
912
|
+
};
|
|
913
|
+
ipaddr.IPv6.subnetMaskFromPrefixLength = function(prefix) {
|
|
914
|
+
prefix = parseInt(prefix);
|
|
915
|
+
if (prefix < 0 || prefix > 128) throw new Error("ipaddr: invalid IPv6 prefix length");
|
|
916
|
+
const octets = [
|
|
917
|
+
0,
|
|
918
|
+
0,
|
|
919
|
+
0,
|
|
920
|
+
0,
|
|
921
|
+
0,
|
|
922
|
+
0,
|
|
923
|
+
0,
|
|
924
|
+
0,
|
|
925
|
+
0,
|
|
926
|
+
0,
|
|
927
|
+
0,
|
|
928
|
+
0,
|
|
929
|
+
0,
|
|
930
|
+
0,
|
|
931
|
+
0,
|
|
932
|
+
0
|
|
933
|
+
];
|
|
934
|
+
let j = 0;
|
|
935
|
+
const filledOctetCount = Math.floor(prefix / 8);
|
|
936
|
+
while (j < filledOctetCount) {
|
|
937
|
+
octets[j] = 255;
|
|
938
|
+
j++;
|
|
939
|
+
}
|
|
940
|
+
if (filledOctetCount < 16) octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - prefix % 8;
|
|
941
|
+
return new this(octets);
|
|
942
|
+
};
|
|
943
|
+
ipaddr.fromByteArray = function(bytes) {
|
|
944
|
+
const length = bytes.length;
|
|
945
|
+
if (length === 4) return new ipaddr.IPv4(bytes);
|
|
946
|
+
else if (length === 16) return new ipaddr.IPv6(bytes);
|
|
947
|
+
else throw new Error("ipaddr: the binary input is neither an IPv6 nor IPv4 address");
|
|
948
|
+
};
|
|
949
|
+
ipaddr.isValid = function(string) {
|
|
950
|
+
return ipaddr.IPv6.isValid(string) || ipaddr.IPv4.isValid(string);
|
|
951
|
+
};
|
|
952
|
+
ipaddr.isValidCIDR = function(string) {
|
|
953
|
+
return ipaddr.IPv6.isValidCIDR(string) || ipaddr.IPv4.isValidCIDR(string);
|
|
954
|
+
};
|
|
955
|
+
ipaddr.parse = function(string) {
|
|
956
|
+
if (ipaddr.IPv6.isValid(string)) return ipaddr.IPv6.parse(string);
|
|
957
|
+
else if (ipaddr.IPv4.isValid(string)) return ipaddr.IPv4.parse(string);
|
|
958
|
+
else throw new Error("ipaddr: the address has neither IPv6 nor IPv4 format");
|
|
959
|
+
};
|
|
960
|
+
ipaddr.parseCIDR = function(string) {
|
|
961
|
+
try {
|
|
962
|
+
return ipaddr.IPv6.parseCIDR(string);
|
|
963
|
+
} catch (e) {
|
|
964
|
+
try {
|
|
965
|
+
return ipaddr.IPv4.parseCIDR(string);
|
|
966
|
+
} catch (e2) {
|
|
967
|
+
throw new Error("ipaddr: the address has neither IPv6 nor IPv4 CIDR format");
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
ipaddr.process = function(string) {
|
|
972
|
+
const addr = this.parse(string);
|
|
973
|
+
if (addr.kind() === "ipv6" && addr.isIPv4MappedAddress()) return addr.toIPv4Address();
|
|
974
|
+
else return addr;
|
|
975
|
+
};
|
|
976
|
+
ipaddr.subnetMatch = function(address, rangeList, defaultName) {
|
|
977
|
+
let i, rangeName, rangeSubnets, subnet;
|
|
978
|
+
if (defaultName === void 0 || defaultName === null) defaultName = "unicast";
|
|
979
|
+
for (rangeName in rangeList) if (Object.prototype.hasOwnProperty.call(rangeList, rangeName)) {
|
|
980
|
+
rangeSubnets = rangeList[rangeName];
|
|
981
|
+
if (rangeSubnets[0] && !(rangeSubnets[0] instanceof Array)) rangeSubnets = [rangeSubnets];
|
|
982
|
+
for (i = 0; i < rangeSubnets.length; i++) {
|
|
983
|
+
subnet = rangeSubnets[i];
|
|
984
|
+
if (address.kind() === subnet[0].kind() && address.match.apply(address, subnet)) return rangeName;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return defaultName;
|
|
988
|
+
};
|
|
989
|
+
if (typeof module !== "undefined" && module.exports) module.exports = ipaddr;
|
|
990
|
+
else root.ipaddr = ipaddr;
|
|
991
|
+
})(exports);
|
|
992
|
+
})))(), 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
|
+
import_ipaddr.default.IPv4.parse("198.18.0.0");
|
|
1001
|
+
function stripIpv6Brackets(value) {
|
|
1002
|
+
if (value.startsWith("[") && value.endsWith("]")) return value.slice(1, -1);
|
|
1003
|
+
return value;
|
|
1004
|
+
}
|
|
1005
|
+
/** Type guard for parsed IPv6 addresses. */
|
|
1006
|
+
function isIpv6Address(address) {
|
|
1007
|
+
return address.kind() === "ipv6";
|
|
1008
|
+
}
|
|
1009
|
+
function normalizeIpv4MappedAddress(address) {
|
|
1010
|
+
if (!isIpv6Address(address)) return address;
|
|
1011
|
+
if (!address.isIPv4MappedAddress()) return address;
|
|
1012
|
+
return address.toIPv4Address();
|
|
1013
|
+
}
|
|
1014
|
+
function normalizeIpParseInput(raw) {
|
|
1015
|
+
const trimmed = normalizeOptionalString(raw);
|
|
1016
|
+
if (!trimmed) return;
|
|
1017
|
+
return stripIpv6Brackets(trimmed);
|
|
1018
|
+
}
|
|
1019
|
+
/** Parses canonical IPv4/IPv6 literals, rejecting legacy IPv4 shorthand forms. */
|
|
1020
|
+
function parseCanonicalIpAddress(raw) {
|
|
1021
|
+
const normalized = normalizeIpParseInput(raw);
|
|
1022
|
+
if (!normalized) return;
|
|
1023
|
+
return import_ipaddr.default.IPv4.isValidFourPartDecimal(normalized) || import_ipaddr.default.IPv6.isValid(normalized) ? import_ipaddr.default.parse(normalized) : void 0;
|
|
1024
|
+
}
|
|
1025
|
+
/** Normalizes canonical IP literals and maps IPv4-mapped IPv6 addresses to IPv4 text. */
|
|
1026
|
+
function normalizeIpAddress(raw) {
|
|
1027
|
+
const parsed = parseCanonicalIpAddress(raw);
|
|
1028
|
+
if (!parsed) return;
|
|
1029
|
+
return normalizeLowercaseStringOrEmpty$1(normalizeIpv4MappedAddress(parsed).toString());
|
|
1030
|
+
}
|
|
1031
|
+
/** True when a canonical IP literal is loopback, including IPv4-mapped IPv6. */
|
|
1032
|
+
function isLoopbackIpAddress(raw) {
|
|
1033
|
+
const parsed = parseCanonicalIpAddress(raw);
|
|
1034
|
+
if (!parsed) return false;
|
|
1035
|
+
return normalizeIpv4MappedAddress(parsed).range() === "loopback";
|
|
1036
|
+
}
|
|
1037
|
+
//#endregion
|
|
1038
|
+
//#region packages/gateway-client/src/client-address-utils.ts
|
|
1039
|
+
function normalizeLowercaseStringOrEmpty(value) {
|
|
1040
|
+
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
1041
|
+
}
|
|
1042
|
+
function isSensitiveUrlQueryParamName(key) {
|
|
1043
|
+
return /(?:token|password|secret|key|auth|credential)/iu.test(key);
|
|
1044
|
+
}
|
|
1045
|
+
function normalizeFingerprint(fingerprint) {
|
|
1046
|
+
return (fingerprint ?? "").replaceAll(":", "").trim().toLowerCase();
|
|
1047
|
+
}
|
|
1048
|
+
function parseHostForAddressChecks(host) {
|
|
1049
|
+
if (!host) return null;
|
|
1050
|
+
const normalizedHost = host.toLowerCase().trim();
|
|
1051
|
+
const canonicalHost = normalizedHost.replace(/\.+$/, "");
|
|
1052
|
+
if (canonicalHost === "localhost") return {
|
|
1053
|
+
isLocalhost: true,
|
|
1054
|
+
unbracketedHost: canonicalHost
|
|
1055
|
+
};
|
|
1056
|
+
return {
|
|
1057
|
+
isLocalhost: false,
|
|
1058
|
+
unbracketedHost: normalizedHost.startsWith("[") && normalizedHost.endsWith("]") ? normalizedHost.slice(1, -1) : normalizedHost
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
function parseGatewayIpAddress(host) {
|
|
1062
|
+
const normalized = normalizeIpAddress(host);
|
|
1063
|
+
return normalized ? parseCanonicalIpAddress(normalized) : void 0;
|
|
1064
|
+
}
|
|
1065
|
+
//#endregion
|
|
1066
|
+
//#region packages/gateway-client/src/websocket-data.ts
|
|
1067
|
+
function rawDataToString(data) {
|
|
1068
|
+
if (Array.isArray(data)) return Buffer$1.concat(data).toString("utf8");
|
|
1069
|
+
return data instanceof ArrayBuffer ? Buffer$1.from(data).toString("utf8") : data.toString("utf8");
|
|
1070
|
+
}
|
|
1071
|
+
//#endregion
|
|
1072
|
+
//#region packages/gateway-client/src/client.ts
|
|
1073
|
+
const DEFAULT_HOST_DEPS = {
|
|
1074
|
+
loadOrCreateDeviceIdentity: () => void 0,
|
|
1075
|
+
signDevicePayload: () => {
|
|
1076
|
+
throw new Error("GatewayClient device signature dependency is not configured");
|
|
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
|
|
1090
|
+
};
|
|
1091
|
+
function resolveHostDeps(overrides) {
|
|
1092
|
+
return Object.fromEntries(Object.entries(DEFAULT_HOST_DEPS).map(([key, fallback]) => [key, overrides?.[key] ?? fallback]));
|
|
1093
|
+
}
|
|
1094
|
+
const PRIVATE_OR_LOOPBACK_IPV4_RANGES = /* @__PURE__ */ new Set([
|
|
1095
|
+
"loopback",
|
|
1096
|
+
"private",
|
|
1097
|
+
"linkLocal",
|
|
1098
|
+
"carrierGradeNat"
|
|
1099
|
+
]);
|
|
1100
|
+
const PRIVATE_OR_LOOPBACK_IPV6_RANGES = /* @__PURE__ */ new Set([
|
|
1101
|
+
"loopback",
|
|
1102
|
+
"linkLocal",
|
|
1103
|
+
"uniqueLocal",
|
|
1104
|
+
"deprecatedSiteLocal"
|
|
1105
|
+
]);
|
|
1106
|
+
function isPrivateOrLoopbackIpAddress(address) {
|
|
1107
|
+
return (address.kind() === "ipv4" ? PRIVATE_OR_LOOPBACK_IPV4_RANGES : PRIVATE_OR_LOOPBACK_IPV6_RANGES).has(address.range());
|
|
1108
|
+
}
|
|
1109
|
+
function isLoopbackHost(host) {
|
|
1110
|
+
const parsed = parseHostForAddressChecks(host);
|
|
1111
|
+
if (!parsed) return false;
|
|
1112
|
+
if (parsed.isLocalhost) return true;
|
|
1113
|
+
return isLoopbackIpAddress(parsed.unbracketedHost);
|
|
1114
|
+
}
|
|
1115
|
+
function isPrivateOrLoopbackHost(host) {
|
|
1116
|
+
const parsed = parseHostForAddressChecks(host);
|
|
1117
|
+
if (!parsed) return false;
|
|
1118
|
+
if (parsed.isLocalhost) return true;
|
|
1119
|
+
const address = parseGatewayIpAddress(parsed.unbracketedHost);
|
|
1120
|
+
if (!address) return false;
|
|
1121
|
+
return isPrivateOrLoopbackIpAddress(address);
|
|
1122
|
+
}
|
|
1123
|
+
function isTrustedPlaintextWebSocketHost(hostname) {
|
|
1124
|
+
if (isPrivateOrLoopbackHost(hostname)) return true;
|
|
1125
|
+
const normalized = hostname.toLowerCase().trim().replace(/\.+$/, "");
|
|
1126
|
+
return normalized.endsWith(".local") || normalized.endsWith(".ts.net");
|
|
1127
|
+
}
|
|
1128
|
+
function isSecureWebSocketUrl(rawUrl, options) {
|
|
1129
|
+
try {
|
|
1130
|
+
const url = new URL(rawUrl);
|
|
1131
|
+
const protocol = url.protocol === "https:" ? "wss:" : url.protocol === "http:" ? "ws:" : url.protocol;
|
|
1132
|
+
if (protocol === "wss:") return true;
|
|
1133
|
+
if (protocol !== "ws:") return false;
|
|
1134
|
+
if (isLoopbackHost(url.hostname) || isTrustedPlaintextWebSocketHost(url.hostname)) return true;
|
|
1135
|
+
if (options?.allowPrivateWs === true) {
|
|
1136
|
+
const hostForIpCheck = url.hostname.startsWith("[") && url.hostname.endsWith("]") ? url.hostname.slice(1, -1) : url.hostname;
|
|
1137
|
+
return isPrivateOrLoopbackHost(url.hostname) || parseGatewayIpAddress(hostForIpCheck) === void 0;
|
|
1138
|
+
}
|
|
1139
|
+
return false;
|
|
1140
|
+
} catch {
|
|
1141
|
+
return false;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
const DEFAULT_GATEWAY_CLIENT_URL = "ws://127.0.0.1:18789";
|
|
1145
|
+
const DEFAULT_CLIENT_VERSION = "0.0.0";
|
|
1146
|
+
var GatewayClientRequestError = class extends GatewayProtocolRequestError {
|
|
1147
|
+
constructor(error) {
|
|
1148
|
+
super({
|
|
1149
|
+
...error,
|
|
1150
|
+
message: formatConnectErrorMessage({
|
|
1151
|
+
message: error.message,
|
|
1152
|
+
details: error.details
|
|
1153
|
+
})
|
|
1154
|
+
});
|
|
1155
|
+
this.name = "GatewayClientRequestError";
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
var GatewayClientTransientPreHelloCloseError = class extends Error {
|
|
1159
|
+
constructor() {
|
|
1160
|
+
super("gateway transient pre-hello clean close");
|
|
1161
|
+
this.name = "GatewayClientTransientPreHelloCloseError";
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
var GatewayClientTransportPolicyError = class extends Error {};
|
|
1165
|
+
const GATEWAY_CONNECT_ASSEMBLY_ERROR = Symbol("gateway.connectAssemblyError");
|
|
1166
|
+
function markGatewayConnectAssemblyError(error) {
|
|
1167
|
+
Object.defineProperty(error, GATEWAY_CONNECT_ASSEMBLY_ERROR, {
|
|
1168
|
+
configurable: true,
|
|
1169
|
+
value: true
|
|
1170
|
+
});
|
|
1171
|
+
return error;
|
|
1172
|
+
}
|
|
1173
|
+
function isGatewayConnectAssemblyError(value) {
|
|
1174
|
+
return value instanceof Error && value[GATEWAY_CONNECT_ASSEMBLY_ERROR] === true;
|
|
1175
|
+
}
|
|
1176
|
+
function isGatewayClientStoppedError(err) {
|
|
1177
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1178
|
+
return message === "gateway client stopped" || message === "Error: gateway client stopped";
|
|
1179
|
+
}
|
|
1180
|
+
function formatGatewayClientErrorForLog(err) {
|
|
1181
|
+
return String(err).replace(/\/\/([^@/?#\s]+)@/g, "//***:***@").replace(/(Authorization:\s*Bearer\s+)[^\s]+/giu, "$1***").replace(/([?&])([^=&\s]+)=([^&#\s"'<>)]*)/g, (match, prefix, key) => isSensitiveUrlQueryParamName(key) ? `${prefix}${key}=***` : match);
|
|
1182
|
+
}
|
|
1183
|
+
const FORCE_STOP_TERMINATE_GRACE_MS = 250;
|
|
1184
|
+
const STOP_AND_WAIT_TIMEOUT_MS = 1e3;
|
|
1185
|
+
const MAX_SUPPRESSED_TRANSIENT_PRE_HELLO_CLEAN_CLOSES = 1;
|
|
1186
|
+
var GatewayClient = class {
|
|
1187
|
+
constructor(opts) {
|
|
1188
|
+
this.ws = null;
|
|
1189
|
+
this.stopped = false;
|
|
1190
|
+
this.pendingDeviceTokenRetry = false;
|
|
1191
|
+
this.deviceTokenRetryBudgetUsed = false;
|
|
1192
|
+
this.approvalRuntimeTokenCompatibilityDisabled = false;
|
|
1193
|
+
this.approvalRuntimeTokenRetryBudgetUsed = false;
|
|
1194
|
+
this.lastTick = null;
|
|
1195
|
+
this.tickIntervalMs = 3e4;
|
|
1196
|
+
this.tickTimer = null;
|
|
1197
|
+
this.pendingStop = null;
|
|
1198
|
+
this.transportValidated = false;
|
|
1199
|
+
this.suppressedTransientPreHelloCleanCloses = 0;
|
|
1200
|
+
this.deps = resolveHostDeps(opts.hostDeps);
|
|
1201
|
+
this.opts = {
|
|
1202
|
+
...opts,
|
|
1203
|
+
deviceIdentity: opts.deviceIdentity === null ? void 0 : opts.deviceIdentity ?? this.deps.loadOrCreateDeviceIdentity()
|
|
1204
|
+
};
|
|
1205
|
+
this.requestTimeoutMs = typeof opts.requestTimeoutMs === "number" && Number.isFinite(opts.requestTimeoutMs) ? resolveSafeTimeoutDelayMs(opts.requestTimeoutMs, { minMs: 0 }) : DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS;
|
|
1206
|
+
const connectChallengeTimeoutMs = resolveConnectChallengeTimeoutMs(this.opts.connectChallengeTimeoutMs, {
|
|
1207
|
+
env: this.opts.env,
|
|
1208
|
+
configuredTimeoutMs: this.opts.preauthHandshakeTimeoutMs
|
|
1209
|
+
});
|
|
1210
|
+
this.protocol = new GatewayProtocolClient({
|
|
1211
|
+
createSocket: (handlers) => this.createSocket(handlers),
|
|
1212
|
+
createRequestId: randomUUID,
|
|
1213
|
+
createRequestError: (error) => new GatewayClientRequestError(error),
|
|
1214
|
+
createRequestTimeoutError: (method) => /* @__PURE__ */ new Error(`gateway request timeout for ${method}`),
|
|
1215
|
+
createRequestAbortError: createGatewayRequestAbortError,
|
|
1216
|
+
buildConnectPlan: ({ nonce }) => {
|
|
1217
|
+
if (!nonce) throw new Error("gateway connect challenge missing nonce");
|
|
1218
|
+
return this.assembleConnectParams({
|
|
1219
|
+
role: this.opts.role ?? "operator",
|
|
1220
|
+
nonce
|
|
1221
|
+
});
|
|
1222
|
+
},
|
|
1223
|
+
buildConnectParams: (assembled) => assembled.params,
|
|
1224
|
+
onConnectPlanError: (error) => {
|
|
1225
|
+
this.stopped = true;
|
|
1226
|
+
const marked = markGatewayConnectAssemblyError(error);
|
|
1227
|
+
const msg = `gateway connect failed: ${formatGatewayClientErrorForLog(error)}`;
|
|
1228
|
+
if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE || isGatewayClientStoppedError(error)) this.logDebug(msg);
|
|
1229
|
+
else this.logError(msg);
|
|
1230
|
+
return {
|
|
1231
|
+
closeCode: 1008,
|
|
1232
|
+
closeReason: "connect failed",
|
|
1233
|
+
stop: true,
|
|
1234
|
+
error: marked
|
|
1235
|
+
};
|
|
1236
|
+
},
|
|
1237
|
+
onConnectHello: (hello, context) => this.handleConnectHello(hello, context.plan),
|
|
1238
|
+
onHello: (hello) => this.opts.onHelloOk?.(hello),
|
|
1239
|
+
onConnectFailure: (error, context) => this.handleConnectRequestFailure(error, context.plan),
|
|
1240
|
+
resolveClose: (context) => this.resolveClose(context),
|
|
1241
|
+
onClose: (context, decision) => {
|
|
1242
|
+
if (this.tickTimer) {
|
|
1243
|
+
clearInterval(this.tickTimer);
|
|
1244
|
+
this.tickTimer = null;
|
|
1245
|
+
}
|
|
1246
|
+
if (decision.notify) this.opts.onClose?.(context.code, context.reason, this.closeInfo(context));
|
|
1247
|
+
},
|
|
1248
|
+
notifyStoppedClose: true,
|
|
1249
|
+
onConnectError: (error) => this.notifyConnectError(error),
|
|
1250
|
+
onParseError: (error) => this.logDebug(`gateway client parse error: ${formatGatewayClientErrorForLog(error)}`),
|
|
1251
|
+
onEvent: (event) => this.opts.onEvent?.(event),
|
|
1252
|
+
onGap: (info) => this.opts.onGap?.(info),
|
|
1253
|
+
onActivity: () => {
|
|
1254
|
+
this.lastTick = Date.now();
|
|
1255
|
+
},
|
|
1256
|
+
onCallbackError: (label, error) => this.logDebug(`gateway client ${label === "hello" ? "hello-ok" : label === "gap" ? "event" : label} handler error: ${formatGatewayClientErrorForLog(error)}`),
|
|
1257
|
+
handshake: {
|
|
1258
|
+
mode: "require-challenge",
|
|
1259
|
+
timeoutMs: connectChallengeTimeoutMs,
|
|
1260
|
+
timeoutMessage: (elapsedMs) => `gateway connect challenge timeout (waited ${elapsedMs}ms, limit ${connectChallengeTimeoutMs}ms)`
|
|
1261
|
+
},
|
|
1262
|
+
reconnect: {
|
|
1263
|
+
initialMs: 1e3,
|
|
1264
|
+
multiplier: 2,
|
|
1265
|
+
maxMs: 3e4
|
|
1266
|
+
},
|
|
1267
|
+
requestTimeoutMs: this.requestTimeoutMs,
|
|
1268
|
+
rethrowSocketFactoryError: (error) => error instanceof GatewayClientTransportPolicyError
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
getConnectionMetadata() {
|
|
1272
|
+
return {
|
|
1273
|
+
clientName: this.opts.clientName,
|
|
1274
|
+
hasDeviceIdentity: Boolean(this.opts.deviceIdentity),
|
|
1275
|
+
mode: this.opts.mode,
|
|
1276
|
+
preauthHandshakeTimeoutMs: this.opts.preauthHandshakeTimeoutMs
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
updateNodeManifest(manifest) {
|
|
1280
|
+
this.opts = {
|
|
1281
|
+
...this.opts,
|
|
1282
|
+
caps: [...manifest.caps],
|
|
1283
|
+
commands: [...manifest.commands]
|
|
1284
|
+
};
|
|
1285
|
+
if (!this.stopped) this.protocol.closeSocket(1012, "node manifest changed");
|
|
1286
|
+
}
|
|
1287
|
+
start() {
|
|
1288
|
+
if (this.stopped) return;
|
|
1289
|
+
this.protocol.start();
|
|
1290
|
+
}
|
|
1291
|
+
createSocket(handlers) {
|
|
1292
|
+
const url = this.opts.url ?? DEFAULT_GATEWAY_CLIENT_URL;
|
|
1293
|
+
if (this.opts.tlsFingerprint && !url.startsWith("wss://")) throw new Error("gateway tls fingerprint requires wss:// gateway url");
|
|
1294
|
+
const allowPrivateWs = (this.opts.env ?? process.env).OPENCLAW_ALLOW_INSECURE_PRIVATE_WS === "1";
|
|
1295
|
+
if (!isSecureWebSocketUrl(url, { allowPrivateWs })) {
|
|
1296
|
+
let displayHost = url;
|
|
1297
|
+
try {
|
|
1298
|
+
displayHost = new URL(url).hostname || url;
|
|
1299
|
+
} catch {}
|
|
1300
|
+
throw new Error(`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.");
|
|
1301
|
+
}
|
|
1302
|
+
this.deps.beforeConnect();
|
|
1303
|
+
const wsOptions = {
|
|
1304
|
+
maxPayload: 25 * 1024 * 1024,
|
|
1305
|
+
handshakeTimeout: resolvePreauthHandshakeTimeoutMs({
|
|
1306
|
+
env: this.opts.env,
|
|
1307
|
+
configuredTimeoutMs: this.opts.preauthHandshakeTimeoutMs
|
|
1308
|
+
}),
|
|
1309
|
+
...this.opts.origin ? { origin: this.opts.origin } : {}
|
|
1310
|
+
};
|
|
1311
|
+
if (url.startsWith("wss://") && this.opts.tlsFingerprint) {
|
|
1312
|
+
wsOptions.rejectUnauthorized = false;
|
|
1313
|
+
wsOptions.checkServerIdentity = (_hostValue, cert) => {
|
|
1314
|
+
const fingerprintValue = typeof cert === "object" && cert && "fingerprint256" in cert ? cert.fingerprint256 ?? "" : "";
|
|
1315
|
+
const fingerprint = this.deps.normalizeTlsFingerprint(typeof fingerprintValue === "string" ? fingerprintValue : "");
|
|
1316
|
+
const expected = this.deps.normalizeTlsFingerprint(this.opts.tlsFingerprint ?? "");
|
|
1317
|
+
if (!expected) return;
|
|
1318
|
+
if (!fingerprint) return /* @__PURE__ */ new Error("Missing server TLS fingerprint");
|
|
1319
|
+
if (fingerprint !== expected) return /* @__PURE__ */ new Error("Server TLS fingerprint mismatch");
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
let ws;
|
|
1323
|
+
let unregisterGatewayLoopbackBypass;
|
|
1324
|
+
try {
|
|
1325
|
+
unregisterGatewayLoopbackBypass = this.deps.registerGatewayLoopbackBypass(url);
|
|
1326
|
+
} catch (error) {
|
|
1327
|
+
throw new GatewayClientTransportPolicyError(error instanceof Error ? error.message : String(error));
|
|
1328
|
+
}
|
|
1329
|
+
try {
|
|
1330
|
+
ws = new WebSocket(url, wsOptions);
|
|
1331
|
+
ws.binaryType = "nodebuffer";
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
1334
|
+
} finally {
|
|
1335
|
+
unregisterGatewayLoopbackBypass?.();
|
|
1336
|
+
}
|
|
1337
|
+
this.ws = ws;
|
|
1338
|
+
this.transportValidated = false;
|
|
1339
|
+
ws.on("open", () => {
|
|
1340
|
+
handlers.open();
|
|
1341
|
+
if (url.startsWith("wss://") && this.opts.tlsFingerprint) {
|
|
1342
|
+
const tlsError = this.validateTlsFingerprint();
|
|
1343
|
+
if (tlsError) {
|
|
1344
|
+
handlers.error(tlsError);
|
|
1345
|
+
ws.close(1008, tlsError.message);
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
this.transportValidated = true;
|
|
1350
|
+
});
|
|
1351
|
+
ws.on("message", (data) => handlers.message(rawDataToString(data)));
|
|
1352
|
+
ws.on("close", (code, reason) => {
|
|
1353
|
+
const reasonText = reason.toString();
|
|
1354
|
+
if (this.ws === ws) this.ws = null;
|
|
1355
|
+
this.resolvePendingStop(ws);
|
|
1356
|
+
handlers.close(code, reasonText);
|
|
1357
|
+
});
|
|
1358
|
+
ws.on("error", (err) => {
|
|
1359
|
+
this.logDebug(`gateway client error: ${formatGatewayClientErrorForLog(err)}`);
|
|
1360
|
+
handlers.error(err instanceof Error ? err : new Error(String(err)));
|
|
1361
|
+
});
|
|
1362
|
+
return {
|
|
1363
|
+
isOpen: () => ws.readyState === WebSocket.OPEN,
|
|
1364
|
+
send: (data) => ws.send(data),
|
|
1365
|
+
close: (code, reason) => ws.close(code, reason)
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
stop() {
|
|
1369
|
+
this.beginStop();
|
|
1370
|
+
}
|
|
1371
|
+
async stopAndWait(opts) {
|
|
1372
|
+
const stopPromise = this.beginStop();
|
|
1373
|
+
if (!stopPromise) return;
|
|
1374
|
+
const timeoutMs = opts?.timeoutMs === void 0 ? STOP_AND_WAIT_TIMEOUT_MS : resolveSafeTimeoutDelayMs(opts.timeoutMs);
|
|
1375
|
+
let timeout = null;
|
|
1376
|
+
try {
|
|
1377
|
+
await Promise.race([stopPromise, new Promise((_, reject) => {
|
|
1378
|
+
timeout = setTimeout(() => {
|
|
1379
|
+
reject(/* @__PURE__ */ new Error(`gateway client stop timed out after ${timeoutMs}ms`));
|
|
1380
|
+
}, timeoutMs);
|
|
1381
|
+
timeout.unref?.();
|
|
1382
|
+
})]);
|
|
1383
|
+
} finally {
|
|
1384
|
+
if (timeout) clearTimeout(timeout);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
beginStop() {
|
|
1388
|
+
this.stopped = true;
|
|
1389
|
+
this.pendingDeviceTokenRetry = false;
|
|
1390
|
+
this.deviceTokenRetryBudgetUsed = false;
|
|
1391
|
+
if (this.tickTimer) {
|
|
1392
|
+
clearInterval(this.tickTimer);
|
|
1393
|
+
this.tickTimer = null;
|
|
1394
|
+
}
|
|
1395
|
+
if (this.pendingStop) return this.pendingStop.promise;
|
|
1396
|
+
const ws = this.ws;
|
|
1397
|
+
this.ws = null;
|
|
1398
|
+
if (ws) {
|
|
1399
|
+
const pendingStop = this.createPendingStop(ws);
|
|
1400
|
+
const forceTerminateTimer = setTimeout(() => {
|
|
1401
|
+
try {
|
|
1402
|
+
ws.terminate();
|
|
1403
|
+
} finally {
|
|
1404
|
+
this.resolvePendingStop(ws);
|
|
1405
|
+
}
|
|
1406
|
+
}, FORCE_STOP_TERMINATE_GRACE_MS);
|
|
1407
|
+
forceTerminateTimer.unref?.();
|
|
1408
|
+
pendingStop.terminateTimer = forceTerminateTimer;
|
|
1409
|
+
if (this.protocol.connecting) {
|
|
1410
|
+
const error = /* @__PURE__ */ new Error("gateway client stopped");
|
|
1411
|
+
this.notifyConnectError(error);
|
|
1412
|
+
this.logDebug(`gateway connect failed: ${formatGatewayClientErrorForLog(error)}`);
|
|
1413
|
+
}
|
|
1414
|
+
this.protocol.stop();
|
|
1415
|
+
return pendingStop.promise;
|
|
1416
|
+
}
|
|
1417
|
+
this.protocol.stop();
|
|
1418
|
+
return null;
|
|
1419
|
+
}
|
|
1420
|
+
createPendingStop(ws) {
|
|
1421
|
+
if (this.pendingStop?.ws === ws) return this.pendingStop;
|
|
1422
|
+
let resolve = () => {};
|
|
1423
|
+
const promise = new Promise((done) => {
|
|
1424
|
+
resolve = done;
|
|
1425
|
+
});
|
|
1426
|
+
this.pendingStop = {
|
|
1427
|
+
ws,
|
|
1428
|
+
promise,
|
|
1429
|
+
resolve
|
|
1430
|
+
};
|
|
1431
|
+
return this.pendingStop;
|
|
1432
|
+
}
|
|
1433
|
+
resolvePendingStop(ws) {
|
|
1434
|
+
if (this.pendingStop?.ws !== ws) return;
|
|
1435
|
+
const { resolve, terminateTimer } = this.pendingStop;
|
|
1436
|
+
if (terminateTimer) clearTimeout(terminateTimer);
|
|
1437
|
+
this.pendingStop = null;
|
|
1438
|
+
resolve();
|
|
1439
|
+
}
|
|
1440
|
+
logDebug(message) {
|
|
1441
|
+
this.deps.logDebug(this.deps.redactForLog(message));
|
|
1442
|
+
}
|
|
1443
|
+
logError(message) {
|
|
1444
|
+
this.deps.logError(this.deps.redactForLog(message));
|
|
1445
|
+
}
|
|
1446
|
+
assembleConnectParams(params) {
|
|
1447
|
+
const { role, nonce } = params;
|
|
1448
|
+
const selectedAuth = this.selectConnectAuth(role);
|
|
1449
|
+
const { authDeviceToken, authApprovalRuntimeToken, authAgentRuntimeIdentityToken, signatureToken, resolvedDeviceToken, storedToken, storedScopes, usingStoredDeviceToken } = selectedAuth;
|
|
1450
|
+
if (this.pendingDeviceTokenRetry && authDeviceToken) this.pendingDeviceTokenRetry = false;
|
|
1451
|
+
const auth = buildGatewayConnectAuth(selectedAuth);
|
|
1452
|
+
const signedAtMs = Date.now();
|
|
1453
|
+
const scopes = resolveGatewayConnectScopes({
|
|
1454
|
+
requestedScopes: this.opts.scopes,
|
|
1455
|
+
usingStoredDeviceToken,
|
|
1456
|
+
storedScopes,
|
|
1457
|
+
defaultScopes: ["operator.admin"]
|
|
1458
|
+
});
|
|
1459
|
+
const platform = this.opts.platform ?? process.platform;
|
|
1460
|
+
return {
|
|
1461
|
+
params: {
|
|
1462
|
+
minProtocol: this.opts.minProtocol ?? MIN_CLIENT_PROTOCOL_VERSION,
|
|
1463
|
+
maxProtocol: this.opts.maxProtocol ?? PROTOCOL_VERSION,
|
|
1464
|
+
client: {
|
|
1465
|
+
id: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
|
|
1466
|
+
displayName: this.opts.clientDisplayName,
|
|
1467
|
+
version: this.opts.clientVersion ?? DEFAULT_CLIENT_VERSION,
|
|
1468
|
+
platform,
|
|
1469
|
+
deviceFamily: this.opts.deviceFamily,
|
|
1470
|
+
mode: this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND,
|
|
1471
|
+
instanceId: this.opts.instanceId
|
|
1472
|
+
},
|
|
1473
|
+
caps: Array.isArray(this.opts.caps) ? this.opts.caps : [],
|
|
1474
|
+
commands: Array.isArray(this.opts.commands) ? this.opts.commands : void 0,
|
|
1475
|
+
permissions: this.opts.permissions && typeof this.opts.permissions === "object" ? this.opts.permissions : void 0,
|
|
1476
|
+
pathEnv: this.opts.pathEnv,
|
|
1477
|
+
auth,
|
|
1478
|
+
role,
|
|
1479
|
+
scopes,
|
|
1480
|
+
device: this.buildDeviceConnectParams({
|
|
1481
|
+
nonce,
|
|
1482
|
+
role,
|
|
1483
|
+
scopes,
|
|
1484
|
+
signatureToken,
|
|
1485
|
+
signedAtMs,
|
|
1486
|
+
platform
|
|
1487
|
+
})
|
|
1488
|
+
},
|
|
1489
|
+
authApprovalRuntimeToken,
|
|
1490
|
+
authAgentRuntimeIdentityToken,
|
|
1491
|
+
resolvedDeviceToken,
|
|
1492
|
+
storedToken,
|
|
1493
|
+
usingStoredDeviceToken
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
buildDeviceConnectParams(params) {
|
|
1497
|
+
if (!this.opts.deviceIdentity) return;
|
|
1498
|
+
const { nonce, role, scopes, signatureToken, signedAtMs, platform } = params;
|
|
1499
|
+
const payload = buildDeviceAuthPayloadV3({
|
|
1500
|
+
deviceId: this.opts.deviceIdentity.deviceId,
|
|
1501
|
+
clientId: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
|
|
1502
|
+
clientMode: this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND,
|
|
1503
|
+
role,
|
|
1504
|
+
scopes,
|
|
1505
|
+
signedAtMs,
|
|
1506
|
+
token: signatureToken ?? null,
|
|
1507
|
+
nonce,
|
|
1508
|
+
platform,
|
|
1509
|
+
deviceFamily: this.opts.deviceFamily
|
|
1510
|
+
});
|
|
1511
|
+
const signature = this.deps.signDevicePayload(this.opts.deviceIdentity.privateKeyPem, payload);
|
|
1512
|
+
return {
|
|
1513
|
+
id: this.opts.deviceIdentity.deviceId,
|
|
1514
|
+
publicKey: this.deps.publicKeyRawBase64UrlFromPem(this.opts.deviceIdentity.publicKeyPem),
|
|
1515
|
+
signature,
|
|
1516
|
+
signedAt: signedAtMs,
|
|
1517
|
+
nonce
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
handleConnectHello(helloOk, assembled) {
|
|
1521
|
+
this.pendingDeviceTokenRetry = false;
|
|
1522
|
+
this.deviceTokenRetryBudgetUsed = false;
|
|
1523
|
+
this.suppressedTransientPreHelloCleanCloses = 0;
|
|
1524
|
+
const role = this.opts.role ?? "operator";
|
|
1525
|
+
const authInfo = helloOk.auth;
|
|
1526
|
+
if (authInfo?.deviceToken && this.opts.deviceIdentity) this.deps.storeDeviceAuthToken({
|
|
1527
|
+
deviceId: this.opts.deviceIdentity.deviceId,
|
|
1528
|
+
role: authInfo.role ?? role,
|
|
1529
|
+
token: authInfo.deviceToken,
|
|
1530
|
+
scopes: authInfo.scopes ?? [],
|
|
1531
|
+
env: this.opts.env
|
|
1532
|
+
});
|
|
1533
|
+
this.tickIntervalMs = typeof helloOk.policy?.tickIntervalMs === "number" ? helloOk.policy.tickIntervalMs : 3e4;
|
|
1534
|
+
this.lastTick = Date.now();
|
|
1535
|
+
this.startTickWatch();
|
|
1536
|
+
}
|
|
1537
|
+
handleConnectRequestFailure(error, assembled) {
|
|
1538
|
+
const role = this.opts.role ?? "operator";
|
|
1539
|
+
const shouldRetryWithDeviceToken = shouldRetryGatewayWithDeviceToken({
|
|
1540
|
+
retryBudgetUsed: this.deviceTokenRetryBudgetUsed,
|
|
1541
|
+
currentDeviceToken: assembled.resolvedDeviceToken,
|
|
1542
|
+
explicitToken: this.opts.token?.trim() || void 0,
|
|
1543
|
+
storedToken: assembled.storedToken,
|
|
1544
|
+
trustedEndpoint: this.isTrustedDeviceRetryEndpoint(),
|
|
1545
|
+
errorDetails: error instanceof GatewayClientRequestError ? error.details : void 0
|
|
1546
|
+
});
|
|
1547
|
+
if (this.opts.deviceIdentity && assembled.usingStoredDeviceToken && error instanceof GatewayClientRequestError && readConnectErrorDetailCode(error.details) === ConnectErrorDetailCodes.AUTH_DEVICE_TOKEN_MISMATCH) {
|
|
1548
|
+
const deviceId = this.opts.deviceIdentity.deviceId;
|
|
1549
|
+
try {
|
|
1550
|
+
this.deps.clearDeviceAuthToken({
|
|
1551
|
+
deviceId,
|
|
1552
|
+
role,
|
|
1553
|
+
env: this.opts.env
|
|
1554
|
+
});
|
|
1555
|
+
this.logDebug(`cleared stale device-auth token for device ${deviceId}`);
|
|
1556
|
+
} catch (clearError) {
|
|
1557
|
+
this.logDebug(`failed clearing stale device-auth token for device ${deviceId}: ${String(clearError)}`);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
if (shouldRetryWithDeviceToken) {
|
|
1561
|
+
this.pendingDeviceTokenRetry = true;
|
|
1562
|
+
this.deviceTokenRetryBudgetUsed = true;
|
|
1563
|
+
this.protocol.resetReconnectBackoff(250);
|
|
1564
|
+
}
|
|
1565
|
+
const startupRetryAfterMs = resolveGatewayStartupRetryAfterMs(error);
|
|
1566
|
+
if (startupRetryAfterMs !== null) {
|
|
1567
|
+
this.logDebug(`gateway connect failed: ${formatGatewayClientErrorForLog(error)}`);
|
|
1568
|
+
return {
|
|
1569
|
+
closeCode: 1013,
|
|
1570
|
+
closeReason: "gateway starting",
|
|
1571
|
+
reconnectDelayMs: startupRetryAfterMs
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
if (this.shouldFailClosedForUnsupportedAgentRuntimeIdentity({
|
|
1575
|
+
error,
|
|
1576
|
+
authAgentRuntimeIdentityToken: assembled.authAgentRuntimeIdentityToken
|
|
1577
|
+
})) {
|
|
1578
|
+
const unsupportedIdentityError = /* @__PURE__ */ new Error("gateway rejected required agent runtime identity auth field; refusing to retry without it");
|
|
1579
|
+
this.stopped = true;
|
|
1580
|
+
this.notifyConnectError(unsupportedIdentityError);
|
|
1581
|
+
this.logError(`gateway connect failed: ${unsupportedIdentityError.message}`);
|
|
1582
|
+
return {
|
|
1583
|
+
closeCode: 1008,
|
|
1584
|
+
closeReason: "connect failed",
|
|
1585
|
+
stop: true
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
if (this.shouldRetryWithoutApprovalRuntimeToken({
|
|
1589
|
+
error,
|
|
1590
|
+
authApprovalRuntimeToken: assembled.authApprovalRuntimeToken
|
|
1591
|
+
})) {
|
|
1592
|
+
this.approvalRuntimeTokenCompatibilityDisabled = true;
|
|
1593
|
+
this.approvalRuntimeTokenRetryBudgetUsed = true;
|
|
1594
|
+
this.protocol.resetReconnectBackoff(250);
|
|
1595
|
+
this.logDebug("gateway rejected approval runtime auth field; retrying without it");
|
|
1596
|
+
return {
|
|
1597
|
+
closeCode: 1008,
|
|
1598
|
+
closeReason: "connect retry"
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
this.notifyConnectError(error);
|
|
1602
|
+
const message = `gateway connect failed: ${formatGatewayClientErrorForLog(error)}`;
|
|
1603
|
+
if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE || isGatewayClientStoppedError(error)) this.logDebug(message);
|
|
1604
|
+
else this.logError(message);
|
|
1605
|
+
return {
|
|
1606
|
+
closeCode: 1008,
|
|
1607
|
+
closeReason: "connect failed"
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
resolveClose(context) {
|
|
1611
|
+
const info = this.closeInfo(context);
|
|
1612
|
+
const detailCode = context.connectFailure?.error instanceof GatewayClientRequestError ? readConnectErrorDetailCode(context.connectFailure.error.details) : null;
|
|
1613
|
+
const details = context.connectFailure?.error instanceof GatewayClientRequestError ? context.connectFailure.error.details : void 0;
|
|
1614
|
+
if (context.code === 1013 && context.connectFailure?.reconnectDelayMs !== void 0) return {
|
|
1615
|
+
retry: true,
|
|
1616
|
+
notify: false,
|
|
1617
|
+
reconnectDelayMs: context.connectFailure.reconnectDelayMs
|
|
1618
|
+
};
|
|
1619
|
+
if (info.transientPreHelloCleanClose && this.suppressedTransientPreHelloCleanCloses < MAX_SUPPRESSED_TRANSIENT_PRE_HELLO_CLEAN_CLOSES) {
|
|
1620
|
+
this.suppressedTransientPreHelloCleanCloses += 1;
|
|
1621
|
+
return {
|
|
1622
|
+
retry: true,
|
|
1623
|
+
notify: true,
|
|
1624
|
+
pendingError: new GatewayClientTransientPreHelloCloseError()
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
if (info.transientPreHelloCleanClose || context.connectRequestSent && !context.helloReceived && !context.connectFailure) {
|
|
1628
|
+
const error = /* @__PURE__ */ new Error(`gateway closed (${context.code}): ${context.reason}`);
|
|
1629
|
+
this.notifyConnectError(error);
|
|
1630
|
+
this.logError(`gateway connect failed: ${formatGatewayClientErrorForLog(error)}`);
|
|
1631
|
+
}
|
|
1632
|
+
this.clearStaleDeviceTokenForClose(context.code, context.reason);
|
|
1633
|
+
if (shouldPauseGatewayReconnect({
|
|
1634
|
+
details,
|
|
1635
|
+
deviceTokenRetryPending: this.pendingDeviceTokenRetry,
|
|
1636
|
+
tokenMismatchIsTerminal: true,
|
|
1637
|
+
clientVersionMismatchIsTerminal: true
|
|
1638
|
+
})) {
|
|
1639
|
+
this.notifyReconnectPaused({
|
|
1640
|
+
code: context.code,
|
|
1641
|
+
reason: context.reason,
|
|
1642
|
+
detailCode
|
|
1643
|
+
});
|
|
1644
|
+
return {
|
|
1645
|
+
retry: false,
|
|
1646
|
+
notify: true
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
return {
|
|
1650
|
+
retry: true,
|
|
1651
|
+
notify: true,
|
|
1652
|
+
reconnectDelayMs: context.connectFailure?.reconnectDelayMs
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
closeInfo(context) {
|
|
1656
|
+
return {
|
|
1657
|
+
phase: context.helloReceived ? "post-hello" : "pre-hello",
|
|
1658
|
+
socketOpened: context.socketOpened,
|
|
1659
|
+
transportValidated: this.transportValidated,
|
|
1660
|
+
transientPreHelloCleanClose: !context.helloReceived && context.code === 1e3 && context.reason === ""
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
clearStaleDeviceTokenForClose(code, reason) {
|
|
1664
|
+
if (code !== 1008 || !normalizeLowercaseStringOrEmpty(reason).includes("device token mismatch") || this.opts.token || this.opts.password || !this.opts.deviceIdentity) return;
|
|
1665
|
+
const deviceId = this.opts.deviceIdentity.deviceId;
|
|
1666
|
+
const role = this.opts.role ?? "operator";
|
|
1667
|
+
try {
|
|
1668
|
+
this.deps.clearDeviceAuthToken({
|
|
1669
|
+
deviceId,
|
|
1670
|
+
role,
|
|
1671
|
+
env: this.opts.env
|
|
1672
|
+
});
|
|
1673
|
+
this.logDebug(`cleared stale device-auth token for device ${deviceId}`);
|
|
1674
|
+
} catch (error) {
|
|
1675
|
+
this.logDebug(`failed clearing stale device-auth token for device ${deviceId}: ${String(error)}`);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
notifyConnectError(error) {
|
|
1679
|
+
try {
|
|
1680
|
+
this.opts.onConnectError?.(error);
|
|
1681
|
+
} catch (err) {
|
|
1682
|
+
this.logDebug(`gateway client connect error handler error: ${formatGatewayClientErrorForLog(err)}`);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
notifyReconnectPaused(info) {
|
|
1686
|
+
try {
|
|
1687
|
+
this.opts.onReconnectPaused?.(info);
|
|
1688
|
+
} catch (err) {
|
|
1689
|
+
this.logDebug(`gateway client reconnect paused handler error: ${formatGatewayClientErrorForLog(err)}`);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
shouldRetryWithoutApprovalRuntimeToken(params) {
|
|
1693
|
+
if (this.approvalRuntimeTokenRetryBudgetUsed) return false;
|
|
1694
|
+
if (!params.authApprovalRuntimeToken) return false;
|
|
1695
|
+
if (!(params.error instanceof GatewayClientRequestError)) return false;
|
|
1696
|
+
if (params.error.gatewayCode !== "INVALID_REQUEST") return false;
|
|
1697
|
+
const message = normalizeLowercaseStringOrEmpty(params.error.message);
|
|
1698
|
+
return message.includes("invalid connect params") && message.includes("approvalruntimetoken");
|
|
1699
|
+
}
|
|
1700
|
+
shouldFailClosedForUnsupportedAgentRuntimeIdentity(params) {
|
|
1701
|
+
if (!params.authAgentRuntimeIdentityToken) return false;
|
|
1702
|
+
if (!(params.error instanceof GatewayClientRequestError)) return false;
|
|
1703
|
+
if (params.error.gatewayCode !== "INVALID_REQUEST") return false;
|
|
1704
|
+
const message = normalizeLowercaseStringOrEmpty(params.error.message);
|
|
1705
|
+
return message.includes("invalid connect params") && message.includes("agentruntimeidentitytoken");
|
|
1706
|
+
}
|
|
1707
|
+
isTrustedDeviceRetryEndpoint() {
|
|
1708
|
+
const rawUrl = this.opts.url ?? "ws://127.0.0.1:18789";
|
|
1709
|
+
try {
|
|
1710
|
+
const parsed = new URL(rawUrl);
|
|
1711
|
+
const protocol = parsed.protocol === "https:" ? "wss:" : parsed.protocol === "http:" ? "ws:" : parsed.protocol;
|
|
1712
|
+
if (isLoopbackHost(parsed.hostname)) return true;
|
|
1713
|
+
return protocol === "wss:" && Boolean(this.opts.tlsFingerprint?.trim());
|
|
1714
|
+
} catch {
|
|
1715
|
+
return false;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
selectConnectAuth(role) {
|
|
1719
|
+
const storedAuth = this.opts.deviceIdentity ? this.deps.loadDeviceAuthToken({
|
|
1720
|
+
deviceId: this.opts.deviceIdentity.deviceId,
|
|
1721
|
+
role,
|
|
1722
|
+
env: this.opts.env
|
|
1723
|
+
}) : null;
|
|
1724
|
+
return selectGatewayConnectAuth({
|
|
1725
|
+
token: this.opts.token,
|
|
1726
|
+
bootstrapToken: this.opts.bootstrapToken,
|
|
1727
|
+
deviceToken: this.opts.deviceToken,
|
|
1728
|
+
password: this.opts.password,
|
|
1729
|
+
approvalRuntimeToken: this.approvalRuntimeTokenCompatibilityDisabled ? void 0 : this.opts.approvalRuntimeToken,
|
|
1730
|
+
agentRuntimeIdentityToken: this.opts.agentRuntimeIdentityToken,
|
|
1731
|
+
storedToken: storedAuth?.token,
|
|
1732
|
+
storedScopes: storedAuth?.scopes,
|
|
1733
|
+
pendingDeviceTokenRetry: this.pendingDeviceTokenRetry,
|
|
1734
|
+
trustedDeviceTokenRetry: this.isTrustedDeviceRetryEndpoint()
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
startTickWatch() {
|
|
1738
|
+
if (this.tickTimer) clearInterval(this.tickTimer);
|
|
1739
|
+
const rawMinInterval = this.opts.tickWatchMinIntervalMs;
|
|
1740
|
+
const minInterval = typeof rawMinInterval === "number" && Number.isFinite(rawMinInterval) ? Math.max(1, Math.min(3e4, rawMinInterval)) : 1e3;
|
|
1741
|
+
const interval = resolveSafeTimeoutDelayMs(Math.max(this.tickIntervalMs, minInterval));
|
|
1742
|
+
this.tickTimer = setInterval(() => {
|
|
1743
|
+
if (this.stopped) return;
|
|
1744
|
+
if (!this.lastTick) return;
|
|
1745
|
+
if (this.protocol.hasPendingRequests && !this.protocol.hasUnboundedPendingRequests) return;
|
|
1746
|
+
const gap = Date.now() - this.lastTick;
|
|
1747
|
+
const rawTimeoutMs = this.opts.tickWatchTimeoutMs;
|
|
1748
|
+
if (gap > (typeof rawTimeoutMs === "number" && Number.isFinite(rawTimeoutMs) ? Math.max(1, rawTimeoutMs) : this.tickIntervalMs * 2)) this.protocol.closeSocket(4e3, "tick timeout");
|
|
1749
|
+
}, interval);
|
|
1750
|
+
}
|
|
1751
|
+
validateTlsFingerprint() {
|
|
1752
|
+
if (!this.opts.tlsFingerprint || !this.ws) return null;
|
|
1753
|
+
const expected = this.deps.normalizeTlsFingerprint(this.opts.tlsFingerprint);
|
|
1754
|
+
if (!expected) return /* @__PURE__ */ new Error("gateway tls fingerprint missing");
|
|
1755
|
+
const socket = this.ws["_socket"];
|
|
1756
|
+
if (!socket || typeof socket.getPeerCertificate !== "function") return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
|
|
1757
|
+
const cert = socket.getPeerCertificate();
|
|
1758
|
+
const fingerprint = this.deps.normalizeTlsFingerprint(cert?.fingerprint256 ?? "");
|
|
1759
|
+
if (!fingerprint) return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
|
|
1760
|
+
if (fingerprint !== expected) return /* @__PURE__ */ new Error("gateway tls fingerprint mismatch");
|
|
1761
|
+
return null;
|
|
1762
|
+
}
|
|
1763
|
+
async request(method, params, opts) {
|
|
1764
|
+
const expectFinal = opts?.expectFinal === true;
|
|
1765
|
+
const timeoutMs = opts?.timeoutMs === null ? null : typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) ? resolveSafeTimeoutDelayMs(opts.timeoutMs, { minMs: 0 }) : expectFinal ? null : this.requestTimeoutMs;
|
|
1766
|
+
return this.protocol.request(method, params, {
|
|
1767
|
+
expectFinal,
|
|
1768
|
+
timeoutMs,
|
|
1769
|
+
signal: opts?.signal,
|
|
1770
|
+
onSent: opts?.onSent,
|
|
1771
|
+
onAccepted: opts?.onAccepted
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
function createGatewayRequestAbortError(method) {
|
|
1776
|
+
const err = /* @__PURE__ */ new Error(`gateway request aborted for ${method}`);
|
|
1777
|
+
err.name = "AbortError";
|
|
1778
|
+
return err;
|
|
1779
|
+
}
|
|
1780
|
+
//#endregion
|
|
1781
|
+
export { DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, GatewayBrowserDeviceAuthLifecycle, GatewayClient, GatewayClientRequestError, MAX_CONNECT_CHALLENGE_TIMEOUT_MS, MAX_SAFE_TIMEOUT_DELAY_MS, MIN_CONNECT_CHALLENGE_TIMEOUT_MS, addSafeTimeoutDelayGraceMs, buildDeviceAuthPayload, buildDeviceAuthPayloadV3, buildGatewayConnectAuth, clampConnectChallengeTimeoutMs, getConnectChallengeTimeoutMsFromEnv, isGatewayConnectAssemblyError, normalizeDeviceMetadataForAuth, resolveConnectChallengeTimeoutMs, resolveFiniteTimeoutDelayMs, resolveGatewayConnectScopes, resolvePreauthHandshakeTimeoutMs, resolveSafeTimeoutDelayMs, selectGatewayConnectAuth, shouldRetryGatewayWithDeviceToken, startGatewayClientWhenEventLoopReady, startGatewayClientWithReadinessWait, waitForEventLoopReady };
|