This commit is contained in:
@@ -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,25 +111,24 @@ 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);
|
const id = routeId(value);
|
||||||
return id ? { [id]: value } : {};
|
return id ? { [id]: value } : {};
|
||||||
}
|
}
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readProgressStore() {
|
function readProgressStore() {
|
||||||
return progressEnvelope(readJson(PROGRESS_KEY, {}));
|
return progressEnvelope(readJson(PROGRESS_KEY, {}));
|
||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user