69 lines
2.2 KiB
TypeScript
69 lines
2.2 KiB
TypeScript
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<Uint8Array> {
|
|
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<Blob> {
|
|
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<ArrayBuffer>], {
|
|
type: response.headers.get("content-type") ?? source.mime ?? "",
|
|
});
|
|
}
|