@crouton-kit/capture 1.4.0 → 1.4.1

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/bin/capture CHANGED
@@ -2232,7 +2232,7 @@ var require_websocket = __commonJS({
2232
2232
  "use strict";
2233
2233
  var EventEmitter = require("events");
2234
2234
  var https = require("https");
2235
- var http = require("http");
2235
+ var http2 = require("http");
2236
2236
  var net5 = require("net");
2237
2237
  var tls = require("tls");
2238
2238
  var { randomBytes: randomBytes10, createHash: createHash5 } = require("crypto");
@@ -2766,7 +2766,7 @@ var require_websocket = __commonJS({
2766
2766
  }
2767
2767
  const defaultPort = isSecure ? 443 : 80;
2768
2768
  const key = randomBytes10(16).toString("base64");
2769
- const request = isSecure ? https.request : http.request;
2769
+ const request = isSecure ? https.request : http2.request;
2770
2770
  const protocolSet = /* @__PURE__ */ new Set();
2771
2771
  let perMessageDeflate;
2772
2772
  opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
@@ -3260,7 +3260,7 @@ var require_websocket_server = __commonJS({
3260
3260
  "node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket-server.js"(exports2, module2) {
3261
3261
  "use strict";
3262
3262
  var EventEmitter = require("events");
3263
- var http = require("http");
3263
+ var http2 = require("http");
3264
3264
  var { Duplex } = require("stream");
3265
3265
  var { createHash: createHash5 } = require("crypto");
3266
3266
  var extension2 = require_extension();
@@ -3335,8 +3335,8 @@ var require_websocket_server = __commonJS({
3335
3335
  );
3336
3336
  }
3337
3337
  if (options.port != null) {
3338
- this._server = http.createServer((req, res) => {
3339
- const body = http.STATUS_CODES[426];
3338
+ this._server = http2.createServer((req, res) => {
3339
+ const body = http2.STATUS_CODES[426];
3340
3340
  res.writeHead(426, {
3341
3341
  "Content-Length": body.length,
3342
3342
  "Content-Type": "text/plain"
@@ -3623,7 +3623,7 @@ var require_websocket_server = __commonJS({
3623
3623
  this.destroy();
3624
3624
  }
3625
3625
  function abortHandshake(socket, code, message, headers2) {
3626
- message = message || http.STATUS_CODES[code];
3626
+ message = message || http2.STATUS_CODES[code];
3627
3627
  headers2 = {
3628
3628
  Connection: "close",
3629
3629
  "Content-Type": "text/html",
@@ -3632,7 +3632,7 @@ var require_websocket_server = __commonJS({
3632
3632
  };
3633
3633
  socket.once("finish", socket.destroy);
3634
3634
  socket.end(
3635
- `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r
3635
+ `HTTP/1.1 ${code} ${http2.STATUS_CODES[code]}\r
3636
3636
  ` + Object.keys(headers2).map((h) => `${h}: ${headers2[h]}`).join("\r\n") + "\r\n\r\n" + message
3637
3637
  );
3638
3638
  }
@@ -3779,6 +3779,39 @@ __export(detect_exports, {
3779
3779
  identifyBrowserBundleId: () => identifyBrowserBundleId,
3780
3780
  pickPreferredEndpoint: () => pickPreferredEndpoint
3781
3781
  });
3782
+ function httpGetJson(port, path33, timeoutMs) {
3783
+ return new Promise((resolve12) => {
3784
+ const req = http.get(
3785
+ { host: "localhost", port, path: path33, timeout: timeoutMs },
3786
+ (res) => {
3787
+ const status = res.statusCode ?? 0;
3788
+ if (status < 200 || status >= 300) {
3789
+ res.resume();
3790
+ resolve12(null);
3791
+ return;
3792
+ }
3793
+ let body = "";
3794
+ res.setEncoding("utf-8");
3795
+ res.on("data", (chunk) => {
3796
+ body += chunk;
3797
+ });
3798
+ res.on("end", () => {
3799
+ try {
3800
+ resolve12(JSON.parse(body));
3801
+ } catch {
3802
+ resolve12(null);
3803
+ }
3804
+ });
3805
+ res.on("error", () => resolve12(null));
3806
+ }
3807
+ );
3808
+ req.on("timeout", () => {
3809
+ req.destroy();
3810
+ resolve12(null);
3811
+ });
3812
+ req.on("error", () => resolve12(null));
3813
+ });
3814
+ }
3782
3815
  function getDefaultBrowserId() {
3783
3816
  try {
3784
3817
  const output = (0, import_child_process.execSync)(
@@ -3795,14 +3828,8 @@ function getDefaultBrowserId() {
3795
3828
  }
3796
3829
  async function probeCdpPort(port) {
3797
3830
  try {
3798
- const controller = new AbortController();
3799
- const timeout = setTimeout(() => controller.abort(), 500);
3800
- const resp = await fetch(`http://localhost:${port}/json/version`, {
3801
- signal: controller.signal
3802
- });
3803
- clearTimeout(timeout);
3804
- if (resp.ok) {
3805
- const version = await resp.json();
3831
+ const version = await httpGetJson(port, "/json/version", 500);
3832
+ if (version) {
3806
3833
  const browser = version.Browser;
3807
3834
  const userAgent = version["User-Agent"] ?? "";
3808
3835
  if (!browser) return null;
@@ -3824,14 +3851,7 @@ async function probeCdpPort(port) {
3824
3851
  }
3825
3852
  async function probeHasPageTarget(port) {
3826
3853
  try {
3827
- const controller = new AbortController();
3828
- const timeout = setTimeout(() => controller.abort(), 500);
3829
- const resp = await fetch(`http://localhost:${port}/json/list`, {
3830
- signal: controller.signal
3831
- });
3832
- clearTimeout(timeout);
3833
- if (!resp.ok) return false;
3834
- const list2 = await resp.json();
3854
+ const list2 = await httpGetJson(port, "/json/list", 500);
3835
3855
  return Array.isArray(list2) && list2.some((t) => t.type === "page");
3836
3856
  } catch {
3837
3857
  return false;
@@ -3926,10 +3946,11 @@ async function detectCdpPort() {
3926
3946
  }
3927
3947
  return pickPreferredEndpoint(endpoints, getDefaultBrowserId()).port;
3928
3948
  }
3929
- var import_child_process, BROWSER_PATTERNS;
3949
+ var import_child_process, http, BROWSER_PATTERNS;
3930
3950
  var init_detect = __esm({
3931
3951
  "src/cdp/detect.ts"() {
3932
3952
  import_child_process = require("child_process");
3953
+ http = __toESM(require("http"));
3933
3954
  BROWSER_PATTERNS = {
3934
3955
  "company.thebrowser.browser": ["arc"],
3935
3956
  "com.google.chrome": ["chrome", "google chrome"],
@@ -10295,7 +10316,7 @@ function validateHarAppendBatch(value, source) {
10295
10316
  return { entries, incompleteLifecycles };
10296
10317
  }
10297
10318
  function captureVersion() {
10298
- const version = "1.4.0";
10319
+ const version = "1.4.1";
10299
10320
  return typeof version === "string" && version.length > 0 ? version : "development";
10300
10321
  }
10301
10322
  function token() {
@@ -12086,7 +12107,7 @@ function wallTime(value) {
12086
12107
  }
12087
12108
  }
12088
12109
  function captureVersion2() {
12089
- const version = "1.4.0";
12110
+ const version = "1.4.1";
12090
12111
  return typeof version === "string" && version.length > 0 ? version : "development";
12091
12112
  }
12092
12113
  function deepFreeze(value) {
@@ -64688,8 +64709,8 @@ ${lanes.join("\n")}
64688
64709
  }
64689
64710
  function createPerModuleNameCache() {
64690
64711
  const directoryPathMap = /* @__PURE__ */ new Map();
64691
- return { get, set };
64692
- function get(directory) {
64712
+ return { get: get2, set };
64713
+ function get2(directory) {
64693
64714
  return directoryPathMap.get(toPath(directory, currentDirectory, getCanonicalFileName));
64694
64715
  }
64695
64716
  function set(directory, result) {
@@ -168021,12 +168042,12 @@ ${lanes.join("\n")}
168021
168042
  ...info,
168022
168043
  parseable: !!content,
168023
168044
  fileName,
168024
- get,
168045
+ get: get2,
168025
168046
  has(dependencyName, inGroups) {
168026
- return !!get(dependencyName, inGroups);
168047
+ return !!get2(dependencyName, inGroups);
168027
168048
  }
168028
168049
  };
168029
- function get(dependencyName, inGroups = 15) {
168050
+ function get2(dependencyName, inGroups = 15) {
168030
168051
  for (const [group2, deps7] of dependencyGroups) {
168031
168052
  if (deps7 && inGroups & group2) {
168032
168053
  const dep = deps7.get(dependencyName);
@@ -243312,7 +243333,7 @@ precedence: explicit flag > active session > env. \`session start\` adopts only
243312
243333
  explicit --target.`;
243313
243334
  }
243314
243335
  function printVersion() {
243315
- const declared = "1.4.0";
243336
+ const declared = "1.4.1";
243316
243337
  if (declared) {
243317
243338
  console.log(declared);
243318
243339
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crouton-kit/capture",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Browser automation and UI validation via CDP — session-based screenshots, HAR, a11y, click/type, and JS exec",
5
5
  "bin": {
6
6
  "capture": "./bin/capture"
package/src/cdp/detect.ts CHANGED
@@ -1,4 +1,56 @@
1
1
  import { execSync } from 'child_process';
2
+ import * as http from 'http';
3
+
4
+ /**
5
+ * A CDP-endpoint JSON probe that CANNOT leak a socket handle. Node's built-in
6
+ * `fetch` (undici) leaves a dangling socket + request handle when a
7
+ * connect-phase abort fires against a port that accepts TCP but never sends an
8
+ * HTTP response (e.g. a docker-proxied or half-open listener). That handle
9
+ * keeps this short-lived CLI process alive for undici's ~7s internal socket
10
+ * timeout AFTER all real work is done, so a single dead port on the machine
11
+ * adds ~7s to every command that runs port detection. A raw `http.get` whose
12
+ * request we explicitly `destroy()` on timeout releases the handle immediately.
13
+ *
14
+ * Resolves to the parsed JSON body on a 2xx response, or `null` on any
15
+ * timeout, connection error, non-2xx status, or unparseable body.
16
+ */
17
+ function httpGetJson(
18
+ port: number,
19
+ path: string,
20
+ timeoutMs: number,
21
+ ): Promise<unknown | null> {
22
+ return new Promise((resolve) => {
23
+ const req = http.get(
24
+ { host: 'localhost', port, path, timeout: timeoutMs },
25
+ (res) => {
26
+ const status = res.statusCode ?? 0;
27
+ if (status < 200 || status >= 300) {
28
+ res.resume();
29
+ resolve(null);
30
+ return;
31
+ }
32
+ let body = '';
33
+ res.setEncoding('utf-8');
34
+ res.on('data', (chunk) => {
35
+ body += chunk;
36
+ });
37
+ res.on('end', () => {
38
+ try {
39
+ resolve(JSON.parse(body));
40
+ } catch {
41
+ resolve(null);
42
+ }
43
+ });
44
+ res.on('error', () => resolve(null));
45
+ },
46
+ );
47
+ req.on('timeout', () => {
48
+ req.destroy();
49
+ resolve(null);
50
+ });
51
+ req.on('error', () => resolve(null));
52
+ });
53
+ }
2
54
 
3
55
  export const BROWSER_PATTERNS: Record<string, string[]> = {
4
56
  'company.thebrowser.browser': ['arc'],
@@ -40,18 +92,12 @@ interface CdpProbeResult {
40
92
  // being confused for the intended browser.
41
93
  async function probeCdpPort(port: number): Promise<CdpProbeResult | null> {
42
94
  try {
43
- const controller = new AbortController();
44
- const timeout = setTimeout(() => controller.abort(), 500);
45
- const resp = await fetch(`http://localhost:${port}/json/version`, {
46
- signal: controller.signal,
47
- });
48
- clearTimeout(timeout);
49
- if (resp.ok) {
50
- const version = (await resp.json()) as {
51
- Browser?: string;
52
- 'User-Agent'?: string;
53
- webSocketDebuggerUrl?: string;
54
- };
95
+ const version = (await httpGetJson(port, '/json/version', 500)) as {
96
+ Browser?: string;
97
+ 'User-Agent'?: string;
98
+ webSocketDebuggerUrl?: string;
99
+ } | null;
100
+ if (version) {
55
101
  const browser = version.Browser;
56
102
  const userAgent = version['User-Agent'] ?? '';
57
103
  if (!browser) return null;
@@ -83,14 +129,9 @@ async function probeCdpPort(port: number): Promise<CdpProbeResult | null> {
83
129
 
84
130
  async function probeHasPageTarget(port: number): Promise<boolean> {
85
131
  try {
86
- const controller = new AbortController();
87
- const timeout = setTimeout(() => controller.abort(), 500);
88
- const resp = await fetch(`http://localhost:${port}/json/list`, {
89
- signal: controller.signal,
90
- });
91
- clearTimeout(timeout);
92
- if (!resp.ok) return false;
93
- const list = (await resp.json()) as Array<{ type?: string }>;
132
+ const list = (await httpGetJson(port, '/json/list', 500)) as Array<{
133
+ type?: string;
134
+ }> | null;
94
135
  return Array.isArray(list) && list.some((t) => t.type === 'page');
95
136
  } catch {
96
137
  return false;