UNPKG

@ayonli/jsext

Version:

A JavaScript extension package for building strong and modern applications.

246 lines (219 loc) 7.35 kB
/** * A slim version of the `hash` module for the browser. * * Normally, we should just use the `hash` module, however, if we don't want * to include other parts that are not needed in the browser, we can use this * module instead. * @module */ import bytes, { text } from "../bytes.ts"; import { toBytes, toBytesAsync } from "./util.ts"; export type DataSource = string | BufferSource | ReadableStream<Uint8Array> | Blob; /** * Calculates the hash of the given data, the result is a 32-bit unsigned integer. * * This function uses the same algorithm as the [string-hash](https://www.npmjs.com/package/string-hash) * package, non-string data are converted to strings before hashing. * * @example * ```ts * import hash from "@ayonli/jsext/hash"; * * console.log(hash("Hello, World!")); // 4010631688 * console.log(hash(new Uint8Array([1, 2, 3]))); // 193378021 * ``` */ export default function hash(data: string | BufferSource): number { let str: string; if (typeof data === "string") { str = data; } else if (data instanceof ArrayBuffer) { str = text(new Uint8Array(data)); } else if (data instanceof Uint8Array) { str = text(data); } else if (ArrayBuffer.isView(data)) { const bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); str = text(bytes); } else { throw new TypeError("Unsupported data type"); } let hash = 5381; let i = str.length; while (i) { hash = (hash * 33) ^ str.charCodeAt(--i); } /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed * integers. Since we want the results to be always positive, convert the * signed int to an unsigned by doing an unsigned bitshift. */ return hash >>> 0; } /** * Calculates the Adler-32 checksum of the given data, the result is a 32-bit * unsigned integer. * * Adler-32 checksum is used in zlib and libpng, it is similar to CRC32 but * faster and less reliable. * * @param previous The previous Adler-32 value, default is `1`. This is useful * when calculating the checksum of a large data in chunks. * * @example * ```ts * import { adler32 } from "@ayonli/jsext/hash"; * * console.log(adler32("Hello, World!")); // 530449514 * console.log(adler32(new Uint8Array([1, 2, 3]))); // 851975 * * // calculate chunks * const chunks = ["Hello, ", "World!"]; * let checksum = 1; * * for (const chunk of chunks) { * checksum = adler32(chunk, checksum); * } * * console.log(checksum); // 530449514 * ``` */ export function adler32(data: string | BufferSource, previous = 1): number { const bin = toBytes(data); let a = previous & 0xffff; let b = (previous >>> 16) & 0xffff; for (let i = 0; i < bin.length; i++) { a = (a + bin[i]!) % 65521; b = (b + a) % 65521; } return ((b << 16) | a) >>> 0; } const CRC32_TABLE = (() => { const IEEE = 0xedb88320; const table = new Uint32Array(256); // Fill the table for (let i = 0; i < 256; i++) { let crc = i; for (let j = 0; j < 8; j++) { crc = crc & 1 ? IEEE ^ crc >>> 1 : crc >>> 1; } table[i] = crc; } return table; })(); /** * Calculates the CRC-32 checksum of the given data, the result is a 32-bit * unsigned integer. * * This function is based on IEEE polynomial, which is widely used by Ethernet * (IEEE 802.3), v.42, fddi, gzip, zip, png and other technologies. * * @param previous The previous checksum value, default is `0`. This is useful * when calculating the CRC of a large data in chunks. * * @example * ```ts * import { crc32 } from "@ayonli/jsext/hash"; * * console.log(crc32("Hello, World!")); // 3964322768 * console.log(crc32(new Uint8Array([1, 2, 3]))); // 1438416925 * * // calculate chunks * const chunks = ["Hello, ", "World!"]; * let checksum = 0; * * for (const chunk of chunks) { * checksum = crc32(chunk, checksum); * } * * console.log(checksum); // 3964322768 * ``` */ export function crc32(data: string | BufferSource, previous = 0): number { const bin = toBytes(data); let crc = ~~previous ^ -1; for (let i = 0; i < bin.length; i++) { crc = CRC32_TABLE[(crc ^ bin[i]!) & 0xff]! ^ crc >>> 8; } return (crc ^ -1) >>> 0; } export function sha1(data: DataSource): Promise<ArrayBuffer>; export function sha1(data: DataSource, encoding: "hex" | "base64"): Promise<string>; export async function sha1( data: DataSource, encoding: "hex" | "base64" | undefined = undefined ): Promise<string | ArrayBuffer> { const bytes = await toBytesAsync(data); const hash = await crypto.subtle.digest("SHA-1", bytes); if (encoding === "hex") { return text(new Uint8Array(hash), "hex"); } else if (encoding === "base64") { return text(new Uint8Array(hash), "base64"); } else { return hash; } } export async function sha256(data: DataSource): Promise<ArrayBuffer>; export async function sha256(data: DataSource, encoding: "hex" | "base64"): Promise<string>; export async function sha256( data: DataSource, encoding: "hex" | "base64" | undefined = undefined ): Promise<string | ArrayBuffer> { const bytes = await toBytesAsync(data); const hash = await crypto.subtle.digest("SHA-256", bytes); if (encoding === "hex") { return text(new Uint8Array(hash), "hex"); } else if (encoding === "base64") { return text(new Uint8Array(hash), "base64"); } else { return hash; } } export async function sha512(data: DataSource): Promise<ArrayBuffer>; export async function sha512(data: DataSource, encoding: "hex" | "base64"): Promise<string>; export async function sha512( data: DataSource, encoding: "hex" | "base64" | undefined = undefined ): Promise<string | ArrayBuffer> { const bytes = await toBytesAsync(data); const hash = await crypto.subtle.digest("SHA-512", bytes); if (encoding === "hex") { return text(new Uint8Array(hash), "hex"); } else if (encoding === "base64") { return text(new Uint8Array(hash), "base64"); } else { return hash; } } export async function hmac( algorithm: "sha1" | "sha256" | "sha512", key: string | BufferSource, data: DataSource ): Promise<ArrayBuffer>; export async function hmac( algorithm: "sha1" | "sha256" | "sha512", key: string | BufferSource, data: DataSource, encoding: "hex" | "base64" ): Promise<string>; export async function hmac( algorithm: "sha1" | "sha256" | "sha512", key: string | BufferSource, data: DataSource, encoding: "hex" | "base64" | undefined = undefined ): Promise<string | ArrayBuffer> { const keyBuffer = await crypto.subtle.importKey("raw", bytes(key), { name: "HMAC", hash: { "sha1": "SHA-1", "sha256": "SHA-256", "sha512": "SHA-512", }[algorithm], }, false, ["sign"]); const dataBytes = await toBytesAsync(data); const hash = await crypto.subtle.sign("HMAC", keyBuffer, dataBytes); if (encoding === "hex") { return text(new Uint8Array(hash), "hex"); } else if (encoding === "base64") { return text(new Uint8Array(hash), "base64"); } else { return hash; } }