1/**
2 * Browser-side durable session helpers for Impeccable live mode.
3 *
4 * Kept separate from live-browser.js so recovery state can be tested without
5 * booting the full overlay UI. Served before live-browser.js and attached to
6 * window.__IMPECCABLE_LIVE_SESSION__.
7 */
8(function (root) {
9 'use strict';
10
11 function createLiveBrowserSessionState({ prefix, storage, idFactory }) {
12 if (!prefix) throw new Error('prefix required');
13 const store = storage || root.localStorage;
14 const makeId = idFactory || function () { return Math.random().toString(16).slice(2, 10); };
15 const sessionKey = prefix + '-session';
16 const handledKey = sessionKey + '-handled';
17 const scrollKey = sessionKey + '-scroll';
18 let checkpointRevision = 0;
19 const owner = makeId();
20
21 function safeRead(key) {
22 try { return store.getItem(key); } catch { return null; }
23 }
24
25 function safeWrite(key, value) {
26 try { store.setItem(key, value); } catch { /* quota exceeded or private mode */ }
27 }
28
29 function safeRemove(key) {
30 try { store.removeItem(key); } catch { /* unavailable storage */ }
31 }
32
33 function loadSession() {
34 try {
35 const raw = safeRead(sessionKey);
36 if (!raw) return null;
37 const parsed = JSON.parse(raw);
38 if (Number.isInteger(parsed.checkpointRevision)) {
39 checkpointRevision = Math.max(checkpointRevision, parsed.checkpointRevision);
40 }
41 return parsed;
42 } catch { return null; }
43 }
44
45 function saveSession(session) {
46 if (!session || !session.id) return;
47 const payload = {
48 ...session,
49 checkpointRevision,
50 };
51 safeWrite(sessionKey, JSON.stringify(payload));
52 }
53
54 function clearSession() {
55 safeRemove(sessionKey);
56 }
57
58 function nextCheckpointRevision() {
59 checkpointRevision += 1;
60 const existing = loadSession();
61 if (existing?.id) saveSession(existing);
62 return checkpointRevision;
63 }
64
65 function seedCheckpointRevision(value) {
66 if (Number.isInteger(value)) checkpointRevision = Math.max(checkpointRevision, value);
67 return checkpointRevision;
68 }
69
70 function currentCheckpointRevision() {
71 return checkpointRevision;
72 }
73
74 function markHandled(id) {
75 if (!id) return;
76 safeWrite(handledKey, id);
77 }
78
79 function isHandled(id) {
80 return !!id && safeRead(handledKey) === id;
81 }
82
83 function clearHandled() {
84 safeRemove(handledKey);
85 }
86
87 function writeScrollY(y) {
88 safeWrite(scrollKey, String(y));
89 }
90
91 function readScrollY() {
92 const raw = safeRead(scrollKey);
93 if (raw == null) return null;
94 const n = parseFloat(raw);
95 return isFinite(n) ? n : null;
96 }
97
98 function clearScrollY() {
99 safeRemove(scrollKey);
100 }
101
102 return {
103 owner,
104 sessionKey,
105 handledKey,
106 scrollKey,
107 saveSession,
108 loadSession,
109 clearSession,
110 nextCheckpointRevision,
111 seedCheckpointRevision,
112 currentCheckpointRevision,
113 markHandled,
114 isHandled,
115 clearHandled,
116 writeScrollY,
117 readScrollY,
118 clearScrollY,
119 };
120 }
121
122 root.__IMPECCABLE_LIVE_SESSION__ = { createLiveBrowserSessionState };
123})(typeof window !== 'undefined' ? window : globalThis);