From 69bf3cb91ed3f159d8cbab8b5b59b82c0e3ceaf5 Mon Sep 17 00:00:00 2001 From: Florian Zumpe Date: Tue, 1 Sep 2026 22:32:20 +0200 Subject: [PATCH] fix storage of routes --- public/js/privacy-storage.js | 87 +++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 30 deletions(-) diff --git a/public/js/privacy-storage.js b/public/js/privacy-storage.js index aacad36..285901d 100644 --- a/public/js/privacy-storage.js +++ b/public/js/privacy-storage.js @@ -19,6 +19,16 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {}; return Date.now(); } + function parseTimestamp(value, fallback = 0) { + if (value === null || value === undefined || value === '') return fallback; + + const numeric = Number(value); + if (Number.isFinite(numeric) && numeric > 0) return numeric; + + const parsed = Date.parse(String(value)); + return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; + } + function readJson(key, fallback) { const store = storage(); if (!store) return fallback; @@ -61,7 +71,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {}; return { progressStorage: value?.progressStorage === true, cookies: false, - updatedAt: Number(value?.updatedAt) || now() + updatedAt: parseTimestamp(value?.updatedAt, now()) }; } @@ -101,24 +111,23 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {}; } function isRouteStateObject(value) { - return value && typeof value === 'object' && ( + return Boolean(value && typeof value === 'object' && ( Object.hasOwn(value, 'routeId') || Object.hasOwn(value, 'routeState') || Object.hasOwn(value, 'routeProgressIndex') || Object.hasOwn(value, 'visitedPoiIds') || Object.hasOwn(value, 'viewedPoiIds') || Object.hasOwn(value, 'playedAudioPoiIds') - ); + )); } function progressEnvelope(value) { if (!value || typeof value !== 'object') return {}; if (value.routes && typeof value.routes === 'object') return value.routes; - if (isRouteStateObject(value)) { - const id = routeId(value); - return id ? { [id]: value } : {}; - } - return value; + if (!isRouteStateObject(value)) return value; + + const id = routeId(value); + return id ? { [id]: value } : {}; } function readProgressStore() { @@ -133,8 +142,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {}; }); } - function stateTimestamp(value) { - return Number(value?.updatedAt) || Number(value?.savedAt) || now(); + function normalizeRouteTimestamp(value) { + return parseTimestamp(value?.updatedAt, parseTimestamp(value?.savedAt, 0)); } function normalizeRouteState(value, fallbackRouteId = null) { @@ -143,6 +152,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {}; const id = value.routeId === null || value.routeId === undefined ? fallbackRouteId : value.routeId; + const timestamp = normalizeRouteTimestamp(value) || now(); return { routeId: id === null || id === undefined ? null : String(id), @@ -152,8 +162,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {}; visitedPoiIds: numericIdSet(value.visitedPoiIds), viewedPoiIds: numericIdSet(value.viewedPoiIds), playedAudioPoiIds: numericIdSet(value.playedAudioPoiIds), - savedAt: Number(value.savedAt) || Number(value.updatedAt) || now(), - updatedAt: stateTimestamp(value) + savedAt: parseTimestamp(value.savedAt, timestamp), + updatedAt: timestamp }; } @@ -178,13 +188,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {}; } function isExpired(value, referenceTime = now()) { - const timestamp = Number(value?.updatedAt) || Number(value?.savedAt) || now(); - return referenceTime - timestamp > MAX_PROGRESS_AGE_MS; - } - - function reportCleanupProgress(callback, processed, total, removed) { - if (typeof callback !== 'function') return; - callback({ processed, total, removed }); + const timestamp = normalizeRouteTimestamp(value); + return timestamp > 0 && referenceTime - timestamp > MAX_PROGRESS_AGE_MS; } function updateRouteState(route, updater) { @@ -246,37 +251,58 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {}; return writeProgressStore(routes); } + function normalizedEntries(routes) { + return Object.entries(routes) + .map(([id, value]) => [id, normalizeRouteState(value, id)]) + .filter(([, value]) => value !== null); + } + + function reportCleanupProgress(callback, removed, total) { + if (typeof callback !== 'function') return; + callback(removed, total); + } + function cleanupExpired(progressCallback) { const referenceTime = now(); - const routes = readProgressStore(); - const entries = Object.entries(routes); + const entries = normalizedEntries(readProgressStore()); + const total = entries.filter(([, value]) => isExpired(value, referenceTime)).length; const kept = {}; let removed = 0; - entries.forEach(([id, value], index) => { - const normalized = normalizeRouteState(value, id); - if (normalized && !isExpired(normalized, referenceTime)) kept[id] = normalized; - else removed += 1; - reportCleanupProgress(progressCallback, index + 1, entries.length, removed); + entries.forEach(([id, value]) => { + reportCleanupProgress(progressCallback, removed, total); + if (isExpired(value, referenceTime)) { + removed += 1; + return; + } + kept[id] = value; }); if (removed > 0) writeProgressStore(kept); - return { removed, total: entries.length, routes: kept }; + return { removed, total, routes: kept }; } async function cleanupExpiredProgress(progressCallback) { return cleanupExpired(progressCallback); } + function loadRoute(route) { + if (!isProgressStorageEnabled()) return null; + + const id = routeId(route); + if (!id) return null; + + const saved = normalizeRouteState(readProgressStore()[id], id); + return saved && !isExpired(saved) ? saved : null; + } + function activateRoute(route) { if (!isProgressStorageEnabled()) return null; const id = routeId(route); if (!id) return null; - const routes = cleanupExpired().routes; - const saved = normalizeRouteState(routes[id], id); - return saved && !isExpired(saved) ? saved : null; + return loadRoute(route); } ns.PrivacyStorage = Object.freeze({ @@ -290,6 +316,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {}; cookiesEnabled: () => false, getPrivacySettings: readSettings, isProgressStorageEnabled, + loadRoute, markAudioPlayed, markPlayed: markAudioPlayed, markPlayedAudio: markAudioPlayed,