| 1 | import { app } from "./bridge"; |
| 2 | import type { HistoryContentChunk, HistoryContentRef } from "./types"; |
| 3 | |
| 4 | function decodeBase64Bytes(data: string): Uint8Array { |
| 5 | const binary = atob(data); |
| 6 | return Uint8Array.from(binary, character => character.charCodeAt(0)); |
| 7 | } |
| 8 | |
| 9 | export async function readCanonicalHistoryContent(tabID: string, ref: HistoryContentRef, chunkIndex: number, remote: boolean, recover: () => void): Promise<HistoryContentChunk> { |
| 10 | if (ref.transcriptRef) { |
| 11 | const read = remote ? app.RemoteTranscriptContentForTab : app.TranscriptContentForTab; |
| 12 | if (!read) throw new Error("Transcript v2 content is unavailable"); |
| 13 | let offset = 0, data = ""; |
| 14 | while (true) { |
| 15 | const chunk = await read(tabID, { ...ref.transcriptRef, offset }); |
| 16 | if (chunk.stale) { |
| 17 | recover(); |
| 18 | throw new Error("Transcript content snapshot expired; synchronizing, retry after recovery"); |
| 19 | } |
| 20 | data += chunk.data; |
| 21 | if (chunk.done) return { entryId: ref.entryId, field: ref.field, chunk: chunkIndex, chunks: 1, data, done: true, stale: false }; |
| 22 | if (chunk.nextOffset <= offset) throw new Error("Transcript content did not advance"); |
| 23 | offset = chunk.nextOffset; |
| 24 | } |
| 25 | } |
| 26 | if (!ref.canonicalRef) return app.HistoryContentForTab(tabID, ref, chunkIndex); |
| 27 | const offset = chunkIndex * (1 << 20); |
| 28 | const chunk = remote |
| 29 | ? await app.RemoteSessionHistoryContentForTab(tabID, ref.canonicalRef, offset) |
| 30 | : await app.SessionHistoryContentForTab(tabID, ref.canonicalRef, offset); |
| 31 | const bytes = decodeBase64Bytes(chunk.data ?? ""); |
| 32 | let data = ""; |
| 33 | for (let start = 0; start < bytes.length; start += 0x8000) data += String.fromCharCode(...bytes.subarray(start, start + 0x8000)); |
| 34 | return { entryId: ref.entryId, field: ref.field, chunk: chunkIndex, chunks: ref.chunks, data, done: chunk.done, stale: false }; |
| 35 | } |
| 36 |