fix: cache webmention API responses in sessionStorage to persist across refreshes

The old sessionStorage rate limiter prevented re-fetching on page refresh,
causing webmentions to disappear since they weren't in the build-time HTML.
Now caches the actual API response data with a 5-minute TTL so webmentions
render instantly from cache on refresh, while still fetching fresh data in
the background.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ricardo
2026-02-05 10:39:47 +01:00
parent f58198c021
commit a8b44329d8
+61 -21
View File
@@ -13,11 +13,6 @@
if (!target || !domain) return; if (!target || !domain) return;
// Rate limit: only fetch once per page load
const cacheKey = `wm-fetched-${target}`;
if (sessionStorage.getItem(cacheKey)) return;
sessionStorage.setItem(cacheKey, '1');
// Use server-side proxy to keep webmention.io token secure // Use server-side proxy to keep webmention.io token secure
// Fetch both with and without trailing slash since webmention.io // Fetch both with and without trailing slash since webmention.io
// stores targets inconsistently (Bridgy sends different formats) // stores targets inconsistently (Bridgy sends different formats)
@@ -29,33 +24,47 @@
// Check if build-time webmentions section exists // Check if build-time webmentions section exists
const hasBuildTimeSection = document.getElementById('webmentions') !== null; const hasBuildTimeSection = document.getElementById('webmentions') !== null;
Promise.all([ // Cache API responses in sessionStorage (5 min TTL) so webmentions
fetch(apiUrl1).then((res) => res.json()).catch(() => ({ children: [] })), // persist across page refreshes without re-fetching every time
fetch(apiUrl2).then((res) => res.json()).catch(() => ({ children: [] })), const cacheKey = `wm-data-${target}`;
]) const cacheTTL = 5 * 60 * 1000; // 5 minutes
.then(([data1, data2]) => {
// Merge and deduplicate by wm-id function getCachedData() {
const seen = new Set(); try {
const allChildren = []; const cached = sessionStorage.getItem(cacheKey);
for (const wm of [...(data1.children || []), ...(data2.children || [])]) { if (!cached) return null;
if (!seen.has(wm['wm-id'])) { const parsed = JSON.parse(cached);
seen.add(wm['wm-id']); if (Date.now() - parsed.ts > cacheTTL) {
allChildren.push(wm); sessionStorage.removeItem(cacheKey);
return null;
}
return parsed.children;
} catch {
return null;
} }
} }
const data = { children: allChildren };
if (!data.children || !data.children.length) return; function setCachedData(children) {
try {
sessionStorage.setItem(cacheKey, JSON.stringify({ ts: Date.now(), children: children }));
} catch {
// sessionStorage full or unavailable - no problem
}
}
function processWebmentions(allChildren) {
if (!allChildren || !allChildren.length) return;
let mentionsToShow; let mentionsToShow;
if (hasBuildTimeSection) { if (hasBuildTimeSection) {
// Build-time section exists - only show NEW webmentions to avoid duplicates // Build-time section exists - only show NEW webmentions to avoid duplicates
mentionsToShow = data.children.filter((wm) => { mentionsToShow = allChildren.filter((wm) => {
const wmTime = new Date(wm['wm-received']).getTime(); const wmTime = new Date(wm['wm-received']).getTime();
return wmTime > buildTime; return wmTime > buildTime;
}); });
} else { } else {
// No build-time section - show ALL webmentions from API // No build-time section - show ALL webmentions from API
mentionsToShow = data.children; mentionsToShow = allChildren;
} }
if (!mentionsToShow.length) return; if (!mentionsToShow.length) return;
@@ -92,6 +101,37 @@
// Update total count in main header // Update total count in main header
updateTotalCount(mentionsToShow.length); updateTotalCount(mentionsToShow.length);
}
// Try cached data first (renders instantly on refresh)
const cached = getCachedData();
if (cached) {
processWebmentions(cached);
}
// Always fetch fresh data (updates cache for next refresh)
Promise.all([
fetch(apiUrl1).then((res) => res.json()).catch(() => ({ children: [] })),
fetch(apiUrl2).then((res) => res.json()).catch(() => ({ children: [] })),
])
.then(([data1, data2]) => {
// Merge and deduplicate by wm-id
const seen = new Set();
const allChildren = [];
for (const wm of [...(data1.children || []), ...(data2.children || [])]) {
if (!seen.has(wm['wm-id'])) {
seen.add(wm['wm-id']);
allChildren.push(wm);
}
}
// Cache the merged results
setCachedData(allChildren);
// Only render if we didn't already render from cache
if (!cached) {
processWebmentions(allChildren);
}
}) })
.catch((err) => { .catch((err) => {
console.debug('[Webmentions] Error fetching:', err.message); console.debug('[Webmentions] Error fetching:', err.message);