browserclaw 0.5.3 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -10,35 +10,826 @@ var promises = require('dns/promises');
10
10
  var dns = require('dns');
11
11
  var promises$1 = require('fs/promises');
12
12
  var crypto = require('crypto');
13
- var ipaddr = require('ipaddr.js');
14
13
 
15
14
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
16
15
 
17
- function _interopNamespace(e) {
18
- if (e && e.__esModule) return e;
19
- var n = Object.create(null);
20
- if (e) {
21
- Object.keys(e).forEach(function (k) {
22
- if (k !== 'default') {
23
- var d = Object.getOwnPropertyDescriptor(e, k);
24
- Object.defineProperty(n, k, d.get ? d : {
25
- enumerable: true,
26
- get: function () { return e[k]; }
27
- });
28
- }
29
- });
30
- }
31
- n.default = e;
32
- return Object.freeze(n);
33
- }
34
-
35
16
  var os__default = /*#__PURE__*/_interopDefault(os);
36
17
  var path__default = /*#__PURE__*/_interopDefault(path);
37
18
  var fs__default = /*#__PURE__*/_interopDefault(fs);
38
19
  var net__default = /*#__PURE__*/_interopDefault(net);
39
- var ipaddr__namespace = /*#__PURE__*/_interopNamespace(ipaddr);
40
20
 
41
- // src/chrome-launcher.ts
21
+ var __create = Object.create;
22
+ var __defProp = Object.defineProperty;
23
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
24
+ var __getOwnPropNames = Object.getOwnPropertyNames;
25
+ var __getProtoOf = Object.getPrototypeOf;
26
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
27
+ var __commonJS = (cb, mod) => function __require() {
28
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
29
+ };
30
+ var __copyProps = (to, from, except, desc) => {
31
+ if (from && typeof from === "object" || typeof from === "function") {
32
+ for (let key of __getOwnPropNames(from))
33
+ if (!__hasOwnProp.call(to, key) && key !== except)
34
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
35
+ }
36
+ return to;
37
+ };
38
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
39
+ // If the importer is in node compatibility mode or this is not an ESM
40
+ // file that has been converted to a CommonJS file using a Babel-
41
+ // compatible transform (i.e. "__esModule" has not been set), then set
42
+ // "default" to the CommonJS "module.exports" for node compatibility.
43
+ __defProp(target, "default", { value: mod, enumerable: true }) ,
44
+ mod
45
+ ));
46
+
47
+ // node_modules/ipaddr.js/lib/ipaddr.js
48
+ var require_ipaddr = __commonJS({
49
+ "node_modules/ipaddr.js/lib/ipaddr.js"(exports$1, module) {
50
+ (function(root) {
51
+ const ipv4Part = "(0?\\d+|0x[a-f0-9]+)";
52
+ const ipv4Regexes = {
53
+ fourOctet: new RegExp(`^${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}$`, "i"),
54
+ threeOctet: new RegExp(`^${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}$`, "i"),
55
+ twoOctet: new RegExp(`^${ipv4Part}\\.${ipv4Part}$`, "i"),
56
+ longValue: new RegExp(`^${ipv4Part}$`, "i")
57
+ };
58
+ const octalRegex = new RegExp(`^0[0-7]+$`, "i");
59
+ const hexRegex = new RegExp(`^0x[a-f0-9]+$`, "i");
60
+ const zoneIndex = "%[0-9a-z]{1,}";
61
+ const ipv6Part = "(?:[0-9a-f]+::?)+";
62
+ const ipv6Regexes = {
63
+ zoneIndex: new RegExp(zoneIndex, "i"),
64
+ "native": new RegExp(`^(::)?(${ipv6Part})?([0-9a-f]+)?(::)?(${zoneIndex})?$`, "i"),
65
+ deprecatedTransitional: new RegExp(`^(?:::)(${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}(${zoneIndex})?)$`, "i"),
66
+ transitional: new RegExp(`^((?:${ipv6Part})|(?:::)(?:${ipv6Part})?)${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}\\.${ipv4Part}(${zoneIndex})?$`, "i")
67
+ };
68
+ function expandIPv6(string, parts) {
69
+ if (string.indexOf("::") !== string.lastIndexOf("::")) {
70
+ return null;
71
+ }
72
+ let colonCount = 0;
73
+ let lastColon = -1;
74
+ let zoneId = (string.match(ipv6Regexes.zoneIndex) || [])[0];
75
+ let replacement, replacementCount;
76
+ if (zoneId) {
77
+ zoneId = zoneId.substring(1);
78
+ string = string.replace(/%.+$/, "");
79
+ }
80
+ while ((lastColon = string.indexOf(":", lastColon + 1)) >= 0) {
81
+ colonCount++;
82
+ }
83
+ if (string.substr(0, 2) === "::") {
84
+ colonCount--;
85
+ }
86
+ if (string.substr(-2, 2) === "::") {
87
+ colonCount--;
88
+ }
89
+ if (colonCount > parts) {
90
+ return null;
91
+ }
92
+ replacementCount = parts - colonCount;
93
+ replacement = ":";
94
+ while (replacementCount--) {
95
+ replacement += "0:";
96
+ }
97
+ string = string.replace("::", replacement);
98
+ if (string[0] === ":") {
99
+ string = string.slice(1);
100
+ }
101
+ if (string[string.length - 1] === ":") {
102
+ string = string.slice(0, -1);
103
+ }
104
+ parts = (function() {
105
+ const ref = string.split(":");
106
+ const results = [];
107
+ for (let i = 0; i < ref.length; i++) {
108
+ results.push(parseInt(ref[i], 16));
109
+ }
110
+ return results;
111
+ })();
112
+ return {
113
+ parts,
114
+ zoneId
115
+ };
116
+ }
117
+ function matchCIDR(first, second, partSize, cidrBits) {
118
+ if (first.length !== second.length) {
119
+ throw new Error("ipaddr: cannot match CIDR for objects with different lengths");
120
+ }
121
+ let part = 0;
122
+ let shift;
123
+ while (cidrBits > 0) {
124
+ shift = partSize - cidrBits;
125
+ if (shift < 0) {
126
+ shift = 0;
127
+ }
128
+ if (first[part] >> shift !== second[part] >> shift) {
129
+ return false;
130
+ }
131
+ cidrBits -= partSize;
132
+ part += 1;
133
+ }
134
+ return true;
135
+ }
136
+ function parseIntAuto(string) {
137
+ if (hexRegex.test(string)) {
138
+ return parseInt(string, 16);
139
+ }
140
+ if (string[0] === "0" && !isNaN(parseInt(string[1], 10))) {
141
+ if (octalRegex.test(string)) {
142
+ return parseInt(string, 8);
143
+ }
144
+ throw new Error(`ipaddr: cannot parse ${string} as octal`);
145
+ }
146
+ return parseInt(string, 10);
147
+ }
148
+ function padPart(part, length) {
149
+ while (part.length < length) {
150
+ part = `0${part}`;
151
+ }
152
+ return part;
153
+ }
154
+ const ipaddr2 = {};
155
+ ipaddr2.IPv4 = (function() {
156
+ function IPv42(octets) {
157
+ if (octets.length !== 4) {
158
+ throw new Error("ipaddr: ipv4 octet count should be 4");
159
+ }
160
+ let i, octet;
161
+ for (i = 0; i < octets.length; i++) {
162
+ octet = octets[i];
163
+ if (!(0 <= octet && octet <= 255)) {
164
+ throw new Error("ipaddr: ipv4 octet should fit in 8 bits");
165
+ }
166
+ }
167
+ this.octets = octets;
168
+ }
169
+ IPv42.prototype.SpecialRanges = {
170
+ unspecified: [[new IPv42([0, 0, 0, 0]), 8]],
171
+ broadcast: [[new IPv42([255, 255, 255, 255]), 32]],
172
+ // RFC3171
173
+ multicast: [[new IPv42([224, 0, 0, 0]), 4]],
174
+ // RFC3927
175
+ linkLocal: [[new IPv42([169, 254, 0, 0]), 16]],
176
+ // RFC5735
177
+ loopback: [[new IPv42([127, 0, 0, 0]), 8]],
178
+ // RFC6598
179
+ carrierGradeNat: [[new IPv42([100, 64, 0, 0]), 10]],
180
+ // RFC1918
181
+ "private": [
182
+ [new IPv42([10, 0, 0, 0]), 8],
183
+ [new IPv42([172, 16, 0, 0]), 12],
184
+ [new IPv42([192, 168, 0, 0]), 16]
185
+ ],
186
+ // Reserved and testing-only ranges; RFCs 5735, 5737, 2544, 1700
187
+ reserved: [
188
+ [new IPv42([192, 0, 0, 0]), 24],
189
+ [new IPv42([192, 0, 2, 0]), 24],
190
+ [new IPv42([192, 88, 99, 0]), 24],
191
+ [new IPv42([198, 18, 0, 0]), 15],
192
+ [new IPv42([198, 51, 100, 0]), 24],
193
+ [new IPv42([203, 0, 113, 0]), 24],
194
+ [new IPv42([240, 0, 0, 0]), 4]
195
+ ],
196
+ // RFC7534, RFC7535
197
+ as112: [
198
+ [new IPv42([192, 175, 48, 0]), 24],
199
+ [new IPv42([192, 31, 196, 0]), 24]
200
+ ],
201
+ // RFC7450
202
+ amt: [
203
+ [new IPv42([192, 52, 193, 0]), 24]
204
+ ]
205
+ };
206
+ IPv42.prototype.kind = function() {
207
+ return "ipv4";
208
+ };
209
+ IPv42.prototype.match = function(other, cidrRange) {
210
+ let ref;
211
+ if (cidrRange === void 0) {
212
+ ref = other;
213
+ other = ref[0];
214
+ cidrRange = ref[1];
215
+ }
216
+ if (other.kind() !== "ipv4") {
217
+ throw new Error("ipaddr: cannot match ipv4 address with non-ipv4 one");
218
+ }
219
+ return matchCIDR(this.octets, other.octets, 8, cidrRange);
220
+ };
221
+ IPv42.prototype.prefixLengthFromSubnetMask = function() {
222
+ let cidr = 0;
223
+ let stop = false;
224
+ const zerotable = {
225
+ 0: 8,
226
+ 128: 7,
227
+ 192: 6,
228
+ 224: 5,
229
+ 240: 4,
230
+ 248: 3,
231
+ 252: 2,
232
+ 254: 1,
233
+ 255: 0
234
+ };
235
+ let i, octet, zeros;
236
+ for (i = 3; i >= 0; i -= 1) {
237
+ octet = this.octets[i];
238
+ if (octet in zerotable) {
239
+ zeros = zerotable[octet];
240
+ if (stop && zeros !== 0) {
241
+ return null;
242
+ }
243
+ if (zeros !== 8) {
244
+ stop = true;
245
+ }
246
+ cidr += zeros;
247
+ } else {
248
+ return null;
249
+ }
250
+ }
251
+ return 32 - cidr;
252
+ };
253
+ IPv42.prototype.range = function() {
254
+ return ipaddr2.subnetMatch(this, this.SpecialRanges);
255
+ };
256
+ IPv42.prototype.toByteArray = function() {
257
+ return this.octets.slice(0);
258
+ };
259
+ IPv42.prototype.toIPv4MappedAddress = function() {
260
+ return ipaddr2.IPv6.parse(`::ffff:${this.toString()}`);
261
+ };
262
+ IPv42.prototype.toNormalizedString = function() {
263
+ return this.toString();
264
+ };
265
+ IPv42.prototype.toString = function() {
266
+ return this.octets.join(".");
267
+ };
268
+ return IPv42;
269
+ })();
270
+ ipaddr2.IPv4.broadcastAddressFromCIDR = function(string) {
271
+ try {
272
+ const cidr = this.parseCIDR(string);
273
+ const ipInterfaceOctets = cidr[0].toByteArray();
274
+ const subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray();
275
+ const octets = [];
276
+ let i = 0;
277
+ while (i < 4) {
278
+ octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255);
279
+ i++;
280
+ }
281
+ return new this(octets);
282
+ } catch (e) {
283
+ throw new Error("ipaddr: the address does not have IPv4 CIDR format");
284
+ }
285
+ };
286
+ ipaddr2.IPv4.isIPv4 = function(string) {
287
+ return this.parser(string) !== null;
288
+ };
289
+ ipaddr2.IPv4.isValid = function(string) {
290
+ try {
291
+ new this(this.parser(string));
292
+ return true;
293
+ } catch (e) {
294
+ return false;
295
+ }
296
+ };
297
+ ipaddr2.IPv4.isValidCIDR = function(string) {
298
+ try {
299
+ this.parseCIDR(string);
300
+ return true;
301
+ } catch (e) {
302
+ return false;
303
+ }
304
+ };
305
+ ipaddr2.IPv4.isValidFourPartDecimal = function(string) {
306
+ if (ipaddr2.IPv4.isValid(string) && string.match(/^(0|[1-9]\d*)(\.(0|[1-9]\d*)){3}$/)) {
307
+ return true;
308
+ } else {
309
+ return false;
310
+ }
311
+ };
312
+ ipaddr2.IPv4.isValidCIDRFourPartDecimal = function(string) {
313
+ const match = string.match(/^(.+)\/(\d+)$/);
314
+ if (!ipaddr2.IPv4.isValidCIDR(string) || !match) {
315
+ return false;
316
+ }
317
+ return ipaddr2.IPv4.isValidFourPartDecimal(match[1]);
318
+ };
319
+ ipaddr2.IPv4.networkAddressFromCIDR = function(string) {
320
+ let cidr, i, ipInterfaceOctets, octets, subnetMaskOctets;
321
+ try {
322
+ cidr = this.parseCIDR(string);
323
+ ipInterfaceOctets = cidr[0].toByteArray();
324
+ subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray();
325
+ octets = [];
326
+ i = 0;
327
+ while (i < 4) {
328
+ octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10));
329
+ i++;
330
+ }
331
+ return new this(octets);
332
+ } catch (e) {
333
+ throw new Error("ipaddr: the address does not have IPv4 CIDR format");
334
+ }
335
+ };
336
+ ipaddr2.IPv4.parse = function(string) {
337
+ const parts = this.parser(string);
338
+ if (parts === null) {
339
+ throw new Error("ipaddr: string is not formatted like an IPv4 Address");
340
+ }
341
+ return new this(parts);
342
+ };
343
+ ipaddr2.IPv4.parseCIDR = function(string) {
344
+ let match;
345
+ if (match = string.match(/^(.+)\/(\d+)$/)) {
346
+ const maskLength = parseInt(match[2]);
347
+ if (maskLength >= 0 && maskLength <= 32) {
348
+ const parsed = [this.parse(match[1]), maskLength];
349
+ Object.defineProperty(parsed, "toString", {
350
+ value: function() {
351
+ return this.join("/");
352
+ }
353
+ });
354
+ return parsed;
355
+ }
356
+ }
357
+ throw new Error("ipaddr: string is not formatted like an IPv4 CIDR range");
358
+ };
359
+ ipaddr2.IPv4.parser = function(string) {
360
+ let match, part, value;
361
+ if (match = string.match(ipv4Regexes.fourOctet)) {
362
+ return (function() {
363
+ const ref = match.slice(1, 6);
364
+ const results = [];
365
+ for (let i = 0; i < ref.length; i++) {
366
+ part = ref[i];
367
+ results.push(parseIntAuto(part));
368
+ }
369
+ return results;
370
+ })();
371
+ } else if (match = string.match(ipv4Regexes.longValue)) {
372
+ value = parseIntAuto(match[1]);
373
+ if (value > 4294967295 || value < 0) {
374
+ throw new Error("ipaddr: address outside defined range");
375
+ }
376
+ return (function() {
377
+ const results = [];
378
+ let shift;
379
+ for (shift = 0; shift <= 24; shift += 8) {
380
+ results.push(value >> shift & 255);
381
+ }
382
+ return results;
383
+ })().reverse();
384
+ } else if (match = string.match(ipv4Regexes.twoOctet)) {
385
+ return (function() {
386
+ const ref = match.slice(1, 4);
387
+ const results = [];
388
+ value = parseIntAuto(ref[1]);
389
+ if (value > 16777215 || value < 0) {
390
+ throw new Error("ipaddr: address outside defined range");
391
+ }
392
+ results.push(parseIntAuto(ref[0]));
393
+ results.push(value >> 16 & 255);
394
+ results.push(value >> 8 & 255);
395
+ results.push(value & 255);
396
+ return results;
397
+ })();
398
+ } else if (match = string.match(ipv4Regexes.threeOctet)) {
399
+ return (function() {
400
+ const ref = match.slice(1, 5);
401
+ const results = [];
402
+ value = parseIntAuto(ref[2]);
403
+ if (value > 65535 || value < 0) {
404
+ throw new Error("ipaddr: address outside defined range");
405
+ }
406
+ results.push(parseIntAuto(ref[0]));
407
+ results.push(parseIntAuto(ref[1]));
408
+ results.push(value >> 8 & 255);
409
+ results.push(value & 255);
410
+ return results;
411
+ })();
412
+ } else {
413
+ return null;
414
+ }
415
+ };
416
+ ipaddr2.IPv4.subnetMaskFromPrefixLength = function(prefix) {
417
+ prefix = parseInt(prefix);
418
+ if (prefix < 0 || prefix > 32) {
419
+ throw new Error("ipaddr: invalid IPv4 prefix length");
420
+ }
421
+ const octets = [0, 0, 0, 0];
422
+ let j = 0;
423
+ const filledOctetCount = Math.floor(prefix / 8);
424
+ while (j < filledOctetCount) {
425
+ octets[j] = 255;
426
+ j++;
427
+ }
428
+ if (filledOctetCount < 4) {
429
+ octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - prefix % 8;
430
+ }
431
+ return new this(octets);
432
+ };
433
+ ipaddr2.IPv6 = (function() {
434
+ function IPv62(parts, zoneId) {
435
+ let i, part;
436
+ if (parts.length === 16) {
437
+ this.parts = [];
438
+ for (i = 0; i <= 14; i += 2) {
439
+ this.parts.push(parts[i] << 8 | parts[i + 1]);
440
+ }
441
+ } else if (parts.length === 8) {
442
+ this.parts = parts;
443
+ } else {
444
+ throw new Error("ipaddr: ipv6 part count should be 8 or 16");
445
+ }
446
+ for (i = 0; i < this.parts.length; i++) {
447
+ part = this.parts[i];
448
+ if (!(0 <= part && part <= 65535)) {
449
+ throw new Error("ipaddr: ipv6 part should fit in 16 bits");
450
+ }
451
+ }
452
+ if (zoneId) {
453
+ this.zoneId = zoneId;
454
+ }
455
+ }
456
+ IPv62.prototype.SpecialRanges = {
457
+ // RFC4291, here and after
458
+ unspecified: [new IPv62([0, 0, 0, 0, 0, 0, 0, 0]), 128],
459
+ linkLocal: [new IPv62([65152, 0, 0, 0, 0, 0, 0, 0]), 10],
460
+ multicast: [new IPv62([65280, 0, 0, 0, 0, 0, 0, 0]), 8],
461
+ loopback: [new IPv62([0, 0, 0, 0, 0, 0, 0, 1]), 128],
462
+ uniqueLocal: [new IPv62([64512, 0, 0, 0, 0, 0, 0, 0]), 7],
463
+ ipv4Mapped: [new IPv62([0, 0, 0, 0, 0, 65535, 0, 0]), 96],
464
+ // RFC6666
465
+ discard: [new IPv62([256, 0, 0, 0, 0, 0, 0, 0]), 64],
466
+ // RFC6145
467
+ rfc6145: [new IPv62([0, 0, 0, 0, 65535, 0, 0, 0]), 96],
468
+ // RFC6052
469
+ rfc6052: [new IPv62([100, 65435, 0, 0, 0, 0, 0, 0]), 96],
470
+ // RFC3056
471
+ "6to4": [new IPv62([8194, 0, 0, 0, 0, 0, 0, 0]), 16],
472
+ // RFC6052, RFC6146
473
+ teredo: [new IPv62([8193, 0, 0, 0, 0, 0, 0, 0]), 32],
474
+ // RFC5180
475
+ benchmarking: [new IPv62([8193, 2, 0, 0, 0, 0, 0, 0]), 48],
476
+ // RFC7450
477
+ amt: [new IPv62([8193, 3, 0, 0, 0, 0, 0, 0]), 32],
478
+ as112v6: [
479
+ [new IPv62([8193, 4, 274, 0, 0, 0, 0, 0]), 48],
480
+ [new IPv62([9760, 79, 32768, 0, 0, 0, 0, 0]), 48]
481
+ ],
482
+ deprecated: [new IPv62([8193, 16, 0, 0, 0, 0, 0, 0]), 28],
483
+ orchid2: [new IPv62([8193, 32, 0, 0, 0, 0, 0, 0]), 28],
484
+ droneRemoteIdProtocolEntityTags: [new IPv62([8193, 48, 0, 0, 0, 0, 0, 0]), 28],
485
+ reserved: [
486
+ // RFC3849
487
+ [new IPv62([8193, 0, 0, 0, 0, 0, 0, 0]), 23],
488
+ // RFC2928
489
+ [new IPv62([8193, 3512, 0, 0, 0, 0, 0, 0]), 32]
490
+ ]
491
+ };
492
+ IPv62.prototype.isIPv4MappedAddress = function() {
493
+ return this.range() === "ipv4Mapped";
494
+ };
495
+ IPv62.prototype.kind = function() {
496
+ return "ipv6";
497
+ };
498
+ IPv62.prototype.match = function(other, cidrRange) {
499
+ let ref;
500
+ if (cidrRange === void 0) {
501
+ ref = other;
502
+ other = ref[0];
503
+ cidrRange = ref[1];
504
+ }
505
+ if (other.kind() !== "ipv6") {
506
+ throw new Error("ipaddr: cannot match ipv6 address with non-ipv6 one");
507
+ }
508
+ return matchCIDR(this.parts, other.parts, 16, cidrRange);
509
+ };
510
+ IPv62.prototype.prefixLengthFromSubnetMask = function() {
511
+ let cidr = 0;
512
+ let stop = false;
513
+ const zerotable = {
514
+ 0: 16,
515
+ 32768: 15,
516
+ 49152: 14,
517
+ 57344: 13,
518
+ 61440: 12,
519
+ 63488: 11,
520
+ 64512: 10,
521
+ 65024: 9,
522
+ 65280: 8,
523
+ 65408: 7,
524
+ 65472: 6,
525
+ 65504: 5,
526
+ 65520: 4,
527
+ 65528: 3,
528
+ 65532: 2,
529
+ 65534: 1,
530
+ 65535: 0
531
+ };
532
+ let part, zeros;
533
+ for (let i = 7; i >= 0; i -= 1) {
534
+ part = this.parts[i];
535
+ if (part in zerotable) {
536
+ zeros = zerotable[part];
537
+ if (stop && zeros !== 0) {
538
+ return null;
539
+ }
540
+ if (zeros !== 16) {
541
+ stop = true;
542
+ }
543
+ cidr += zeros;
544
+ } else {
545
+ return null;
546
+ }
547
+ }
548
+ return 128 - cidr;
549
+ };
550
+ IPv62.prototype.range = function() {
551
+ return ipaddr2.subnetMatch(this, this.SpecialRanges);
552
+ };
553
+ IPv62.prototype.toByteArray = function() {
554
+ let part;
555
+ const bytes = [];
556
+ const ref = this.parts;
557
+ for (let i = 0; i < ref.length; i++) {
558
+ part = ref[i];
559
+ bytes.push(part >> 8);
560
+ bytes.push(part & 255);
561
+ }
562
+ return bytes;
563
+ };
564
+ IPv62.prototype.toFixedLengthString = function() {
565
+ const addr = (function() {
566
+ const results = [];
567
+ for (let i = 0; i < this.parts.length; i++) {
568
+ results.push(padPart(this.parts[i].toString(16), 4));
569
+ }
570
+ return results;
571
+ }).call(this).join(":");
572
+ let suffix = "";
573
+ if (this.zoneId) {
574
+ suffix = `%${this.zoneId}`;
575
+ }
576
+ return addr + suffix;
577
+ };
578
+ IPv62.prototype.toIPv4Address = function() {
579
+ if (!this.isIPv4MappedAddress()) {
580
+ throw new Error("ipaddr: trying to convert a generic ipv6 address to ipv4");
581
+ }
582
+ const ref = this.parts.slice(-2);
583
+ const high = ref[0];
584
+ const low = ref[1];
585
+ return new ipaddr2.IPv4([high >> 8, high & 255, low >> 8, low & 255]);
586
+ };
587
+ IPv62.prototype.toNormalizedString = function() {
588
+ const addr = (function() {
589
+ const results = [];
590
+ for (let i = 0; i < this.parts.length; i++) {
591
+ results.push(this.parts[i].toString(16));
592
+ }
593
+ return results;
594
+ }).call(this).join(":");
595
+ let suffix = "";
596
+ if (this.zoneId) {
597
+ suffix = `%${this.zoneId}`;
598
+ }
599
+ return addr + suffix;
600
+ };
601
+ IPv62.prototype.toRFC5952String = function() {
602
+ const regex = /((^|:)(0(:|$)){2,})/g;
603
+ const string = this.toNormalizedString();
604
+ let bestMatchIndex = 0;
605
+ let bestMatchLength = -1;
606
+ let match;
607
+ while (match = regex.exec(string)) {
608
+ if (match[0].length > bestMatchLength) {
609
+ bestMatchIndex = match.index;
610
+ bestMatchLength = match[0].length;
611
+ }
612
+ }
613
+ if (bestMatchLength < 0) {
614
+ return string;
615
+ }
616
+ return `${string.substring(0, bestMatchIndex)}::${string.substring(bestMatchIndex + bestMatchLength)}`;
617
+ };
618
+ IPv62.prototype.toString = function() {
619
+ return this.toRFC5952String();
620
+ };
621
+ return IPv62;
622
+ })();
623
+ ipaddr2.IPv6.broadcastAddressFromCIDR = function(string) {
624
+ try {
625
+ const cidr = this.parseCIDR(string);
626
+ const ipInterfaceOctets = cidr[0].toByteArray();
627
+ const subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray();
628
+ const octets = [];
629
+ let i = 0;
630
+ while (i < 16) {
631
+ octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255);
632
+ i++;
633
+ }
634
+ return new this(octets);
635
+ } catch (e) {
636
+ throw new Error(`ipaddr: the address does not have IPv6 CIDR format (${e})`);
637
+ }
638
+ };
639
+ ipaddr2.IPv6.isIPv6 = function(string) {
640
+ return this.parser(string) !== null;
641
+ };
642
+ ipaddr2.IPv6.isValid = function(string) {
643
+ if (typeof string === "string" && string.indexOf(":") === -1) {
644
+ return false;
645
+ }
646
+ try {
647
+ const addr = this.parser(string);
648
+ new this(addr.parts, addr.zoneId);
649
+ return true;
650
+ } catch (e) {
651
+ return false;
652
+ }
653
+ };
654
+ ipaddr2.IPv6.isValidCIDR = function(string) {
655
+ if (typeof string === "string" && string.indexOf(":") === -1) {
656
+ return false;
657
+ }
658
+ try {
659
+ this.parseCIDR(string);
660
+ return true;
661
+ } catch (e) {
662
+ return false;
663
+ }
664
+ };
665
+ ipaddr2.IPv6.networkAddressFromCIDR = function(string) {
666
+ let cidr, i, ipInterfaceOctets, octets, subnetMaskOctets;
667
+ try {
668
+ cidr = this.parseCIDR(string);
669
+ ipInterfaceOctets = cidr[0].toByteArray();
670
+ subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray();
671
+ octets = [];
672
+ i = 0;
673
+ while (i < 16) {
674
+ octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10));
675
+ i++;
676
+ }
677
+ return new this(octets);
678
+ } catch (e) {
679
+ throw new Error(`ipaddr: the address does not have IPv6 CIDR format (${e})`);
680
+ }
681
+ };
682
+ ipaddr2.IPv6.parse = function(string) {
683
+ const addr = this.parser(string);
684
+ if (addr.parts === null) {
685
+ throw new Error("ipaddr: string is not formatted like an IPv6 Address");
686
+ }
687
+ return new this(addr.parts, addr.zoneId);
688
+ };
689
+ ipaddr2.IPv6.parseCIDR = function(string) {
690
+ let maskLength, match, parsed;
691
+ if (match = string.match(/^(.+)\/(\d+)$/)) {
692
+ maskLength = parseInt(match[2]);
693
+ if (maskLength >= 0 && maskLength <= 128) {
694
+ parsed = [this.parse(match[1]), maskLength];
695
+ Object.defineProperty(parsed, "toString", {
696
+ value: function() {
697
+ return this.join("/");
698
+ }
699
+ });
700
+ return parsed;
701
+ }
702
+ }
703
+ throw new Error("ipaddr: string is not formatted like an IPv6 CIDR range");
704
+ };
705
+ ipaddr2.IPv6.parser = function(string) {
706
+ let addr, i, match, octet, octets, zoneId;
707
+ if (match = string.match(ipv6Regexes.deprecatedTransitional)) {
708
+ return this.parser(`::ffff:${match[1]}`);
709
+ }
710
+ if (ipv6Regexes.native.test(string)) {
711
+ return expandIPv6(string, 8);
712
+ }
713
+ if (match = string.match(ipv6Regexes.transitional)) {
714
+ zoneId = match[6] || "";
715
+ addr = match[1];
716
+ if (!match[1].endsWith("::")) {
717
+ addr = addr.slice(0, -1);
718
+ }
719
+ addr = expandIPv6(addr + zoneId, 6);
720
+ if (addr.parts) {
721
+ octets = [
722
+ parseInt(match[2]),
723
+ parseInt(match[3]),
724
+ parseInt(match[4]),
725
+ parseInt(match[5])
726
+ ];
727
+ for (i = 0; i < octets.length; i++) {
728
+ octet = octets[i];
729
+ if (!(0 <= octet && octet <= 255)) {
730
+ return null;
731
+ }
732
+ }
733
+ addr.parts.push(octets[0] << 8 | octets[1]);
734
+ addr.parts.push(octets[2] << 8 | octets[3]);
735
+ return {
736
+ parts: addr.parts,
737
+ zoneId: addr.zoneId
738
+ };
739
+ }
740
+ }
741
+ return null;
742
+ };
743
+ ipaddr2.IPv6.subnetMaskFromPrefixLength = function(prefix) {
744
+ prefix = parseInt(prefix);
745
+ if (prefix < 0 || prefix > 128) {
746
+ throw new Error("ipaddr: invalid IPv6 prefix length");
747
+ }
748
+ const octets = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
749
+ let j = 0;
750
+ const filledOctetCount = Math.floor(prefix / 8);
751
+ while (j < filledOctetCount) {
752
+ octets[j] = 255;
753
+ j++;
754
+ }
755
+ if (filledOctetCount < 16) {
756
+ octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - prefix % 8;
757
+ }
758
+ return new this(octets);
759
+ };
760
+ ipaddr2.fromByteArray = function(bytes) {
761
+ const length = bytes.length;
762
+ if (length === 4) {
763
+ return new ipaddr2.IPv4(bytes);
764
+ } else if (length === 16) {
765
+ return new ipaddr2.IPv6(bytes);
766
+ } else {
767
+ throw new Error("ipaddr: the binary input is neither an IPv6 nor IPv4 address");
768
+ }
769
+ };
770
+ ipaddr2.isValid = function(string) {
771
+ return ipaddr2.IPv6.isValid(string) || ipaddr2.IPv4.isValid(string);
772
+ };
773
+ ipaddr2.isValidCIDR = function(string) {
774
+ return ipaddr2.IPv6.isValidCIDR(string) || ipaddr2.IPv4.isValidCIDR(string);
775
+ };
776
+ ipaddr2.parse = function(string) {
777
+ if (ipaddr2.IPv6.isValid(string)) {
778
+ return ipaddr2.IPv6.parse(string);
779
+ } else if (ipaddr2.IPv4.isValid(string)) {
780
+ return ipaddr2.IPv4.parse(string);
781
+ } else {
782
+ throw new Error("ipaddr: the address has neither IPv6 nor IPv4 format");
783
+ }
784
+ };
785
+ ipaddr2.parseCIDR = function(string) {
786
+ try {
787
+ return ipaddr2.IPv6.parseCIDR(string);
788
+ } catch (e) {
789
+ try {
790
+ return ipaddr2.IPv4.parseCIDR(string);
791
+ } catch (e2) {
792
+ throw new Error("ipaddr: the address has neither IPv6 nor IPv4 CIDR format");
793
+ }
794
+ }
795
+ };
796
+ ipaddr2.process = function(string) {
797
+ const addr = this.parse(string);
798
+ if (addr.kind() === "ipv6" && addr.isIPv4MappedAddress()) {
799
+ return addr.toIPv4Address();
800
+ } else {
801
+ return addr;
802
+ }
803
+ };
804
+ ipaddr2.subnetMatch = function(address, rangeList, defaultName) {
805
+ let i, rangeName, rangeSubnets, subnet;
806
+ if (defaultName === void 0 || defaultName === null) {
807
+ defaultName = "unicast";
808
+ }
809
+ for (rangeName in rangeList) {
810
+ if (Object.prototype.hasOwnProperty.call(rangeList, rangeName)) {
811
+ rangeSubnets = rangeList[rangeName];
812
+ if (rangeSubnets[0] && !(rangeSubnets[0] instanceof Array)) {
813
+ rangeSubnets = [rangeSubnets];
814
+ }
815
+ for (i = 0; i < rangeSubnets.length; i++) {
816
+ subnet = rangeSubnets[i];
817
+ if (address.kind() === subnet[0].kind() && address.match.apply(address, subnet)) {
818
+ return rangeName;
819
+ }
820
+ }
821
+ }
822
+ }
823
+ return defaultName;
824
+ };
825
+ if (typeof module !== "undefined" && module.exports) {
826
+ module.exports = ipaddr2;
827
+ } else {
828
+ root.ipaddr = ipaddr2;
829
+ }
830
+ })(exports$1);
831
+ }
832
+ });
42
833
  var CHROMIUM_BUNDLE_IDS = /* @__PURE__ */ new Set([
43
834
  "com.google.Chrome",
44
835
  "com.google.Chrome.beta",
@@ -829,15 +1620,16 @@ async function connectBrowser(cdpUrl, authToken) {
829
1620
  const headers = {};
830
1621
  if (authToken) headers["Authorization"] = `Bearer ${authToken}`;
831
1622
  const browser = await playwrightCore.chromium.connectOverCDP(endpoint, { timeout, headers });
832
- const connected = { browser, cdpUrl: normalized, authToken };
833
- cached = connected;
834
- observeBrowser(browser);
835
- browser.on("disconnected", () => {
1623
+ const onDisconnected = () => {
836
1624
  if (cached?.browser === browser) cached = null;
837
1625
  for (const key of roleRefsByTarget.keys()) {
838
1626
  if (key.startsWith(normalized + "::")) roleRefsByTarget.delete(key);
839
1627
  }
840
- });
1628
+ };
1629
+ const connected = { browser, cdpUrl: normalized, authToken, onDisconnected };
1630
+ cached = connected;
1631
+ observeBrowser(browser);
1632
+ browser.on("disconnected", onDisconnected);
841
1633
  return connected;
842
1634
  } catch (err) {
843
1635
  lastErr = err;
@@ -867,6 +1659,14 @@ async function disconnectBrowser() {
867
1659
  if (cur) await cur.browser.close().catch(() => {
868
1660
  });
869
1661
  }
1662
+ function cdpSocketNeedsAttach(wsUrl) {
1663
+ try {
1664
+ const pathname = new URL(wsUrl).pathname;
1665
+ return pathname === "/cdp" || pathname.endsWith("/cdp") || pathname.includes("/devtools/browser/") || pathname === "/";
1666
+ } catch {
1667
+ return false;
1668
+ }
1669
+ }
870
1670
  async function tryTerminateExecutionViaCdp(cdpUrl, targetId) {
871
1671
  const httpBase = normalizeCdpHttpBaseForJsonEndpoints(cdpUrl);
872
1672
  const ctrl = new AbortController();
@@ -883,8 +1683,10 @@ async function tryTerminateExecutionViaCdp(cdpUrl, targetId) {
883
1683
  }
884
1684
  if (!Array.isArray(targets)) return;
885
1685
  const target = targets.find((entry) => String(entry?.id ?? "").trim() === targetId);
886
- const wsUrl = String(target?.webSocketDebuggerUrl ?? "").trim();
887
- if (!wsUrl) return;
1686
+ const wsUrlRaw = String(target?.webSocketDebuggerUrl ?? "").trim();
1687
+ if (!wsUrlRaw) return;
1688
+ const wsUrl = normalizeCdpWsUrl(wsUrlRaw, httpBase);
1689
+ const needsAttach = cdpSocketNeedsAttach(wsUrl);
888
1690
  await new Promise((resolve2) => {
889
1691
  let done = false;
890
1692
  const finish = () => {
@@ -897,8 +1699,9 @@ async function tryTerminateExecutionViaCdp(cdpUrl, targetId) {
897
1699
  }
898
1700
  resolve2();
899
1701
  };
900
- const timer = setTimeout(finish, 2e3);
1702
+ const timer = setTimeout(finish, 3e3);
901
1703
  let ws;
1704
+ let nextId = 1;
902
1705
  try {
903
1706
  ws = new WebSocket(wsUrl);
904
1707
  } catch {
@@ -906,11 +1709,27 @@ async function tryTerminateExecutionViaCdp(cdpUrl, targetId) {
906
1709
  return;
907
1710
  }
908
1711
  ws.onopen = () => {
1712
+ if (needsAttach) {
1713
+ ws.send(JSON.stringify({ id: nextId++, method: "Target.attachToTarget", params: { targetId, flatten: true } }));
1714
+ } else {
1715
+ ws.send(JSON.stringify({ id: nextId++, method: "Runtime.terminateExecution" }));
1716
+ setTimeout(finish, 300);
1717
+ }
1718
+ };
1719
+ ws.onmessage = (event) => {
1720
+ if (!needsAttach) return;
909
1721
  try {
910
- ws.send(JSON.stringify({ id: 1, method: "Runtime.terminateExecution" }));
1722
+ const msg = JSON.parse(String(event.data));
1723
+ if (msg.id && msg.result?.sessionId) {
1724
+ ws.send(JSON.stringify({ id: nextId++, sessionId: msg.result.sessionId, method: "Runtime.terminateExecution" }));
1725
+ try {
1726
+ ws.send(JSON.stringify({ id: nextId++, method: "Target.detachFromTarget", params: { sessionId: msg.result.sessionId } }));
1727
+ } catch {
1728
+ }
1729
+ setTimeout(finish, 300);
1730
+ }
911
1731
  } catch {
912
1732
  }
913
- setTimeout(finish, 300);
914
1733
  };
915
1734
  ws.onerror = () => finish();
916
1735
  ws.onclose = () => finish();
@@ -922,6 +1741,9 @@ async function forceDisconnectPlaywrightForTarget(opts) {
922
1741
  if (!cur || cur.cdpUrl !== normalized) return;
923
1742
  cached = null;
924
1743
  connectingByUrl.delete(normalized);
1744
+ if (cur.onDisconnected && typeof cur.browser.off === "function") {
1745
+ cur.browser.off("disconnected", cur.onDisconnected);
1746
+ }
925
1747
  const targetId = opts.targetId?.trim() || "";
926
1748
  if (targetId) {
927
1749
  await tryTerminateExecutionViaCdp(normalized, targetId).catch(() => {
@@ -1472,6 +2294,9 @@ function formatAriaNodes(nodes, limit) {
1472
2294
  }
1473
2295
  return out;
1474
2296
  }
2297
+
2298
+ // src/security.ts
2299
+ var ipaddr = __toESM(require_ipaddr());
1475
2300
  var InvalidBrowserNavigationUrlError = class extends Error {
1476
2301
  constructor(message) {
1477
2302
  super(message);
@@ -1512,20 +2337,6 @@ function isBlockedHostnameNormalized(normalized) {
1512
2337
  if (BLOCKED_HOSTNAMES.has(normalized)) return true;
1513
2338
  return normalized.endsWith(".localhost") || normalized.endsWith(".local") || normalized.endsWith(".internal");
1514
2339
  }
1515
- function isStrictDecimalOctet(part) {
1516
- if (!/^[0-9]+$/.test(part)) return false;
1517
- const n = parseInt(part, 10);
1518
- if (n < 0 || n > 255) return false;
1519
- if (String(n) !== part) return false;
1520
- return true;
1521
- }
1522
- function isUnsupportedIPv4Literal(ip) {
1523
- if (/^[0-9]+$/.test(ip)) return true;
1524
- const parts = ip.split(".");
1525
- if (parts.length !== 4) return true;
1526
- if (!parts.every(isStrictDecimalOctet)) return true;
1527
- return false;
1528
- }
1529
2340
  var BLOCKED_IPV4_RANGES = /* @__PURE__ */ new Set([
1530
2341
  "unspecified",
1531
2342
  "broadcast",
@@ -1543,7 +2354,98 @@ var BLOCKED_IPV6_RANGES = /* @__PURE__ */ new Set([
1543
2354
  "uniqueLocal",
1544
2355
  "multicast"
1545
2356
  ]);
1546
- var RFC2544_BENCHMARK_PREFIX = [ipaddr__namespace.IPv4.parse("198.18.0.0"), 15];
2357
+ var RFC2544_BENCHMARK_PREFIX = [ipaddr.IPv4.parse("198.18.0.0"), 15];
2358
+ var EMBEDDED_IPV4_SENTINEL_RULES = [
2359
+ // IPv4-compatible (::a.b.c.d)
2360
+ {
2361
+ matches: (parts) => parts[0] === 0 && parts[1] === 0 && parts[2] === 0 && parts[3] === 0 && parts[4] === 0 && parts[5] === 0,
2362
+ toHextets: (parts) => [parts[6], parts[7]]
2363
+ },
2364
+ // NAT64 local-use (64:ff9b:1::/48)
2365
+ {
2366
+ matches: (parts) => parts[0] === 100 && parts[1] === 65435 && parts[2] === 1 && parts[3] === 0 && parts[4] === 0 && parts[5] === 0,
2367
+ toHextets: (parts) => [parts[6], parts[7]]
2368
+ },
2369
+ // 6to4 (2002::/16)
2370
+ {
2371
+ matches: (parts) => parts[0] === 8194,
2372
+ toHextets: (parts) => [parts[1], parts[2]]
2373
+ },
2374
+ // Teredo (2001:0000::/32) — IPv4 XOR'd
2375
+ {
2376
+ matches: (parts) => parts[0] === 8193 && parts[1] === 0,
2377
+ toHextets: (parts) => [parts[6] ^ 65535, parts[7] ^ 65535]
2378
+ },
2379
+ // ISATAP — sentinel in parts[4-5]: 0x0000:0x5efe or 0x0200:0x5efe
2380
+ {
2381
+ matches: (parts) => (parts[4] & 64767) === 0 && parts[5] === 24318,
2382
+ toHextets: (parts) => [parts[6], parts[7]]
2383
+ }
2384
+ ];
2385
+ function stripIpv6Brackets(value) {
2386
+ if (value.startsWith("[") && value.endsWith("]")) return value.slice(1, -1);
2387
+ return value;
2388
+ }
2389
+ function isNumericIpv4LiteralPart(value) {
2390
+ return /^[0-9]+$/.test(value) || /^0x[0-9a-f]+$/i.test(value);
2391
+ }
2392
+ function parseIpv6WithEmbeddedIpv4(raw) {
2393
+ if (!raw.includes(":") || !raw.includes(".")) return;
2394
+ const match = /^(.*:)([^:%]+(?:\.[^:%]+){3})(%[0-9A-Za-z]+)?$/i.exec(raw);
2395
+ if (!match) return;
2396
+ const [, prefix, embeddedIpv4, zoneSuffix = ""] = match;
2397
+ if (!ipaddr.IPv4.isValidFourPartDecimal(embeddedIpv4)) return;
2398
+ const octets = embeddedIpv4.split(".").map((part) => Number.parseInt(part, 10));
2399
+ const normalizedIpv6 = `${prefix}${(octets[0] << 8 | octets[1]).toString(16)}:${(octets[2] << 8 | octets[3]).toString(16)}${zoneSuffix}`;
2400
+ if (!ipaddr.IPv6.isValid(normalizedIpv6)) return;
2401
+ return ipaddr.IPv6.parse(normalizedIpv6);
2402
+ }
2403
+ function normalizeIpParseInput(raw) {
2404
+ const trimmed = raw?.trim();
2405
+ if (!trimmed) return;
2406
+ return stripIpv6Brackets(trimmed);
2407
+ }
2408
+ function parseCanonicalIpAddress(raw) {
2409
+ const normalized = normalizeIpParseInput(raw);
2410
+ if (!normalized) return;
2411
+ if (ipaddr.IPv4.isValid(normalized)) {
2412
+ if (!ipaddr.IPv4.isValidFourPartDecimal(normalized)) return;
2413
+ return ipaddr.IPv4.parse(normalized);
2414
+ }
2415
+ if (ipaddr.IPv6.isValid(normalized)) return ipaddr.IPv6.parse(normalized);
2416
+ return parseIpv6WithEmbeddedIpv4(normalized);
2417
+ }
2418
+ function parseLooseIpAddress(raw) {
2419
+ const normalized = normalizeIpParseInput(raw);
2420
+ if (!normalized) return;
2421
+ if (ipaddr.isValid(normalized)) return ipaddr.parse(normalized);
2422
+ return parseIpv6WithEmbeddedIpv4(normalized);
2423
+ }
2424
+ function isCanonicalDottedDecimalIPv4(raw) {
2425
+ const trimmed = raw?.trim();
2426
+ if (!trimmed) return false;
2427
+ const normalized = stripIpv6Brackets(trimmed);
2428
+ if (!normalized) return false;
2429
+ return ipaddr.IPv4.isValidFourPartDecimal(normalized);
2430
+ }
2431
+ function isLegacyIpv4Literal(raw) {
2432
+ const trimmed = raw?.trim();
2433
+ if (!trimmed) return false;
2434
+ const normalized = stripIpv6Brackets(trimmed);
2435
+ if (!normalized || normalized.includes(":")) return false;
2436
+ if (isCanonicalDottedDecimalIPv4(normalized)) return false;
2437
+ const parts = normalized.split(".");
2438
+ if (parts.length === 0 || parts.length > 4) return false;
2439
+ if (parts.some((part) => part.length === 0)) return false;
2440
+ if (!parts.every((part) => isNumericIpv4LiteralPart(part))) return false;
2441
+ return true;
2442
+ }
2443
+ function looksLikeUnsupportedIpv4Literal(address) {
2444
+ const parts = address.split(".");
2445
+ if (parts.length === 0 || parts.length > 4) return false;
2446
+ if (parts.some((part) => part.length === 0)) return true;
2447
+ return parts.every((part) => /^[0-9]+$/.test(part) || /^0x/i.test(part));
2448
+ }
1547
2449
  function isBlockedSpecialUseIpv4Address(address, opts) {
1548
2450
  const inRfc2544 = address.match(RFC2544_BENCHMARK_PREFIX);
1549
2451
  if (inRfc2544 && opts?.allowRfc2544BenchmarkRange === true) return false;
@@ -1553,89 +2455,50 @@ function isBlockedSpecialUseIpv6Address(address) {
1553
2455
  if (BLOCKED_IPV6_RANGES.has(address.range())) return true;
1554
2456
  return (address.parts[0] & 65472) === 65216;
1555
2457
  }
1556
- function extractEmbeddedIpv4FromIpv6(v6, opts) {
1557
- if (v6.isIPv4MappedAddress()) {
1558
- return isBlockedSpecialUseIpv4Address(v6.toIPv4Address(), opts);
1559
- }
1560
- const parts = v6.parts;
1561
- if (parts[0] === 100 && parts[1] === 65435 && parts[2] === 0 && parts[3] === 0 && parts[4] === 0 && parts[5] === 0) {
1562
- const ip4str = `${parts[6] >> 8 & 255}.${parts[6] & 255}.${parts[7] >> 8 & 255}.${parts[7] & 255}`;
1563
- try {
1564
- return isBlockedSpecialUseIpv4Address(ipaddr__namespace.IPv4.parse(ip4str), opts);
1565
- } catch {
1566
- return true;
1567
- }
1568
- }
1569
- if (parts[0] === 100 && parts[1] === 65435 && parts[2] === 1) {
1570
- const ip4str = `${parts[6] >> 8 & 255}.${parts[6] & 255}.${parts[7] >> 8 & 255}.${parts[7] & 255}`;
1571
- try {
1572
- return isBlockedSpecialUseIpv4Address(ipaddr__namespace.IPv4.parse(ip4str), opts);
1573
- } catch {
1574
- return true;
1575
- }
1576
- }
1577
- if (parts[0] === 8194) {
1578
- const ip4str = `${parts[1] >> 8 & 255}.${parts[1] & 255}.${parts[2] >> 8 & 255}.${parts[2] & 255}`;
1579
- try {
1580
- return isBlockedSpecialUseIpv4Address(ipaddr__namespace.IPv4.parse(ip4str), opts);
1581
- } catch {
1582
- return true;
1583
- }
1584
- }
1585
- if (parts[0] === 8193 && parts[1] === 0) {
1586
- const hiXored = parts[6] ^ 65535;
1587
- const loXored = parts[7] ^ 65535;
1588
- const ip4str = `${hiXored >> 8 & 255}.${hiXored & 255}.${loXored >> 8 & 255}.${loXored & 255}`;
1589
- try {
1590
- return isBlockedSpecialUseIpv4Address(ipaddr__namespace.IPv4.parse(ip4str), opts);
1591
- } catch {
1592
- return true;
1593
- }
1594
- }
1595
- if (parts[0] === 0 && parts[1] === 0 && parts[2] === 0 && parts[3] === 0 && parts[4] === 65535 && parts[5] === 0) {
1596
- const ip4str = `${parts[6] >> 8 & 255}.${parts[6] & 255}.${parts[7] >> 8 & 255}.${parts[7] & 255}`;
1597
- try {
1598
- return isBlockedSpecialUseIpv4Address(ipaddr__namespace.IPv4.parse(ip4str), opts);
1599
- } catch {
1600
- return true;
1601
- }
1602
- }
1603
- if (parts[0] === 0 && parts[1] === 0 && parts[2] === 0 && parts[3] === 0 && parts[4] === 0 && parts[5] === 0) {
1604
- const ip4str = `${parts[6] >> 8 & 255}.${parts[6] & 255}.${parts[7] >> 8 & 255}.${parts[7] & 255}`;
1605
- try {
1606
- return isBlockedSpecialUseIpv4Address(ipaddr__namespace.IPv4.parse(ip4str), opts);
1607
- } catch {
1608
- return true;
1609
- }
1610
- }
1611
- if ((parts[4] & 65023) === 0 && parts[5] === 24318) {
1612
- const ip4str = `${parts[6] >> 8 & 255}.${parts[6] & 255}.${parts[7] >> 8 & 255}.${parts[7] & 255}`;
1613
- try {
1614
- return isBlockedSpecialUseIpv4Address(ipaddr__namespace.IPv4.parse(ip4str), opts);
1615
- } catch {
1616
- return true;
1617
- }
2458
+ function decodeIpv4FromHextets(high, low) {
2459
+ const octets = [
2460
+ high >>> 8 & 255,
2461
+ high & 255,
2462
+ low >>> 8 & 255,
2463
+ low & 255
2464
+ ];
2465
+ return ipaddr.IPv4.parse(octets.join("."));
2466
+ }
2467
+ function extractEmbeddedIpv4FromIpv6(address) {
2468
+ if (address.isIPv4MappedAddress()) return address.toIPv4Address();
2469
+ if (address.range() === "rfc6145") return decodeIpv4FromHextets(address.parts[6], address.parts[7]);
2470
+ if (address.range() === "rfc6052") return decodeIpv4FromHextets(address.parts[6], address.parts[7]);
2471
+ for (const rule of EMBEDDED_IPV4_SENTINEL_RULES) {
2472
+ if (!rule.matches(address.parts)) continue;
2473
+ const [high, low] = rule.toHextets(address.parts);
2474
+ return decodeIpv4FromHextets(high, low);
1618
2475
  }
1619
- return null;
1620
2476
  }
1621
- function isPrivateIpAddress(address, opts) {
2477
+ function resolveIpv4SpecialUseBlockOptions(policy) {
2478
+ return { allowRfc2544BenchmarkRange: policy?.allowRfc2544BenchmarkRange === true };
2479
+ }
2480
+ function isBlockedHostnameOrIp(hostname, policy) {
2481
+ const normalized = normalizeHostname(hostname);
2482
+ if (!normalized) return false;
2483
+ return isBlockedHostnameNormalized(normalized) || isPrivateIpAddress(normalized, policy);
2484
+ }
2485
+ function isPrivateIpAddress(address, policy) {
1622
2486
  let normalized = address.trim().toLowerCase();
1623
2487
  if (normalized.startsWith("[") && normalized.endsWith("]")) normalized = normalized.slice(1, -1);
1624
2488
  if (!normalized) return false;
1625
- try {
1626
- const parsed = ipaddr__namespace.parse(normalized);
1627
- if (parsed.kind() === "ipv4") {
1628
- return isBlockedSpecialUseIpv4Address(parsed, opts);
1629
- }
1630
- const v6 = parsed;
2489
+ const blockOptions = resolveIpv4SpecialUseBlockOptions(policy);
2490
+ const strictIp = parseCanonicalIpAddress(normalized);
2491
+ if (strictIp) {
2492
+ if (strictIp.kind() === "ipv4") return isBlockedSpecialUseIpv4Address(strictIp, blockOptions);
2493
+ const v6 = strictIp;
1631
2494
  if (isBlockedSpecialUseIpv6Address(v6)) return true;
1632
- const embeddedV4 = extractEmbeddedIpv4FromIpv6(v6, opts);
1633
- if (embeddedV4 !== null) return embeddedV4;
2495
+ const embeddedIpv4 = extractEmbeddedIpv4FromIpv6(v6);
2496
+ if (embeddedIpv4) return isBlockedSpecialUseIpv4Address(embeddedIpv4, blockOptions);
1634
2497
  return false;
1635
- } catch {
1636
2498
  }
1637
- if (!normalized.includes(":") && isUnsupportedIPv4Literal(normalized)) return true;
1638
- if (normalized.includes(":")) return true;
2499
+ if (normalized.includes(":") && !parseLooseIpAddress(normalized)) return true;
2500
+ if (!isCanonicalDottedDecimalIPv4(normalized) && isLegacyIpv4Literal(normalized)) return true;
2501
+ if (looksLikeUnsupportedIpv4Literal(normalized)) return true;
1639
2502
  return false;
1640
2503
  }
1641
2504
  function normalizeHostnameSet(values) {
@@ -1717,13 +2580,7 @@ async function resolvePinnedHostnameWithPolicy(hostname, params = {}) {
1717
2580
  );
1718
2581
  }
1719
2582
  if (!skipPrivateNetworkChecks) {
1720
- if (isBlockedHostnameNormalized(normalized)) {
1721
- throw new InvalidBrowserNavigationUrlError(
1722
- `Navigation to internal/loopback address blocked: "${hostname}". ssrfPolicy.dangerouslyAllowPrivateNetwork is false (strict mode).`
1723
- );
1724
- }
1725
- const ipOpts = { allowRfc2544BenchmarkRange: params.policy?.allowRfc2544BenchmarkRange };
1726
- if (isPrivateIpAddress(normalized, ipOpts)) {
2583
+ if (isBlockedHostnameOrIp(normalized, params.policy)) {
1727
2584
  throw new InvalidBrowserNavigationUrlError(
1728
2585
  `Navigation to internal/loopback address blocked: "${hostname}". ssrfPolicy.dangerouslyAllowPrivateNetwork is false (strict mode).`
1729
2586
  );
@@ -1744,9 +2601,8 @@ async function resolvePinnedHostnameWithPolicy(hostname, params = {}) {
1744
2601
  );
1745
2602
  }
1746
2603
  if (!skipPrivateNetworkChecks) {
1747
- const ipOpts = { allowRfc2544BenchmarkRange: params.policy?.allowRfc2544BenchmarkRange };
1748
2604
  for (const r of results) {
1749
- if (isPrivateIpAddress(r.address, ipOpts)) {
2605
+ if (isBlockedHostnameOrIp(r.address, params.policy)) {
1750
2606
  throw new InvalidBrowserNavigationUrlError(
1751
2607
  `Navigation to internal/loopback address blocked: "${hostname}" resolves to "${r.address}". ssrfPolicy.dangerouslyAllowPrivateNetwork is false (strict mode).`
1752
2608
  );
@@ -1767,6 +2623,7 @@ async function resolvePinnedHostnameWithPolicy(hostname, params = {}) {
1767
2623
  }
1768
2624
  async function assertBrowserNavigationAllowed(opts) {
1769
2625
  const rawUrl = String(opts.url ?? "").trim();
2626
+ if (!rawUrl) throw new InvalidBrowserNavigationUrlError("url is required");
1770
2627
  let parsed;
1771
2628
  try {
1772
2629
  parsed = new URL(rawUrl);
@@ -1923,12 +2780,30 @@ function resolveBoundedDelayMs(value, label, maxMs) {
1923
2780
  if (normalized > maxMs) throw new Error(`${label} exceeds maximum of ${maxMs}ms`);
1924
2781
  return normalized;
1925
2782
  }
1926
- async function clickViaPlaywright(opts) {
2783
+ function resolveInteractionTimeoutMs(timeoutMs) {
2784
+ return Math.max(500, Math.min(6e4, Math.floor(timeoutMs ?? 8e3)));
2785
+ }
2786
+ function requireRefOrSelector(ref, selector) {
2787
+ const trimmedRef = typeof ref === "string" ? ref.trim() : "";
2788
+ const trimmedSelector = typeof selector === "string" ? selector.trim() : "";
2789
+ if (!trimmedRef && !trimmedSelector) throw new Error("ref or selector is required");
2790
+ return { ref: trimmedRef || void 0, selector: trimmedSelector || void 0 };
2791
+ }
2792
+ function resolveLocator(page, resolved) {
2793
+ return resolved.ref ? refLocator(page, resolved.ref) : page.locator(resolved.selector);
2794
+ }
2795
+ async function getRestoredPageForTarget(opts) {
1927
2796
  const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
1928
2797
  ensurePageState(page);
1929
2798
  restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
1930
- const locator = refLocator(page, opts.ref);
1931
- const timeout = normalizeTimeoutMs(opts.timeoutMs, 8e3, 6e4);
2799
+ return page;
2800
+ }
2801
+ async function clickViaPlaywright(opts) {
2802
+ const resolved = requireRefOrSelector(opts.ref, opts.selector);
2803
+ const page = await getRestoredPageForTarget(opts);
2804
+ const label = resolved.ref ?? resolved.selector;
2805
+ const locator = resolveLocator(page, resolved);
2806
+ const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
1932
2807
  try {
1933
2808
  const delayMs = resolveBoundedDelayMs(opts.delayMs, "click delayMs", MAX_CLICK_DELAY_MS);
1934
2809
  if (delayMs > 0) {
@@ -1941,28 +2816,27 @@ async function clickViaPlaywright(opts) {
1941
2816
  await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers });
1942
2817
  }
1943
2818
  } catch (err) {
1944
- throw toAIFriendlyError(err, opts.ref);
2819
+ throw toAIFriendlyError(err, label);
1945
2820
  }
1946
2821
  }
1947
2822
  async function hoverViaPlaywright(opts) {
1948
- const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
1949
- ensurePageState(page);
1950
- restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
2823
+ const resolved = requireRefOrSelector(opts.ref, opts.selector);
2824
+ const page = await getRestoredPageForTarget(opts);
2825
+ const label = resolved.ref ?? resolved.selector;
2826
+ const locator = resolveLocator(page, resolved);
1951
2827
  try {
1952
- await refLocator(page, opts.ref).hover({
1953
- timeout: normalizeTimeoutMs(opts.timeoutMs, 8e3, 6e4)
1954
- });
2828
+ await locator.hover({ timeout: resolveInteractionTimeoutMs(opts.timeoutMs) });
1955
2829
  } catch (err) {
1956
- throw toAIFriendlyError(err, opts.ref);
2830
+ throw toAIFriendlyError(err, label);
1957
2831
  }
1958
2832
  }
1959
2833
  async function typeViaPlaywright(opts) {
2834
+ const resolved = requireRefOrSelector(opts.ref, opts.selector);
1960
2835
  const text = String(opts.text ?? "");
1961
- const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
1962
- ensurePageState(page);
1963
- restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
1964
- const locator = refLocator(page, opts.ref);
1965
- const timeout = normalizeTimeoutMs(opts.timeoutMs, 8e3, 6e4);
2836
+ const page = await getRestoredPageForTarget(opts);
2837
+ const label = resolved.ref ?? resolved.selector;
2838
+ const locator = resolveLocator(page, resolved);
2839
+ const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
1966
2840
  try {
1967
2841
  if (opts.slowly) {
1968
2842
  await locator.click({ timeout });
@@ -1972,39 +2846,38 @@ async function typeViaPlaywright(opts) {
1972
2846
  }
1973
2847
  if (opts.submit) await locator.press("Enter", { timeout });
1974
2848
  } catch (err) {
1975
- throw toAIFriendlyError(err, opts.ref);
2849
+ throw toAIFriendlyError(err, label);
1976
2850
  }
1977
2851
  }
1978
2852
  async function selectOptionViaPlaywright(opts) {
2853
+ const resolved = requireRefOrSelector(opts.ref, opts.selector);
1979
2854
  if (!opts.values?.length) throw new Error("values are required");
1980
- const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
1981
- ensurePageState(page);
1982
- restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
2855
+ const page = await getRestoredPageForTarget(opts);
2856
+ const label = resolved.ref ?? resolved.selector;
2857
+ const locator = resolveLocator(page, resolved);
1983
2858
  try {
1984
- await refLocator(page, opts.ref).selectOption(opts.values, {
1985
- timeout: normalizeTimeoutMs(opts.timeoutMs, 8e3, 6e4)
1986
- });
2859
+ await locator.selectOption(opts.values, { timeout: resolveInteractionTimeoutMs(opts.timeoutMs) });
1987
2860
  } catch (err) {
1988
- throw toAIFriendlyError(err, opts.ref);
2861
+ throw toAIFriendlyError(err, label);
1989
2862
  }
1990
2863
  }
1991
2864
  async function dragViaPlaywright(opts) {
1992
- const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
1993
- ensurePageState(page);
1994
- restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
2865
+ const resolvedStart = requireRefOrSelector(opts.startRef, opts.startSelector);
2866
+ const resolvedEnd = requireRefOrSelector(opts.endRef, opts.endSelector);
2867
+ const page = await getRestoredPageForTarget(opts);
2868
+ const startLocator = resolveLocator(page, resolvedStart);
2869
+ const endLocator = resolveLocator(page, resolvedEnd);
2870
+ const startLabel = resolvedStart.ref ?? resolvedStart.selector;
2871
+ const endLabel = resolvedEnd.ref ?? resolvedEnd.selector;
1995
2872
  try {
1996
- await refLocator(page, opts.startRef).dragTo(refLocator(page, opts.endRef), {
1997
- timeout: normalizeTimeoutMs(opts.timeoutMs, 8e3, 6e4)
1998
- });
2873
+ await startLocator.dragTo(endLocator, { timeout: resolveInteractionTimeoutMs(opts.timeoutMs) });
1999
2874
  } catch (err) {
2000
- throw toAIFriendlyError(err, `${opts.startRef} -> ${opts.endRef}`);
2875
+ throw toAIFriendlyError(err, `${startLabel} -> ${endLabel}`);
2001
2876
  }
2002
2877
  }
2003
2878
  async function fillFormViaPlaywright(opts) {
2004
- const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
2005
- ensurePageState(page);
2006
- restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
2007
- const timeout = normalizeTimeoutMs(opts.timeoutMs, 8e3, 6e4);
2879
+ const page = await getRestoredPageForTarget(opts);
2880
+ const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
2008
2881
  for (const field of opts.fields) {
2009
2882
  const ref = field.ref.trim();
2010
2883
  const type = (typeof field.type === "string" ? field.type.trim() : "") || "text";
@@ -2029,21 +2902,18 @@ async function fillFormViaPlaywright(opts) {
2029
2902
  }
2030
2903
  }
2031
2904
  async function scrollIntoViewViaPlaywright(opts) {
2032
- const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
2033
- ensurePageState(page);
2034
- restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
2905
+ const resolved = requireRefOrSelector(opts.ref, opts.selector);
2906
+ const page = await getRestoredPageForTarget(opts);
2907
+ const label = resolved.ref ?? resolved.selector;
2908
+ const locator = resolveLocator(page, resolved);
2035
2909
  try {
2036
- await refLocator(page, opts.ref).scrollIntoViewIfNeeded({
2037
- timeout: normalizeTimeoutMs(opts.timeoutMs, 2e4)
2038
- });
2910
+ await locator.scrollIntoViewIfNeeded({ timeout: normalizeTimeoutMs(opts.timeoutMs, 2e4) });
2039
2911
  } catch (err) {
2040
- throw toAIFriendlyError(err, opts.ref);
2912
+ throw toAIFriendlyError(err, label);
2041
2913
  }
2042
2914
  }
2043
2915
  async function highlightViaPlaywright(opts) {
2044
- const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
2045
- ensurePageState(page);
2046
- restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
2916
+ const page = await getRestoredPageForTarget(opts);
2047
2917
  try {
2048
2918
  await refLocator(page, opts.ref).highlight();
2049
2919
  } catch (err) {
@@ -2051,9 +2921,7 @@ async function highlightViaPlaywright(opts) {
2051
2921
  }
2052
2922
  }
2053
2923
  async function setInputFilesViaPlaywright(opts) {
2054
- const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
2055
- ensurePageState(page);
2056
- restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
2924
+ const page = await getRestoredPageForTarget(opts);
2057
2925
  if (!opts.paths.length) throw new Error("paths are required");
2058
2926
  const inputRef = typeof opts.ref === "string" ? opts.ref.trim() : "";
2059
2927
  const element = typeof opts.element === "string" ? opts.element.trim() : "";
@@ -2246,13 +3114,18 @@ async function resizeViewportViaPlaywright(opts) {
2246
3114
 
2247
3115
  // src/actions/wait.ts
2248
3116
  var MAX_WAIT_TIME_MS = 3e4;
3117
+ function resolveBoundedDelayMs2(value, label, maxMs) {
3118
+ const normalized = Math.floor(value ?? 0);
3119
+ if (!Number.isFinite(normalized) || normalized < 0) throw new Error(`${label} must be >= 0`);
3120
+ if (normalized > maxMs) throw new Error(`${label} exceeds maximum of ${maxMs}ms`);
3121
+ return normalized;
3122
+ }
2249
3123
  async function waitForViaPlaywright(opts) {
2250
3124
  const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
2251
3125
  ensurePageState(page);
2252
3126
  const timeout = normalizeTimeoutMs(opts.timeoutMs, 2e4);
2253
3127
  if (typeof opts.timeMs === "number" && Number.isFinite(opts.timeMs)) {
2254
- const bounded = Math.max(0, Math.min(MAX_WAIT_TIME_MS, Math.floor(opts.timeMs)));
2255
- await page.waitForTimeout(bounded);
3128
+ await page.waitForTimeout(resolveBoundedDelayMs2(opts.timeMs, "wait timeMs", MAX_WAIT_TIME_MS));
2256
3129
  }
2257
3130
  if (opts.text) {
2258
3131
  await page.getByText(opts.text).first().waitFor({ state: "visible", timeout });
@@ -2414,55 +3287,106 @@ async function evaluateViaPlaywright(opts) {
2414
3287
  if (signal && abortListener) signal.removeEventListener("abort", abortListener);
2415
3288
  }
2416
3289
  }
3290
+ function createPageDownloadWaiter(page, timeoutMs) {
3291
+ let done = false;
3292
+ let timer;
3293
+ let handler;
3294
+ const cleanup = () => {
3295
+ if (timer) clearTimeout(timer);
3296
+ timer = void 0;
3297
+ if (handler) {
3298
+ page.off("download", handler);
3299
+ handler = void 0;
3300
+ }
3301
+ };
3302
+ return {
3303
+ promise: new Promise((resolve2, reject) => {
3304
+ handler = (download) => {
3305
+ if (done) return;
3306
+ done = true;
3307
+ cleanup();
3308
+ resolve2(download);
3309
+ };
3310
+ page.on("download", handler);
3311
+ timer = setTimeout(() => {
3312
+ if (done) return;
3313
+ done = true;
3314
+ cleanup();
3315
+ reject(new Error("Timeout waiting for download"));
3316
+ }, timeoutMs);
3317
+ }),
3318
+ cancel: () => {
3319
+ if (done) return;
3320
+ done = true;
3321
+ cleanup();
3322
+ }
3323
+ };
3324
+ }
3325
+ async function saveDownloadPayload(download, outPath) {
3326
+ await writeViaSiblingTempPath({
3327
+ rootDir: path.dirname(outPath),
3328
+ targetPath: outPath,
3329
+ writeTemp: async (tempPath) => {
3330
+ await download.saveAs(tempPath);
3331
+ }
3332
+ });
3333
+ return {
3334
+ url: download.url(),
3335
+ suggestedFilename: download.suggestedFilename(),
3336
+ path: outPath
3337
+ };
3338
+ }
3339
+ async function awaitDownloadPayload(params) {
3340
+ try {
3341
+ const download = await params.waiter.promise;
3342
+ if (params.state.armIdDownload !== params.armId) throw new Error("Download was superseded by another waiter");
3343
+ return await saveDownloadPayload(download, params.outPath);
3344
+ } catch (err) {
3345
+ params.waiter.cancel();
3346
+ throw err;
3347
+ }
3348
+ }
2417
3349
  async function downloadViaPlaywright(opts) {
2418
3350
  await assertSafeOutputPath(opts.path, opts.allowedOutputRoots);
2419
3351
  const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
2420
3352
  const state = ensurePageState(page);
2421
3353
  restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
2422
3354
  const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
2423
- const locator = refLocator(page, opts.ref);
3355
+ const outPath = String(opts.path ?? "").trim();
3356
+ if (!outPath) throw new Error("path is required");
2424
3357
  state.armIdDownload = bumpDownloadArmId();
3358
+ const armId = state.armIdDownload;
3359
+ const waiter = createPageDownloadWaiter(page, timeout);
2425
3360
  try {
2426
- const [download] = await Promise.all([
2427
- page.waitForEvent("download", { timeout }),
2428
- locator.click({ timeout })
2429
- ]);
2430
- const outPath = opts.path;
2431
- await writeViaSiblingTempPath({
2432
- rootDir: path.dirname(outPath),
2433
- targetPath: outPath,
2434
- writeTemp: async (tempPath) => {
2435
- await download.saveAs(tempPath);
2436
- }
2437
- });
2438
- return {
2439
- url: download.url(),
2440
- suggestedFilename: download.suggestedFilename(),
2441
- path: outPath
2442
- };
3361
+ const locator = refLocator(page, opts.ref);
3362
+ try {
3363
+ await locator.click({ timeout });
3364
+ } catch (err) {
3365
+ throw toAIFriendlyError(err, opts.ref);
3366
+ }
3367
+ return await awaitDownloadPayload({ waiter, state, armId, outPath });
2443
3368
  } catch (err) {
2444
- throw toAIFriendlyError(err, opts.ref);
3369
+ waiter.cancel();
3370
+ throw err;
2445
3371
  }
2446
3372
  }
2447
3373
  async function waitForDownloadViaPlaywright(opts) {
2448
3374
  const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
2449
- ensurePageState(page);
3375
+ const state = ensurePageState(page);
2450
3376
  const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
2451
- const download = await page.waitForEvent("download", { timeout });
2452
- const savePath = opts.path ?? download.suggestedFilename();
2453
- await assertSafeOutputPath(savePath, opts.allowedOutputRoots);
2454
- await writeViaSiblingTempPath({
2455
- rootDir: path.dirname(savePath),
2456
- targetPath: savePath,
2457
- writeTemp: async (tempPath) => {
2458
- await download.saveAs(tempPath);
2459
- }
2460
- });
2461
- return {
2462
- url: download.url(),
2463
- suggestedFilename: download.suggestedFilename(),
2464
- path: savePath
2465
- };
3377
+ state.armIdDownload = bumpDownloadArmId();
3378
+ const armId = state.armIdDownload;
3379
+ const waiter = createPageDownloadWaiter(page, timeout);
3380
+ try {
3381
+ const download = await waiter.promise;
3382
+ if (state.armIdDownload !== armId) throw new Error("Download was superseded by another waiter");
3383
+ const savePath = opts.path ?? download.suggestedFilename();
3384
+ await assertSafeOutputPath(savePath, opts.allowedOutputRoots);
3385
+ return await saveDownloadPayload(download, savePath);
3386
+ } catch (err) {
3387
+ waiter.cancel();
3388
+ throw err;
3389
+ }
2466
3390
  }
2467
3391
  async function emulateMediaViaPlaywright(opts) {
2468
3392
  const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
@@ -2577,7 +3501,7 @@ async function setLocaleViaPlaywright(opts) {
2577
3501
  async function setOfflineViaPlaywright(opts) {
2578
3502
  const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
2579
3503
  ensurePageState(page);
2580
- await page.context().setOffline(opts.offline);
3504
+ await page.context().setOffline(Boolean(opts.offline));
2581
3505
  }
2582
3506
  async function setTimezoneViaPlaywright(opts) {
2583
3507
  const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });