Spaces:
Running
Running
| 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<BlobIntegrityResult> { | |
| 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(); | |
| } | |
| } | |