File size: 2,920 Bytes
2fe29a7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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; } };
}