const keyCache = new Map(); async function getHmacKey(secret: string): Promise { const cached = keyCache.get(secret); if (cached) return cached; const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ); keyCache.set(secret, key); return key; } export async function hmacSha256Hex(secret: string, payload: string): Promise { const key = await getHmacKey(secret); const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(payload)); return Array.from(new Uint8Array(sig)) .map((b) => b.toString(16).padStart(2, "0")) .join(""); } /** Constant-time string comparison. */ export function timingSafeEqual(a: string, b: string): boolean { const encoder = new TextEncoder(); const x = encoder.encode(a); const y = encoder.encode(b); if (x.byteLength !== y.byteLength) return false; let diff = 0; for (let i = 0; i < x.byteLength; i++) { diff |= x[i]! ^ y[i]!; } return diff === 0; }