| import { createSession, validateSession } from "./domain.js"; |
|
|
| export const STORAGE_KEY = "branchjam:session:v1"; |
|
|
| const canonicalValue = (value) => { |
| if (Array.isArray(value)) return value.map(canonicalValue); |
| if (!value || typeof value !== "object") return value; |
| return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalValue(value[key])])); |
| }; |
|
|
| export function serializeSession(state) { |
| const ordered = { |
| schemaVersion: state.schemaVersion, |
| catalogVersion: state.catalogVersion, |
| phase: state.phase, |
| seedNodeId: state.seedNodeId, |
| activeNodeId: state.activeNodeId, |
| inspectedNodeId: state.inspectedNodeId, |
| selectedConstraintId: state.selectedConstraintId, |
| currentRoundId: state.currentRoundId, |
| compareSelection: state.compareSelection, |
| nodesById: canonicalValue(state.nodesById), |
| roundsById: canonicalValue(state.roundsById) |
| }; |
| return JSON.stringify(ordered); |
| } |
|
|
| export function createPersistence(storage, source) { |
| let memory = null; |
| let available = true; |
| const save = (state) => { |
| const serialized = serializeSession(state); |
| memory = serialized; |
| try { |
| storage.setItem(STORAGE_KEY, serialized); |
| return { ok: true, available, notice: "Local session saved." }; |
| } catch { |
| available = false; |
| return { ok: false, available, notice: "Storage could not save this session; play continues in memory only." }; |
| } |
| }; |
| const load = () => { |
| let raw = memory; |
| try { raw = storage.getItem(STORAGE_KEY) ?? raw; } |
| catch { available = false; } |
| if (!raw) return { state: createSession(source), recovered: false, available, notice: available ? null : "Storage is unavailable; this session continues in memory only." }; |
| try { |
| const state = JSON.parse(raw); |
| if (!validateSession(state, source)) throw new Error("invalid"); |
| return { state, recovered: false, available, notice: available ? null : "Storage is unavailable; the in-memory session was restored for this page only." }; |
| } catch { |
| memory = null; |
| try { storage.removeItem(STORAGE_KEY); } |
| catch { available = false; } |
| return { |
| state: createSession(source), recovered: true, available, |
| notice: available |
| ? "Saved session was invalid or incompatible and was removed. A fresh local session is ready." |
| : "Saved session was invalid or incompatible. A fresh in-memory session is ready; storage cleanup was unavailable." |
| }; |
| } |
| }; |
| const reset = () => { |
| memory = null; |
| try { storage.removeItem(STORAGE_KEY); } |
| catch { available = false; } |
| return { |
| state: createSession(source), available, |
| notice: available |
| ? "Local session reset to the seed." |
| : "Session reset to the seed in memory; storage cleanup was unavailable." |
| }; |
| }; |
| return { save, load, reset, get available() { return available; } }; |
| } |
|
|