import { HEIC_DECODE_TIMEOUT_MS, HEIC_MAX_INPUT_BYTES, heicError, heicOutputProfile } from './heicDecodeCore.js'; /** One worker at a time keeps multi-photo selection from multiplying decoder memory. */ export function createHeicDecoder({ createWorker = () => new Worker(new URL('./heicDecoder.worker.js', import.meta.url), { type: 'module' }), timeoutMs = HEIC_DECODE_TIMEOUT_MS, setTimeoutFn = setTimeout, clearTimeoutFn = clearTimeout, } = {}) { let tail = Promise.resolve(); const convert = (file, options) => new Promise((resolve, reject) => { let worker, settled = false; const profile = heicOutputProfile(options); const finish = (error, blob = null) => { if (settled) return; settled = true; clearTimeoutFn(timer); if (worker) { worker.onmessage = null; worker.onerror = null; worker.onmessageerror = null; worker.terminate(); } if (error) reject(error); else resolve(blob); }; const timer = setTimeoutFn(() => finish(heicError('HEIC_DECODE_TIMEOUT')), timeoutMs); Promise.resolve().then(() => file.arrayBuffer()).then(buffer => { if (settled) return; if (!(buffer instanceof ArrayBuffer) || !buffer.byteLength || buffer.byteLength > HEIC_MAX_INPUT_BYTES) throw heicError('HEIC_INPUT_TOO_LARGE'); worker = createWorker(); worker.onerror = () => finish(heicError('HEIC_DECODE_FAILED')); worker.onmessageerror = () => finish(heicError('HEIC_DECODE_FAILED')); worker.onmessage = ({ data }) => { if (data?.error) return finish(heicError(/^HEIC_[A-Z_]+$/.test(data.error) ? data.error : 'HEIC_DECODE_FAILED')); const blob = data?.blob; if (!(blob instanceof Blob) || blob.type !== 'image/jpeg' || !blob.size || blob.size > profile.maxSizeBytes) return finish(heicError('HEIC_CONVERSION_FAILED')); finish(null, blob); }; worker.postMessage({ buffer, profile }, [buffer]); }).catch(error => finish(heicError(error?.code || 'HEIC_DECODE_FAILED'))); }); return (file, options = {}) => { if (!file || typeof file.arrayBuffer !== 'function' || !Number.isFinite(file.size) || file.size <= 0 || file.size > HEIC_MAX_INPUT_BYTES) { return Promise.reject(heicError('HEIC_INPUT_TOO_LARGE')); } const task = tail.then(() => convert(file, options)); tail = task.catch(() => {}); return task; }; } export const decodeHeicToBlob = createHeicDecoder();