import { vi } from "vitest"; import type { ImageProbeScheduler, } from "../../src/adapters/browser-transfer/image-cdn/browser-image-probe.ts"; import type { ImageCapabilityVerificationScheduler, } from "../../src/adapters/browser-transfer/image-cdn/image-cdn-runtime.ts"; export function responseAt( url: string, body: Uint8Array, init: ResponseInit, ): Response { const responseBytes = new Uint8Array(body.byteLength); responseBytes.set(body); const response = new Response(responseBytes.buffer, init); Object.defineProperty(response, "url", { configurable: false, enumerable: true, value: url, }); return response; } export function publicImageHeaders( mediaType: string, contentLength?: number, ): Headers { const headers = new Headers({ "cache-control": "public, max-age=31536000, s-maxage=31536000, immutable", "content-type": mediaType, vary: "Accept-Encoding", }); if (contentLength !== undefined) { headers.set("content-length", String(contentLength)); } return headers; } export function pngBytes( width: number, height: number, animated = false, ): Uint8Array { const header = new Uint8Array(13); const headerView = new DataView(header.buffer); headerView.setUint32(0, width); headerView.setUint32(4, height); header[8] = 8; header[9] = 6; return concatenateBytes([ Uint8Array.from([ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, ]), pngChunk("IHDR", header), ...(animated ? [pngChunk("acTL", new Uint8Array(8))] : []), pngChunk("IDAT", new Uint8Array()), pngChunk("IEND", new Uint8Array()), ]); } function pngChunk(type: string, payload: Uint8Array): Uint8Array { const chunk = new Uint8Array(12 + payload.byteLength); const view = new DataView(chunk.buffer); view.setUint32(0, payload.byteLength); writeAscii(chunk, 4, type); chunk.set(payload, 8); return chunk; } export function jpegBytes(width: number, height: number): Uint8Array { return Uint8Array.from([ 0xff, 0xd8, 0xff, 0xc0, 0x00, 0x11, 0x08, (height >>> 8) & 0xff, height & 0xff, (width >>> 8) & 0xff, width & 0xff, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x00, 0x03, 0x11, 0x00, 0xff, 0xda, ]); } export function webpBytes( width: number, height: number, animated = false, ): Uint8Array { const chunkType = animated ? "VP8X" : "VP8 "; const payload = new Uint8Array(10); if (animated) { payload[0] = 0x02; writeUint24LittleEndian(payload, 4, width - 1); writeUint24LittleEndian(payload, 7, height - 1); } else { payload.set([0x9d, 0x01, 0x2a], 3); const view = new DataView(payload.buffer); view.setUint16(6, width, true); view.setUint16(8, height, true); } const chunk = concatenateBytes([ asciiBytes(chunkType), littleEndianUint32(payload.byteLength), payload, ]); return concatenateBytes([ asciiBytes("RIFF"), littleEndianUint32(4 + chunk.byteLength), asciiBytes("WEBP"), chunk, ]); } export function avifBytes( width: number, height: number, brand = "avif", ): Uint8Array { const fileType = isoBox( "ftyp", concatenateBytes([ asciiBytes(brand), new Uint8Array(4), asciiBytes(brand), ]), ); const spatialExtent = new Uint8Array(12); const extentView = new DataView(spatialExtent.buffer); extentView.setUint32(4, width); extentView.setUint32(8, height); const primaryItem = new Uint8Array(6); new DataView(primaryItem.buffer).setUint16(4, 1); const itemInfoEntry = new Uint8Array(13); itemInfoEntry[0] = 2; const itemInfoView = new DataView(itemInfoEntry.buffer); itemInfoView.setUint16(4, 1); writeAscii(itemInfoEntry, 8, "av01"); const itemInfo = new Uint8Array(6); new DataView(itemInfo.buffer).setUint16(4, 1); const propertyAssociation = new Uint8Array(12); const associationView = new DataView( propertyAssociation.buffer, ); associationView.setUint32(4, 1); associationView.setUint16(8, 1); propertyAssociation[10] = 1; propertyAssociation[11] = 0x81; const properties = isoBox( "iprp", concatenateBytes([ isoBox("ipco", isoBox("ispe", spatialExtent)), isoBox("ipma", propertyAssociation), ]), ); const metadata = isoBox( "meta", concatenateBytes([ new Uint8Array(4), isoBox("pitm", primaryItem), isoBox( "iinf", concatenateBytes([ itemInfo, isoBox("infe", itemInfoEntry), ]), ), properties, ]), ); return concatenateBytes([ fileType, metadata, isoBox("mdat", Uint8Array.of(0)), ]); } export function isoBox(type: string, payload: Uint8Array): Uint8Array { const box = new Uint8Array(8 + payload.byteLength); const view = new DataView(box.buffer); view.setUint32(0, box.byteLength); writeAscii(box, 4, type); box.set(payload, 8); return box; } export function littleEndianUint32(value: number): Uint8Array { const bytes = new Uint8Array(4); new DataView(bytes.buffer).setUint32(0, value, true); return bytes; } export function writeUint24LittleEndian( bytes: Uint8Array, offset: number, value: number, ): void { bytes[offset] = value & 0xff; bytes[offset + 1] = (value >>> 8) & 0xff; bytes[offset + 2] = (value >>> 16) & 0xff; } export function asciiBytes(value: string): Uint8Array { return Uint8Array.from( [...value].map((character) => character.charCodeAt(0)), ); } export function writeAscii( target: Uint8Array, offset: number, value: string, ): void { target.set(asciiBytes(value), offset); } export function concatenateBytes( chunks: readonly Uint8Array[], ): Uint8Array { const combined = new Uint8Array( chunks.reduce((total, chunk) => total + chunk.byteLength, 0), ); let offset = 0; for (const chunk of chunks) { combined.set(chunk, offset); offset += chunk.byteLength; } return combined; } export function manualImageProbeScheduler(): Readonly<{ scheduler: ImageProbeScheduler; fire(): void; }> { let callback: (() => void) | undefined; return { scheduler: { setTimeout(nextCallback) { callback = nextCallback; return 1; }, clearTimeout: vi.fn(), }, fire() { if (!callback) { throw new TypeError("No image probe timeout is scheduled."); } callback(); }, }; } export function manualCapabilityVerificationScheduler(): Readonly<{ scheduler: ImageCapabilityVerificationScheduler; delays: readonly number[]; clearTimeout: ReturnType; fire(): void; }> { let callback: (() => void) | undefined; const delays: number[] = []; const clearTimeout = vi.fn(); return { scheduler: { setTimeout(nextCallback, milliseconds) { callback = nextCallback; delays.push(milliseconds); return 1; }, clearTimeout, }, delays, clearTimeout, fire() { if (!callback) { throw new TypeError( "No capability verification timeout is scheduled.", ); } callback(); }, }; } export function base64Url(bytes: Uint8Array): string { let binary = ""; for (const byte of bytes) binary += String.fromCharCode(byte); return btoa(binary) .replace(/\+/gu, "-") .replace(/\//gu, "_") .replace(/=+$/gu, ""); }