1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
import { debug, isDebug } from "./console";
export const fetch = "fetch" in globalThis ? webFetch : nodeFetch;
type Options = RequestInit & { assert?: boolean };
async function webFetch(url: string, options: Options = {}): Promise<Response> {
debug("fetch request", url, options);
const response = await globalThis.fetch(url, options, { verbose: isDebug });
debug("fetch response", response);
if (options?.assert !== false && !isOk(response.status)) {
try {
debug(await response.text());
} catch {}
throw new Error(`${response.status}: ${url}`);
}
return response;
}
async function nodeFetch(
url: string,
options: Options = {},
): Promise<Response> {
const { get } = await import("node:http");
return new Promise((resolve, reject) => {
get(url, (response) => {
debug("http.get", url, response.statusCode);
const status = response.statusCode ?? 501;
if (response.headers.location && isRedirect(status)) {
return nodeFetch(url).then(resolve, reject);
}
if (options?.assert !== false && !isOk(status)) {
return reject(new Error(`${status}: ${url}`));
}
const body: Buffer[] = [];
response.on("data", (chunk) => {
body.push(chunk);
});
response.on("end", () => {
resolve({
ok: isOk(status),
status,
async arrayBuffer() {
return Buffer.concat(body).buffer as ArrayBuffer;
},
async text() {
return Buffer.concat(body).toString("utf-8");
},
async json() {
const text = Buffer.concat(body).toString("utf-8");
return JSON.parse(text);
},
} as Response);
});
}).on("error", reject);
});
}
function isOk(status: number): boolean {
return status >= 200 && status <= 204;
}
function isRedirect(status: number): boolean {
switch (status) {
case 301: // Moved Permanently
case 308: // Permanent Redirect
case 302: // Found
case 307: // Temporary Redirect
case 303: // See Other
return true;
}
return false;
}
|