fix storage of routes
Sonarqube Scanner / Build and analyze (push) Successful in 1m23s

This commit is contained in:
Florian Zumpe
2026-09-01 22:32:20 +02:00
parent 0a9b19951a
commit 69bf3cb91e
+57 -30
View File
@@ -19,6 +19,16 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
return Date.now(); 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) { function readJson(key, fallback) {
const store = storage(); const store = storage();
if (!store) return fallback; if (!store) return fallback;
@@ -61,7 +71,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
return { return {
progressStorage: value?.progressStorage === true, progressStorage: value?.progressStorage === true,
cookies: false, cookies: false,
updatedAt: Number(value?.updatedAt) || now() updatedAt: parseTimestamp(value?.updatedAt, now())
}; };
} }
@@ -101,24 +111,23 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
} }
function isRouteStateObject(value) { function isRouteStateObject(value) {
return value && typeof value === 'object' && ( return Boolean(value && typeof value === 'object' && (
Object.hasOwn(value, 'routeId') Object.hasOwn(value, 'routeId')
|| Object.hasOwn(value, 'routeState') || Object.hasOwn(value, 'routeState')
|| Object.hasOwn(value, 'routeProgressIndex') || Object.hasOwn(value, 'routeProgressIndex')
|| Object.hasOwn(value, 'visitedPoiIds') || Object.hasOwn(value, 'visitedPoiIds')
|| Object.hasOwn(value, 'viewedPoiIds') || Object.hasOwn(value, 'viewedPoiIds')
|| Object.hasOwn(value, 'playedAudioPoiIds') || Object.hasOwn(value, 'playedAudioPoiIds')
); ));
} }
function progressEnvelope(value) { function progressEnvelope(value) {
if (!value || typeof value !== 'object') return {}; if (!value || typeof value !== 'object') return {};
if (value.routes && typeof value.routes === 'object') return value.routes; if (value.routes && typeof value.routes === 'object') return value.routes;
if (isRouteStateObject(value)) { if (!isRouteStateObject(value)) return value;
const id = routeId(value);
return id ? { [id]: value } : {}; const id = routeId(value);
} return id ? { [id]: value } : {};
return value;
} }
function readProgressStore() { function readProgressStore() {
@@ -133,8 +142,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
}); });
} }
function stateTimestamp(value) { function normalizeRouteTimestamp(value) {
return Number(value?.updatedAt) || Number(value?.savedAt) || now(); return parseTimestamp(value?.updatedAt, parseTimestamp(value?.savedAt, 0));
} }
function normalizeRouteState(value, fallbackRouteId = null) { function normalizeRouteState(value, fallbackRouteId = null) {
@@ -143,6 +152,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
const id = value.routeId === null || value.routeId === undefined const id = value.routeId === null || value.routeId === undefined
? fallbackRouteId ? fallbackRouteId
: value.routeId; : value.routeId;
const timestamp = normalizeRouteTimestamp(value) || now();
return { return {
routeId: id === null || id === undefined ? null : String(id), routeId: id === null || id === undefined ? null : String(id),
@@ -152,8 +162,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
visitedPoiIds: numericIdSet(value.visitedPoiIds), visitedPoiIds: numericIdSet(value.visitedPoiIds),
viewedPoiIds: numericIdSet(value.viewedPoiIds), viewedPoiIds: numericIdSet(value.viewedPoiIds),
playedAudioPoiIds: numericIdSet(value.playedAudioPoiIds), playedAudioPoiIds: numericIdSet(value.playedAudioPoiIds),
savedAt: Number(value.savedAt) || Number(value.updatedAt) || now(), savedAt: parseTimestamp(value.savedAt, timestamp),
updatedAt: stateTimestamp(value) updatedAt: timestamp
}; };
} }
@@ -178,13 +188,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
} }
function isExpired(value, referenceTime = now()) { function isExpired(value, referenceTime = now()) {
const timestamp = Number(value?.updatedAt) || Number(value?.savedAt) || now(); const timestamp = normalizeRouteTimestamp(value);
return referenceTime - timestamp > MAX_PROGRESS_AGE_MS; return timestamp > 0 && referenceTime - timestamp > MAX_PROGRESS_AGE_MS;
}
function reportCleanupProgress(callback, processed, total, removed) {
if (typeof callback !== 'function') return;
callback({ processed, total, removed });
} }
function updateRouteState(route, updater) { function updateRouteState(route, updater) {
@@ -246,37 +251,58 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
return writeProgressStore(routes); 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) { function cleanupExpired(progressCallback) {
const referenceTime = now(); const referenceTime = now();
const routes = readProgressStore(); const entries = normalizedEntries(readProgressStore());
const entries = Object.entries(routes); const total = entries.filter(([, value]) => isExpired(value, referenceTime)).length;
const kept = {}; const kept = {};
let removed = 0; let removed = 0;
entries.forEach(([id, value], index) => { entries.forEach(([id, value]) => {
const normalized = normalizeRouteState(value, id); reportCleanupProgress(progressCallback, removed, total);
if (normalized && !isExpired(normalized, referenceTime)) kept[id] = normalized; if (isExpired(value, referenceTime)) {
else removed += 1; removed += 1;
reportCleanupProgress(progressCallback, index + 1, entries.length, removed); return;
}
kept[id] = value;
}); });
if (removed > 0) writeProgressStore(kept); if (removed > 0) writeProgressStore(kept);
return { removed, total: entries.length, routes: kept }; return { removed, total, routes: kept };
} }
async function cleanupExpiredProgress(progressCallback) { async function cleanupExpiredProgress(progressCallback) {
return cleanupExpired(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) { function activateRoute(route) {
if (!isProgressStorageEnabled()) return null; if (!isProgressStorageEnabled()) return null;
const id = routeId(route); const id = routeId(route);
if (!id) return null; if (!id) return null;
const routes = cleanupExpired().routes; return loadRoute(route);
const saved = normalizeRouteState(routes[id], id);
return saved && !isExpired(saved) ? saved : null;
} }
ns.PrivacyStorage = Object.freeze({ ns.PrivacyStorage = Object.freeze({
@@ -290,6 +316,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
cookiesEnabled: () => false, cookiesEnabled: () => false,
getPrivacySettings: readSettings, getPrivacySettings: readSettings,
isProgressStorageEnabled, isProgressStorageEnabled,
loadRoute,
markAudioPlayed, markAudioPlayed,
markPlayed: markAudioPlayed, markPlayed: markAudioPlayed,
markPlayedAudio: markAudioPlayed, markPlayedAudio: markAudioPlayed,