94 lines
3.5 KiB
JavaScript
94 lines
3.5 KiB
JavaScript
/**
|
|
* ZDDC — shared SHA-256 helpers
|
|
*
|
|
* Attaches to window.zddc.crypto. Must load AFTER shared/zddc.js (which creates
|
|
* the window.zddc object).
|
|
*
|
|
* Exports:
|
|
* zddc.crypto.sha256Hex(buffer) → Promise<string> hex digest of ArrayBuffer/Uint8Array
|
|
* zddc.crypto.sha256String(str) → Promise<string> hex digest of UTF-8 encoded string
|
|
* zddc.crypto.sha256File(file, onProgress?) → Promise<string>
|
|
* chunked streaming digest for File/Blob; for files >= 4 MB, streams 2 MB chunks
|
|
* and invokes onProgress(loaded, total) every ~8 MB.
|
|
* zddc.crypto.bytesToHex(buffer) → string (hex of ArrayBuffer/Uint8Array, no digest)
|
|
*
|
|
* Throws if Web Crypto SubtleCrypto is not available.
|
|
*/
|
|
(function (root) {
|
|
'use strict';
|
|
|
|
if (!root.zddc) {
|
|
throw new Error('shared/hash.js: window.zddc must be loaded first');
|
|
}
|
|
|
|
var HASH_CHUNK_SIZE = 2 * 1024 * 1024; // 2 MB
|
|
|
|
function requireSubtle() {
|
|
if (!root.crypto || !root.crypto.subtle || typeof root.crypto.subtle.digest !== 'function') {
|
|
throw new Error('Web Crypto SubtleCrypto is required');
|
|
}
|
|
}
|
|
|
|
function bytesToHex(buffer) {
|
|
return Array.from(new Uint8Array(buffer), function (byte) {
|
|
return byte.toString(16).padStart(2, '0');
|
|
}).join('');
|
|
}
|
|
|
|
async function sha256Hex(buffer) {
|
|
requireSubtle();
|
|
var input = (buffer instanceof Uint8Array) ? buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) : buffer;
|
|
var hash = await root.crypto.subtle.digest('SHA-256', input);
|
|
return bytesToHex(hash);
|
|
}
|
|
|
|
async function sha256String(str) {
|
|
requireSubtle();
|
|
var bytes = new TextEncoder().encode(str);
|
|
var hash = await root.crypto.subtle.digest('SHA-256', bytes);
|
|
return bytesToHex(hash);
|
|
}
|
|
|
|
async function sha256File(file, onProgress) {
|
|
requireSubtle();
|
|
// Single-shot for small files or environments without ReadableStream
|
|
if (file.size < HASH_CHUNK_SIZE * 2 || typeof file.stream !== 'function') {
|
|
if (onProgress) { onProgress(file.size, file.size); }
|
|
var buf = await file.arrayBuffer();
|
|
var hash = await root.crypto.subtle.digest('SHA-256', buf);
|
|
return bytesToHex(hash);
|
|
}
|
|
// Chunked streaming for large files
|
|
var reader = file.stream().getReader();
|
|
var loaded = 0;
|
|
var chunks = [];
|
|
var yieldCounter = 0;
|
|
while (true) {
|
|
var result = await reader.read();
|
|
if (result.done) { break; }
|
|
chunks.push(result.value);
|
|
loaded += result.value.byteLength;
|
|
yieldCounter++;
|
|
if (onProgress && yieldCounter % 4 === 0) {
|
|
onProgress(loaded, file.size);
|
|
await new Promise(function (r) { setTimeout(r, 0); });
|
|
}
|
|
}
|
|
var total = new Uint8Array(loaded);
|
|
var offset = 0;
|
|
for (var i = 0; i < chunks.length; i++) {
|
|
total.set(chunks[i], offset);
|
|
offset += chunks[i].byteLength;
|
|
}
|
|
var digest = await root.crypto.subtle.digest('SHA-256', total.buffer);
|
|
if (onProgress) { onProgress(file.size, file.size); }
|
|
return bytesToHex(digest);
|
|
}
|
|
|
|
root.zddc.crypto = {
|
|
sha256Hex: sha256Hex,
|
|
sha256String: sha256String,
|
|
sha256File: sha256File,
|
|
bytesToHex: bytesToHex,
|
|
};
|
|
})(typeof window !== 'undefined' ? window : globalThis);
|