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:
+61
-21
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user