export type PageSource = { type: "page"; url: string; }; export type ZulipSource = { type: "zulip"; messageIds: number[]; }; export type Source = PageSource | ZulipSource; type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue }; function deterministicSerialize(val: JSONValue): string { if (val === null) { return "null"; } else if (Array.isArray(val)) { return `[${val.map(deterministicSerialize).join(",")}]`; } else if (typeof val === "object") { let s = "{"; for (const key of Object.keys(val).sort()) { if (s !== "{") s += ","; s += `${key}:${deterministicSerialize(val[key]!)}`; } return s + "}"; } else { return JSON.stringify(val); } } export async function deriveUid(source: Source) { const serialized: string = deterministicSerialize(source); const encoded = new TextEncoder().encode(serialized); const uid: ArrayBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded); return { uid, serialized }; }