const encoder = new TextEncoder(); export function defaultRandomBytes(length: number): Uint8Array { if (!Number.isInteger(length) || length < 1) throw new RangeError("random byte length must be positive"); const bytes = new Uint8Array(length); crypto.getRandomValues(bytes); return bytes; } export function bytesToBase64Url(bytes: Uint8Array): string { let binary = ""; for (const byte of bytes) binary += String.fromCharCode(byte); return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""); } export function randomId(randomBytes = defaultRandomBytes, length = 24): string { return bytesToBase64Url(randomBytes(length)); } function bytesToHex(bytes: Uint8Array): string { let out = ""; for (const byte of bytes) out += byte.toString(16).padStart(2, "0"); return out; } export async function sha256(value: string): Promise { const digest = await crypto.subtle.digest("SHA-256", encoder.encode(value)); return bytesToHex(new Uint8Array(digest)); } export async function hmacSha256(secret: string, value: string): Promise { const key = await crypto.subtle.importKey( "raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ); const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(value)); return bytesToHex(new Uint8Array(signature)); } export function constantTimeEqual(left: string, right: string): boolean { if (left.length !== right.length) return false; let difference = 0; for (let index = 0; index < left.length; index++) { difference |= left.charCodeAt(index) ^ right.charCodeAt(index); } return difference === 0; } export async function bindingHash(value: string | undefined): Promise { if (!value) return undefined; return sha256(value); }