export const REPLAY_JSON_MIME_TYPE = "application/vnd.proctorkit.replay+json"; export const REPLAY_GZIP_MIME_TYPE = "application/vnd.proctorkit.replay+json+gzip"; /** Encode one bounded replay window, preferring native streaming gzip. */ export async function encodeReplayEvents(events: readonly unknown[]): Promise { return encodeSerializedReplayEvents(events.map((event) => JSON.stringify(event))); } /** Encode already-serialized events without parsing and stringifying a second * in-memory copy of the replay window. */ export async function encodeSerializedReplayEvents( serializedEvents: readonly string[], ): Promise { const parts: BlobPart[] = ["["]; serializedEvents.forEach((event, index) => { if (index > 0) parts.push(","); parts.push(event); }); parts.push("]"); const input = new Blob(parts, { type: REPLAY_JSON_MIME_TYPE }); if (typeof CompressionStream === "undefined") { return input; } const compressed = input.stream().pipeThrough(new CompressionStream("gzip")); const bytes = await new Response(compressed).arrayBuffer(); return new Blob([bytes], { type: REPLAY_GZIP_MIME_TYPE }); } /** Decode a replay chunk. Exported for SDK diagnostics and codec tests. */ export async function decodeReplayChunk(blob: Blob): Promise { let text: string; if (blob.type === REPLAY_GZIP_MIME_TYPE) { if (typeof DecompressionStream === "undefined") { throw new Error("This browser cannot decompress assessment replay data"); } const decompressed = blob.stream().pipeThrough(new DecompressionStream("gzip")); text = await new Response(decompressed).text(); } else { text = await blob.text(); } const parsed: unknown = JSON.parse(text); if (!Array.isArray(parsed)) { throw new Error("Assessment replay chunk must contain an event array"); } return parsed; }