| 1 | import type { Meta, TabMeta } from "./types"; |
| 2 | import { sameSessionIdentity, sessionIdentityStableKey, type SessionIdentity } from "./sessionIdentity"; |
| 3 | import type { HydrateSurfacePolicy } from "./hydrateHistoryApply"; |
| 4 | |
| 5 | /** A passive runtime refresh may retain an already certified reading window. |
| 6 | * Missing content metadata is not an instruction to replace it with the tail. |
| 7 | * Navigation, generation changes, explicit retry and new content proof still |
| 8 | * require the reader to establish a fresh window. */ |
| 9 | export function coldHistoryRefreshProof(target: TabMeta, state: { |
| 10 | meta?: Meta; hydrating?: boolean; hydrateError?: string; |
| 11 | historyRevision?: number; historyDigest?: string; |
| 12 | } | undefined, preserve: boolean): { revision?: number; digest: string } | undefined { |
| 13 | if (!preserve || !state || state.hydrating || state.hydrateError || !state.historyDigest |
| 14 | || sessionIdentityStableKey(target) !== sessionIdentityStableKey(state.meta)) return undefined; |
| 15 | if (target.sessionDigest && target.sessionDigest !== state.historyDigest) return undefined; |
| 16 | if (target.sessionRevision !== undefined && target.sessionRevision > 0 && target.sessionRevision !== state.historyRevision) return undefined; |
| 17 | return { revision: state.historyRevision, digest: state.historyDigest }; |
| 18 | } |
| 19 | |
| 20 | type ActiveTabHydrationTarget = SessionIdentity & { |
| 21 | sessionRevision?: number; |
| 22 | sessionDigest?: string; |
| 23 | }; |
| 24 | |
| 25 | export type ActiveTabHydrationLoadOptions = ActiveTabHydrationTarget & { |
| 26 | preserveCachedHistory: boolean; |
| 27 | surfacePolicy?: HydrateSurfacePolicy; |
| 28 | }; |
| 29 | |
| 30 | export function activeTabHydrationPlan( |
| 31 | target: ActiveTabHydrationTarget, |
| 32 | current: SessionIdentity | undefined, |
| 33 | reset: boolean, |
| 34 | requestedPolicy?: HydrateSurfacePolicy, |
| 35 | requestedCache?: boolean, |
| 36 | ): { |
| 37 | sameSession: boolean; |
| 38 | surfacePolicy: HydrateSurfacePolicy; |
| 39 | loadOptions: ActiveTabHydrationLoadOptions; |
| 40 | } { |
| 41 | const sameSession = sameSessionIdentity(target, current); |
| 42 | const surfacePolicy = requestedPolicy ?? (sameSession ? "preserve-current" : "replace-surface"); |
| 43 | if (surfacePolicy === "replace-surface") { |
| 44 | return { |
| 45 | sameSession, |
| 46 | surfacePolicy, |
| 47 | loadOptions: { |
| 48 | preserveCachedHistory: false, |
| 49 | surfacePolicy, |
| 50 | session: target.session, |
| 51 | sessionPath: target.sessionPath, |
| 52 | sessionRevision: target.sessionRevision, |
| 53 | sessionDigest: target.sessionDigest, |
| 54 | sessionGeneration: target.sessionGeneration, |
| 55 | }, |
| 56 | }; |
| 57 | } |
| 58 | return { |
| 59 | sameSession, |
| 60 | surfacePolicy, |
| 61 | loadOptions: { |
| 62 | preserveCachedHistory: sameSession && (requestedCache ?? !reset), |
| 63 | session: target.session, |
| 64 | sessionPath: target.sessionPath, |
| 65 | sessionRevision: target.sessionRevision, |
| 66 | sessionDigest: target.sessionDigest, |
| 67 | }, |
| 68 | }; |
| 69 | } |
| 70 | |
| 71 | export function continueColdHistory( |
| 72 | pending: Promise<"cached" | "loaded" | "miss" | "failed">, |
| 73 | current: () => boolean, |
| 74 | fallback: () => void | Promise<void>, |
| 75 | follow: () => Promise<unknown>, |
| 76 | ready: () => void | Promise<void>, |
| 77 | ): void { |
| 78 | void pending.then(result => { |
| 79 | if (!current()) return; |
| 80 | if (result === "miss" || result === "failed") return fallback(); |
| 81 | void follow().then(() => current() ? ready() : undefined).catch(() => {}); |
| 82 | }).catch(() => {}); |
| 83 | } |
| 84 |