@ultimat3/scraping 31.4.0 → 31.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/scraping",
3
- "version": "31.4.0",
3
+ "version": "31.4.2",
4
4
  "description": "Browser automation as a job: scrape() returns a JobHandle",
5
5
  "keywords": [
6
6
  "ultimate",
@@ -43,10 +43,10 @@
43
43
  "test": "bun test"
44
44
  },
45
45
  "dependencies": {
46
- "@ultimat3/action": "31.4.0",
47
- "@ultimat3/core": "31.4.0",
48
- "@ultimat3/jobs": "31.4.0",
49
- "@ultimat3/schema": "31.4.0",
50
- "@ultimat3/storage": "31.4.0"
46
+ "@ultimat3/action": "31.4.2",
47
+ "@ultimat3/core": "31.4.2",
48
+ "@ultimat3/jobs": "31.4.2",
49
+ "@ultimat3/schema": "31.4.2",
50
+ "@ultimat3/storage": "31.4.2"
51
51
  }
52
52
  }
@@ -96,6 +96,12 @@ export interface CdpConnectionOptions {
96
96
  readonly endpoint: string;
97
97
  /** Per-call deadline. A CDP call that never answers is a suite that never finishes. */
98
98
  readonly timeoutMs: number;
99
+ /**
100
+ * The handshake's deadline, when it is not a call's: a launcher dials a browser that is still
101
+ * starting, inside what is left of its LAUNCH deadline (`cdp-launch-wire.ts`). `timeoutMs` if
102
+ * unset.
103
+ */
104
+ readonly connectTimeoutMs?: number | undefined;
99
105
  }
100
106
 
101
107
  /**
@@ -120,14 +126,15 @@ export async function cdpConnect(options: CdpConnectionOptions): Promise<CdpConn
120
126
  `the CDP endpoint is ${options.endpoint === '' ? 'empty' : 'not a WebSocket url'}`,
121
127
  'pass the `webSocketDebuggerUrl` Chrome prints on stderr, or the one /json/version answers',
122
128
  );
129
+ const connectTimeoutMs = options.connectTimeoutMs ?? options.timeoutMs;
123
130
  const socket = new WebSocket(options.endpoint);
124
131
  await new Promise<void>((resolve, reject) => {
125
132
  // The socket is CLOSED on both failures: a handshake that timed out otherwise kept dialling
126
133
  // in the background, holding a descriptor and a pending connection for a caller that gave up.
127
134
  const timer = setTimeout(() => {
128
135
  socket.close();
129
- reject(new CdpTimeoutError({ method: 'connect', timeoutMs: options.timeoutMs }));
130
- }, options.timeoutMs);
136
+ reject(new CdpTimeoutError({ method: 'connect', timeoutMs: connectTimeoutMs }));
137
+ }, connectTimeoutMs);
131
138
  socket.onopen = (): void => {
132
139
  clearTimeout(timer);
133
140
  resolve();
@@ -68,27 +68,20 @@ export function devToolsEndpoint(text: string): string | undefined {
68
68
  const ANNOUNCE_POLL_MS = 25;
69
69
 
70
70
  /**
71
- * Dial, inside what is left of the LAUNCH deadline — `cdpConnect`'s own deadline is the per-call one
72
- * the connection keeps afterwards, so it cannot also be the launch's. A dial that loses the race
73
- * and connects later is closed, never left holding a socket nobody reads.
71
+ * Dial, inside what is left of the LAUNCH deadline. The handshake gets `leftMs`; `timeoutMs` is the
72
+ * per-call deadline the connection keeps afterwards, and it was the handshake's too until windows
73
+ * CI (run 42978963381) gave up on a cold Chrome 10 s into the upgrade with 50 s of its launch left —
74
+ * a browser that has bound its port is still starting, not dead. `cdpConnect` closes a socket whose
75
+ * handshake ran out, so a late upgrade never holds a socket nobody reads.
74
76
  */
75
77
  async function dialWithin(
76
78
  endpoint: string,
77
79
  leftMs: number,
78
80
  timeoutMs: number,
79
81
  ): Promise<CdpConnection> {
80
- const dialing = cdpConnect({ endpoint, timeoutMs });
81
- let timer: ReturnType<typeof setTimeout> | undefined;
82
- const late = new Promise<never>((_, reject) => {
83
- timer = setTimeout(
84
- () => reject(new CdpTimeoutError({ method: 'connect', timeoutMs: leftMs })),
85
- leftMs,
86
- );
87
- });
88
82
  try {
89
- return await Promise.race([dialing, late]);
83
+ return await cdpConnect({ endpoint, timeoutMs, connectTimeoutMs: leftMs });
90
84
  } catch (error) {
91
- void dialing.then((connection) => connection.close()).catch(() => undefined);
92
85
  // A timeout stays one — the launcher reads it as the deadline. Any other refusal names the
93
86
  // endpoint it was for: the browser announced it, so the dial is the step that failed.
94
87
  if (error instanceof CdpTimeoutError) throw error;
@@ -96,8 +89,6 @@ async function dialWithin(
96
89
  method: `connect ${endpoint}`,
97
90
  detail: renderThrowable(error),
98
91
  });
99
- } finally {
100
- clearTimeout(timer);
101
92
  }
102
93
  }
103
94