import { sha256 } from '@noble/hashes/sha2.js'; import { bytesToHex } from '@noble/hashes/utils.js'; import { abortError, throwIfAborted } from './errors'; export interface BlobIntegrityResult { actualSha256: string; matches: boolean; } export async function verifyBlobSha256( blob: Blob, expectedSha256: string, signal?: AbortSignal, onProgress?: (loadedBytes: number, totalBytes: number) => void, ): Promise { if (signal) { throwIfAborted(signal); } const hasher = sha256.create(); const reader = blob.stream().getReader(); const cancelRead = (): void => { void reader.cancel(abortError()).catch(() => undefined); }; signal?.addEventListener('abort', cancelRead, { once: true }); let loadedBytes = 0; let lastProgressAt = 0; onProgress?.(0, blob.size); try { while (true) { if (signal) { throwIfAborted(signal); } const { done, value } = await reader.read(); if (signal) { throwIfAborted(signal); } if (done) { break; } hasher.update(value); loadedBytes += value.byteLength; const now = performance.now(); if (now - lastProgressAt >= 100) { lastProgressAt = now; onProgress?.(Math.min(loadedBytes, blob.size), blob.size); } } onProgress?.(blob.size, blob.size); const actualSha256 = bytesToHex(hasher.digest()); return { actualSha256, matches: actualSha256 === expectedSha256, }; } finally { signal?.removeEventListener('abort', cancelRead); reader.releaseLock(); hasher.destroy(); } }