feat: stale-while-revalidate cache for /listening/ page
Build & Deploy / build-and-deploy (push) Successful in 2m26s
Build & Deploy / build-and-deploy (push) Successful in 2m26s
On page load, read last known data from localStorage and render instantly
(no skeleton for returning visitors). Fresh data fetches run in background
and update the UI reactively when complete.
- apiFetch() writes each successful response to localStorage (key: lp:{path})
- readCache() reads cached response, null on miss or parse error
- listeningPage._loadCaches() + listeningWidget._loadCaches() populate state
from cache on init, setting loading=false immediately if cache exists
- Cached now-playing status always set to null — prevents stale "Now Playing"
green indicator; fresh fetch updates the real status within seconds
- All localStorage access wrapped in try/catch (private mode safe)
This commit is contained in:
+55
-1
@@ -10,7 +10,18 @@ document.addEventListener("alpine:init", () => {
|
||||
async function apiFetch(path) {
|
||||
const r = await fetch(path);
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
return r.json();
|
||||
const data = await r.json();
|
||||
try { localStorage.setItem("lp:" + path, JSON.stringify({ t: Date.now(), d: data })); } catch {}
|
||||
return data;
|
||||
}
|
||||
|
||||
// Read cached API response; returns null on miss or parse error
|
||||
function readCache(path) {
|
||||
try {
|
||||
const s = localStorage.getItem("lp:" + path);
|
||||
if (!s) return null;
|
||||
return JSON.parse(s).d ?? null;
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
function mergeListens(fw = [], lfm = []) {
|
||||
@@ -48,7 +59,20 @@ document.addEventListener("alpine:init", () => {
|
||||
lfmNowPlaying: null,
|
||||
lfmScrobbles: [],
|
||||
|
||||
_loadCaches() {
|
||||
const fwNp = readCache("/funkwhale/api/now-playing");
|
||||
const fwList = readCache("/funkwhale/api/listenings?limit=2");
|
||||
const lfmNp = readCache("/lastfmapi/api/now-playing");
|
||||
const lfmSc = readCache("/lastfmapi/api/scrobbles?limit=2");
|
||||
if (fwNp) this.fwNowPlaying = { ...fwNp, status: null }; // no stale "Now Playing"
|
||||
if (fwList) this.fwListenings = fwList.listenings || [];
|
||||
if (lfmNp) this.lfmNowPlaying = { ...lfmNp, status: null };
|
||||
if (lfmSc) this.lfmScrobbles = lfmSc.scrobbles || [];
|
||||
return !!(fwNp || fwList || lfmNp || lfmSc);
|
||||
},
|
||||
|
||||
async init() {
|
||||
if (this._loadCaches()) this.loading = false;
|
||||
try {
|
||||
const [fwNp, fwList, lfmNp, lfmList] = await Promise.allSettled([
|
||||
apiFetch("/funkwhale/api/now-playing"),
|
||||
@@ -141,7 +165,37 @@ document.addEventListener("alpine:init", () => {
|
||||
lfm: { nowPlaying: null, scrobbles: [], loved: [], stats: null },
|
||||
activeSource: "all",
|
||||
|
||||
_loadCaches() {
|
||||
const fwNp = readCache("/funkwhale/api/now-playing");
|
||||
const fwList = readCache("/funkwhale/api/listenings");
|
||||
const fwFav = readCache("/funkwhale/api/favorites");
|
||||
const fwSt = readCache("/funkwhale/api/stats");
|
||||
const lfmNp = readCache("/lastfmapi/api/now-playing");
|
||||
const lfmSc = readCache("/lastfmapi/api/scrobbles?period=alltime&limit=20");
|
||||
const lfmLv = readCache("/lastfmapi/api/loved?limit=10");
|
||||
const lfmSt = readCache("/lastfmapi/api/stats?period=alltime");
|
||||
|
||||
const hasFw = fwNp || fwList || fwFav || fwSt;
|
||||
const hasLfm = lfmNp || lfmSc || lfmLv || lfmSt;
|
||||
|
||||
if (hasFw) {
|
||||
if (fwNp) this.fw.nowPlaying = { ...fwNp, status: null }; // no stale "Now Playing"
|
||||
if (fwList) this.fw.listenings = fwList.listenings || [];
|
||||
if (fwFav) this.fw.favorites = fwFav.favorites || [];
|
||||
if (fwSt) this.fw.stats = fwSt;
|
||||
this.fwLoading = false;
|
||||
}
|
||||
if (hasLfm) {
|
||||
if (lfmNp) this.lfm.nowPlaying = { ...lfmNp, status: null };
|
||||
if (lfmSc) this.lfm.scrobbles = lfmSc.scrobbles || [];
|
||||
if (lfmLv) this.lfm.loved = lfmLv.loved || [];
|
||||
if (lfmSt) this.lfm.stats = lfmSt;
|
||||
this.lfmLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async init() {
|
||||
this._loadCaches();
|
||||
await Promise.all([this._fetchFunkwhale(), this._fetchLastfm()]);
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user