Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 74 75 76 77 78 | 1x 9x 9x 9x 9x 2x 7x 6x 1x 1x 1x 6x 6x 5x 5x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 1x 1x 1x 1x | const MAX_REMOTE_HAR_BYTES = 100 * 1024 * 1024;
export interface RemoteHar {
name: string;
text: string;
url: string;
}
/** Resolve a direct HAR/NDJSON URL, including normal gist.github.com share links. */
export function remoteHarURL(value: string): URL {
let url: URL;
try {
url = new URL(value);
} catch {
throw new Error("The har parameter is not a valid URL.");
}
const loopbackHTTP = url.protocol === "http:" && isLoopbackHost(url.hostname);
if (url.protocol !== "https:" && !loopbackHTTP) {
throw new Error("Remote capture links must use HTTPS, except for loopback CLI links.");
}
if (url.username || url.password) throw new Error("Remote capture links must not contain URL credentials.");
if (url.hostname === "gist.github.com") {
const parts = url.pathname.split("/").filter(Boolean);
Iif (parts.length < 2) throw new Error("The GitHub Gist URL must include an owner and gist ID.");
url = new URL(`https://gist.githubusercontent.com/${parts[0]}/${parts[1]}/raw`);
}
url.hash = "";
return url;
}
function isLoopbackHost(hostname: string): boolean {
const normalized = hostname.toLowerCase();
return normalized === "localhost"
|| normalized === "127.0.0.1"
|| normalized === "[::1]";
}
export async function fetchRemoteHar(value: string, signal?: AbortSignal): Promise<RemoteHar> {
const url = remoteHarURL(value);
let response: Response;
try {
response = await fetch(url, { signal, credentials: "omit", referrerPolicy: "no-referrer" });
} catch (error) {
if (error instanceof DOMException && error.name === "AbortError") throw error;
throw new Error("The remote capture could not be downloaded. The host may not allow browser CORS requests.", { cause: error });
}
Iif (!response.ok) throw new Error(`The remote capture returned HTTP ${response.status}.`);
const declaredSize = Number(response.headers.get("content-length"));
Iif (Number.isFinite(declaredSize) && declaredSize > MAX_REMOTE_HAR_BYTES) {
throw new Error("The remote capture is larger than the 100 MiB limit.");
}
const text = await readLimitedText(response);
const pathName = new URL(response.url || url).pathname.split("/").filter(Boolean).at(-1);
return { name: pathName && pathName !== "raw" ? pathName : `remote capture (${url.hostname})`, text, url: url.href };
}
async function readLimitedText(response: Response): Promise<string> {
Iif (!response.body) return "";
const reader = response.body.getReader();
const decoder = new TextDecoder();
let bytes = 0;
let text = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
bytes += value.byteLength;
Iif (bytes > MAX_REMOTE_HAR_BYTES) {
await reader.cancel();
throw new Error("The remote capture is larger than the 100 MiB limit.");
}
text += decoder.decode(value, { stream: true });
}
return text + decoder.decode();
}
|