import type { ResolveOptions, ViewerSource } from "./types.ts"; export function safeSourceUrl(src: string): string | undefined { try { const url = new URL(src, typeof location === "undefined" ? "http://localhost" : location.href); return ["http:", "https:", "blob:", "data:"].includes(url.protocol) ? url.href : undefined; } catch { return undefined; } } /** The source's own http(s) URL, when it has one that an element can load. */ export function directUrl(source: ViewerSource): string | undefined { if (source.blob || !source.src) return undefined; const url = safeSourceUrl(source.src); return url && /^https?:/i.test(url) ? url : undefined; } export async function readBounded( response: Response, cap: number, prefixOnly = false, ): Promise { if (!response.ok || !response.body) throw new Error("File could not be read"); const reader = response.body.getReader(); const parts: Uint8Array[] = []; let length = 0; try { while (true) { const next = await reader.read(); if (next.done) break; if (!prefixOnly && length + next.value.length > cap) throw new Error("File exceeds preview limit"); const part = next.value.subarray(0, cap - length); parts.push(part); length += part.length; if (prefixOnly && length >= cap) break; } } finally { await reader.cancel().catch(() => {}); reader.releaseLock(); } const bytes = new Uint8Array(length); let offset = 0; for (const part of parts) { bytes.set(part, offset); offset += part.length; } return bytes; } export async function readSource( source: ViewerSource, cap: number, options: ResolveOptions = {}, ): Promise { if (source.blob) { if (source.blob.size > cap) throw new Error("File exceeds preview limit"); return source.blob; } const url = source.src && safeSourceUrl(source.src); if (!url) throw new Error("No readable file source"); const response = await (options.fetch ?? fetch)(url, { signal: options.signal }); const bytes = await readBounded(response, cap); return new Blob([bytes as Uint8Array], { type: response.headers.get("content-type") ?? source.mime ?? "", }); }