Created
November 7, 2024 19:14
-
-
Save transitive-bullshit/df92c7df2b8b1a17a71e0edb20b0cecc to your computer and use it in GitHub Desktop.
Get the hash of an object
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import decircular from 'decircular' | |
import isObject from 'is-obj' | |
import sortKeys from 'sort-keys' | |
import { type LiteralUnion } from 'type-fest' | |
export type Encoding = 'hex' | 'base64' | 'buffer' | |
export type Algorithm = LiteralUnion<'sha1' | 'sha256' | 'sha512', string> | |
export type Options = { | |
/** | |
* The encoding of the returned hash. | |
* | |
* @default 'hex' | |
*/ | |
readonly encoding?: Encoding | |
/** | |
* _Don't use `'md5'` or `'sha1'` for anything sensitive. [They're insecure.](http://googleonlinesecurity.blogspot.no/2014/09/gradually-sunsetting-sha-1.html)_ | |
* | |
* @default 'sha512' | |
*/ | |
readonly algorithm?: Algorithm | |
} | |
export type BufferOptions = { | |
readonly encoding: 'buffer' | |
} & Options | |
function normalizeObject<T>(object: T): T { | |
if (typeof object === 'string') { | |
return object.normalize('NFD') as T | |
} | |
if (Array.isArray(object)) { | |
return object.map((element: any) => normalizeObject(element)) as T | |
} | |
if (isObject(object)) { | |
return Object.fromEntries( | |
Object.entries(object).map(([key, value]) => [ | |
key.normalize('NFD'), | |
normalizeObject(value) | |
]) | |
) as T | |
} | |
return object | |
} | |
export default async function hashObject( | |
object: Record<string, any>, | |
{ encoding = 'hex', algorithm = 'sha512' }: BufferOptions | Options = {} | |
): Promise<string | Uint8Array> { | |
if (!isObject(object)) { | |
throw new TypeError('Expected an object') | |
} | |
const normalizedObject = normalizeObject(decircular(object)) | |
const stableJsonInput = JSON.stringify( | |
sortKeys(normalizedObject, { deep: true }) | |
) | |
const encoder = new TextEncoder() | |
const data = encoder.encode(stableJsonInput) | |
const algorithmWebCrypto = | |
algorithm === 'sha1' | |
? 'SHA-1' | |
: algorithm === 'sha256' | |
? 'SHA-256' | |
: 'SHA-512' | |
const hashBuffer = await crypto.subtle.digest(algorithmWebCrypto, data) | |
if (encoding === 'buffer') { | |
return new Uint8Array(hashBuffer) | |
} | |
const hashArray = Array.from(new Uint8Array(hashBuffer)) | |
if (encoding === 'hex') { | |
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') | |
} else if (encoding === 'base64') { | |
// eslint-disable-next-line unicorn/prefer-code-point | |
return btoa(String.fromCharCode(...hashArray)) | |
} else { | |
throw new Error(`Unsupported encoding: ${encoding}`) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Slight modification to https://github.com/sindresorhus/hash-object to support non-node.js environments using the web crypto API.