mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-09-19 16:12:17 +02:00
refactor: cleanup
This commit is contained in:
+67
-14
@@ -1,9 +1,59 @@
|
||||
import assert from "node:assert/strict";
|
||||
import http from "node:http";
|
||||
|
||||
export const ELEMENT_KEY = "element-6066-11e4-a52e-4f735466cecf";
|
||||
|
||||
function abortAfter(timeoutMs) {
|
||||
return AbortSignal.timeout(timeoutMs);
|
||||
/**
|
||||
* One HTTP exchange with the driver, over `node:http` rather than `fetch`.
|
||||
*
|
||||
* `fetch` is undici, and undici gives every request a 300 s headers timeout
|
||||
* of its own. A long `execute/async` sends no headers until the script
|
||||
* completes, so a `download_browser` that pulls a 1 GB Wayfern build over a
|
||||
* slow link died at 300 s whatever `timeoutMs` asked for. `node:http` has no
|
||||
* such default, which leaves `timeoutMs` as the only clock.
|
||||
*/
|
||||
function exchange(method, url, body, timeoutMs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const payload = body === undefined ? undefined : JSON.stringify(body);
|
||||
const request = http.request(
|
||||
url,
|
||||
{
|
||||
method,
|
||||
headers:
|
||||
payload === undefined
|
||||
? {}
|
||||
: {
|
||||
"content-type": "application/json",
|
||||
"content-length": Buffer.byteLength(payload),
|
||||
},
|
||||
signal: AbortSignal.timeout(timeoutMs),
|
||||
},
|
||||
(response) => {
|
||||
const chunks = [];
|
||||
response.on("data", (chunk) => chunks.push(chunk));
|
||||
response.on("error", reject);
|
||||
response.on("end", () =>
|
||||
resolve({
|
||||
status: response.statusCode ?? 0,
|
||||
text: Buffer.concat(chunks).toString("utf8"),
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
request.on("error", (error) => {
|
||||
const timedOut =
|
||||
error?.name === "AbortError" || error?.name === "TimeoutError";
|
||||
reject(
|
||||
timedOut
|
||||
? new Error(
|
||||
`WebDriver ${method} ${url} gave no response within ${timeoutMs}ms`,
|
||||
{ cause: error },
|
||||
)
|
||||
: error,
|
||||
);
|
||||
});
|
||||
request.end(payload);
|
||||
});
|
||||
}
|
||||
|
||||
export class WebDriverClient {
|
||||
@@ -12,30 +62,27 @@ export class WebDriverClient {
|
||||
}
|
||||
|
||||
async request(method, pathname, body, timeoutMs = 330_000) {
|
||||
const response = await fetch(`${this.baseUrl}${pathname}`, {
|
||||
const { status, text } = await exchange(
|
||||
method,
|
||||
headers:
|
||||
body === undefined ? undefined : { "content-type": "application/json" },
|
||||
body: body === undefined ? undefined : JSON.stringify(body),
|
||||
signal: abortAfter(timeoutMs),
|
||||
});
|
||||
const text = await response.text();
|
||||
`${this.baseUrl}${pathname}`,
|
||||
body,
|
||||
timeoutMs,
|
||||
);
|
||||
let payload = null;
|
||||
if (text) {
|
||||
try {
|
||||
payload = JSON.parse(text);
|
||||
} catch {
|
||||
throw new Error(
|
||||
`WebDriver ${method} ${pathname} returned non-JSON HTTP ${response.status}: ${text.slice(0, 500)}`,
|
||||
`WebDriver ${method} ${pathname} returned non-JSON HTTP ${status}: ${text.slice(0, 500)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const error = payload?.value?.error;
|
||||
if (!response.ok) {
|
||||
const message =
|
||||
payload?.value?.message ?? text ?? `HTTP ${response.status}`;
|
||||
if (status < 200 || status >= 300) {
|
||||
const message = payload?.value?.message ?? text ?? `HTTP ${status}`;
|
||||
throw new Error(
|
||||
`WebDriver ${method} ${pathname} failed (${error ?? response.status}): ${message}`,
|
||||
`WebDriver ${method} ${pathname} failed (${error ?? status}): ${message}`,
|
||||
);
|
||||
}
|
||||
return payload?.value;
|
||||
@@ -51,11 +98,17 @@ export class WebDriverClient {
|
||||
env = {},
|
||||
cwd,
|
||||
startupTimeout = 90_000,
|
||||
headless = false,
|
||||
}) {
|
||||
const options = { application, args, env, startupTimeout };
|
||||
if (cwd) {
|
||||
options.cwd = cwd;
|
||||
}
|
||||
// Only sent when asked, so a driver build without the capability is not
|
||||
// handed an option it would reject.
|
||||
if (headless) {
|
||||
options.headless = true;
|
||||
}
|
||||
const value = await this.request(
|
||||
"POST",
|
||||
"/session",
|
||||
|
||||
Reference in New Issue
Block a user