aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/features/hats.js43
1 files changed, 43 insertions, 0 deletions
diff --git a/src/features/hats.js b/src/features/hats.js
index a14fc2b..932f908 100644
--- a/src/features/hats.js
+++ b/src/features/hats.js
@@ -222,9 +222,52 @@ export function hatsFeature(ctx) {
}
}
+ // ---- the whole list ------------------------------------------------------
+ // Only a couple of dozen heads wear a hat, so the registrar hands over all
+ // of them (/hats/worn) and every avatar is answered from that, even one
+ // seen for the first time; per-head lookups were 400 ms + a round trip
+ // after the page had painted. Public data, so it's kept per device, not per
+ // account. Until it has arrived once, the per-head path above still runs.
+ const ALL_KEY = (net) => 'hat-worn-all:' + net;
+ const allWorn = {}; // net -> { hats: {pk: id}, t }
+ const readAll = (net) => {
+ if (!(net in allWorn)) {
+ try { allWorn[net] = JSON.parse(localStorage.getItem(ALL_KEY(net)) || 'null'); } catch { allWorn[net] = null; }
+ }
+ return allWorn[net];
+ };
+ const allBusy = {}, allFailedAt = {};
+ async function fetchAll(net) {
+ if (allBusy[net] || Date.now() - (allFailedAt[net] || 0) < 60_000) return;
+ allBusy[net] = true;
+ try {
+ const r = await fetch(`${REGISTRAR}/hats/worn?net=${net}`);
+ if (!r.ok) throw new Error('hats list failed');
+ const j = await r.json();
+ const hats = j.hats || {};
+ const prev = readAll(net);
+ allWorn[net] = { hats, t: Date.now() };
+ try { localStorage.setItem(ALL_KEY(net), JSON.stringify(allWorn[net])); } catch {}
+ if (!prev || JSON.stringify(prev.hats) !== JSON.stringify(hats)) render();
+ } catch {
+ allFailedAt[net] = Date.now();
+ } finally {
+ allBusy[net] = false;
+ }
+ }
+ const fromAll = (pk) => {
+ const net = hatNet();
+ const all = readAll(net);
+ if (!all || Date.now() - all.t > REVALIDATE) fetchAll(net);
+ return all ? { hat: all.hats[pk] || null } : null;
+ };
+ setTimeout(() => { try { fromAll(''); } catch {} }, 0); // warm before the first avatar
+
// Stale-while-revalidate: answer from cache immediately, refresh behind.
function hatFor(pk) {
if (!pk || !/^[0-9a-f]{64}$/.test(pk)) return null;
+ const known = fromAll(pk);
+ if (known) return known.hat;
loadStore();
const v = worn.get(hatNet() + ':' + pk);
if (!v || Date.now() - v.t > REVALIDATE) queueFetch(pk);