Files
kandev/apps/web/src/vite-preload-recovery.ts
T
José Almeida eab70052d2 fix(web): prevent blank screens during SPA recovery (#2024)
* fix(web): prevent blank screens during SPA recovery

* fix(web): address SPA recovery review feedback
2026-07-30 10:46:35 +01:00

92 lines
2.4 KiB
TypeScript

const PRELOAD_ERROR_EVENT = "vite:preloadError";
const PRELOAD_RECOVERY_KEY = "kandev.preloadRecovery";
const PRELOAD_RECOVERY_TTL_MS = 60_000;
type RecoveryStorage = Pick<Storage, "getItem" | "setItem">;
type RecoveryEventTarget = Pick<EventTarget, "addEventListener" | "removeEventListener">;
interface RecoveryMarker {
attemptedAt: number;
href: string;
}
interface VitePreloadRecoveryOptions {
target?: RecoveryEventTarget;
getStorage?: () => RecoveryStorage;
now?: () => number;
getHref?: () => string;
reload?: () => void;
}
function parseRecoveryMarker(value: string | null): RecoveryMarker | null {
if (value === null) {
return null;
}
try {
const marker: unknown = JSON.parse(value);
if (
typeof marker === "object" &&
marker !== null &&
typeof (marker as RecoveryMarker).attemptedAt === "number" &&
Number.isFinite((marker as RecoveryMarker).attemptedAt) &&
typeof (marker as RecoveryMarker).href === "string"
) {
return marker as RecoveryMarker;
}
} catch {
// A corrupt marker is equivalent to no prior recovery attempt.
}
return null;
}
function isRecent(marker: RecoveryMarker | null, now: number): boolean {
return (
marker !== null &&
marker.attemptedAt <= now &&
now - marker.attemptedAt < PRELOAD_RECOVERY_TTL_MS
);
}
export function installVitePreloadRecovery({
target = window,
getStorage = () => window.sessionStorage,
now = Date.now,
getHref = () => window.location.href,
reload = () => window.location.reload(),
}: VitePreloadRecoveryOptions = {}): () => void {
const handlePreloadError: EventListener = (event) => {
let storage: RecoveryStorage;
let storedMarker: string | null;
try {
storage = getStorage();
storedMarker = storage.getItem(PRELOAD_RECOVERY_KEY);
} catch {
return;
}
const attemptedAt = now();
if (isRecent(parseRecoveryMarker(storedMarker), attemptedAt)) {
return;
}
const marker = JSON.stringify({ attemptedAt, href: getHref() } satisfies RecoveryMarker);
try {
storage.setItem(PRELOAD_RECOVERY_KEY, marker);
if (storage.getItem(PRELOAD_RECOVERY_KEY) !== marker) {
return;
}
} catch {
return;
}
event.preventDefault();
reload();
};
target.addEventListener(PRELOAD_ERROR_EVENT, handlePreloadError);
return () => target.removeEventListener(PRELOAD_ERROR_EVENT, handlePreloadError);
}