112 lines
3.9 KiB
JavaScript
112 lines
3.9 KiB
JavaScript
// ── canonical identifiers ────────────────────────────────────────────
|
||
|
||
const CANONICAL_KEY = "confidence-engine-investigation";
|
||
const LEGACY_KEY = "confidence-engine-session";
|
||
const SCHEMA_VERSION = 1;
|
||
|
||
// ── helpers ──────────────────────────────────────────────────────────
|
||
|
||
function safeGet(storage, key) {
|
||
if (!storage || typeof storage.getItem !== "function") return null;
|
||
try { return storage.getItem(key); } catch (_) { return null; }
|
||
}
|
||
|
||
function isPlainObject(value) {
|
||
return (
|
||
value !== null &&
|
||
typeof value === "object" &&
|
||
!Array.isArray(value) &&
|
||
Object.prototype.toString.call(value) === "[object Object]"
|
||
);
|
||
}
|
||
|
||
// ── loadInvestigation ────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Returns the persisted investigation snapshot (normalised to current schema)
|
||
* or null when no usable state exists.
|
||
*/
|
||
export function loadInvestigation() {
|
||
const storage = _getTargetStorage();
|
||
if (!storage) return null;
|
||
|
||
// 1 – canonical key first
|
||
let raw = safeGet(storage, CANONICAL_KEY);
|
||
if (raw !== null) {
|
||
try {
|
||
const parsed = JSON.parse(raw);
|
||
if (isPlainObject(parsed)) {
|
||
if (!("schemaVersion" in parsed)) parsed.schemaVersion = SCHEMA_VERSION;
|
||
return parsed;
|
||
}
|
||
} catch (_) { /* malformed — fall through to legacy */ }
|
||
}
|
||
|
||
// 2 – legacy fallback
|
||
const legacyStorage = _getLegacyStorage();
|
||
if (!legacyStorage) return null;
|
||
|
||
raw = safeGet(legacyStorage, LEGACY_KEY);
|
||
if (raw === null) return null;
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(raw);
|
||
} catch (_) { return null; }
|
||
|
||
if (!isPlainObject(parsed)) return null;
|
||
|
||
// Normalise schemaVersion for legacy data
|
||
if (!("schemaVersion" in parsed)) parsed.schemaVersion = SCHEMA_VERSION;
|
||
|
||
// Migrate into canonical key
|
||
_persist(storage, CANONICAL_KEY, JSON.stringify(parsed));
|
||
|
||
return parsed;
|
||
}
|
||
|
||
// ── saveInvestigation ────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Persists the supplied snapshot to the canonical localStorage key.
|
||
* The caller's object is never mutated.
|
||
*/
|
||
export function saveInvestigation(snapshot) {
|
||
const storage = _getTargetStorage();
|
||
if (!storage) return; // silently no-op in non-browser
|
||
|
||
try {
|
||
const record = JSON.parse(JSON.stringify(snapshot));
|
||
record.schemaVersion = SCHEMA_VERSION;
|
||
_persist(storage, CANONICAL_KEY, JSON.stringify(record));
|
||
} catch (_) { /* storage errors must not crash caller */ }
|
||
}
|
||
|
||
// ── clearInvestigation ───────────────────────────────────────────────
|
||
|
||
/** Removes only the canonical investigation key. */
|
||
export function clearInvestigation() {
|
||
const storage = _getTargetStorage();
|
||
if (!storage) return;
|
||
try { storage.removeItem(CANONICAL_KEY); } catch (_) {}
|
||
}
|
||
|
||
// ── internals ────────────────────────────────────────────────────────
|
||
|
||
function _persist(storage, key, value) {
|
||
try { storage.setItem(key, value); } catch (_) {}
|
||
}
|
||
|
||
function _getTargetStorage() {
|
||
if (typeof globalThis.window === "undefined") return null;
|
||
const s = globalThis.window.localStorage;
|
||
if (!s || typeof s.getItem !== "function") return null;
|
||
return s;
|
||
}
|
||
|
||
function _getLegacyStorage() {
|
||
if (typeof globalThis.window === "undefined") return null;
|
||
const s = globalThis.window.sessionStorage;
|
||
if (!s || typeof s.getItem !== "function") return null;
|
||
return s;
|
||
}
|