diff options
| author | Adam Soltys <[email protected]> | 2026-09-29 07:48:59 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-29 07:48:59 -0700 |
| commit | b9c04f573567d69d94fb120aa8feed89cf75585c (patch) | |
| tree | ff042c6e988c711fa16c605edc4465e1c68aab61 /src | |
| parent | d5598a131a9d160b929ff7ccbd650a4ef57b21c0 (diff) | |
| download | coinosv3-b9c04f573567d69d94fb120aa8feed89cf75585c.tar.gz coinosv3-b9c04f573567d69d94fb120aa8feed89cf75585c.zip | |
Cache notification note content locally
Diffstat (limited to 'src')
| -rw-r--r-- | src/features/messages.js | 73 |
1 files changed, 69 insertions, 4 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index 2cf799b..7505dbf 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -5346,6 +5346,7 @@ export function messagesFeature(ctx) { const evs = await queryOn(relays, { ids: [ref.id] }, 4500).catch(() => []); c.ev = (evs || [])[0] || c.ev || null; c.status = c.ev ? 'ready' : 'missing'; + if (c.ev) cacheNotifNotes([c.ev]); // the open thread quotes it: keep it with the thread for the next reload const th = c.ev && ui.noteThread && threadCache.get(ui.noteThread.rootId); if (th && th.root) persistThread(th); @@ -8983,6 +8984,7 @@ export function messagesFeature(ctx) { const NOTIF_KEEP = 150; let notif = null; // { status, items } let notifAt = 0, notifUnsub = null; + const notifNoteAsking = new Set(); const notifSeen = () => st().notifSeen || 0; // Notifications are about the IDENTITY, not the wallet: the stored list // remembers whose it is, and another identity on the same wallet starts @@ -8993,7 +8995,17 @@ export function messagesFeature(ctx) { const s = st(); const stored = (!s.notifsPk || s.notifsPk === identityPk()) ? (s.notifs || []) : []; notif = { status: stored.length ? 'ready' : 'loading', items: stored }; + // Reply events and the notes reactions/zaps point at ride beside their + // notification rows. Seed the in-memory note caches synchronously, so + // opening Notifications after a reload never paints "Fetching note…". + const noteCache = s.notifNotesPk === identityPk() ? (s.notifNoteCache || {}) : {}; + for (const note of Object.values(noteCache)) { + if (!note || !note.id) continue; + notifNotes.set(note.id, note); + quoted.set(note.id, { status: 'ready', ev: note, at: Date.now() }); + } warmNotifFaces(stored); + warmNotifNotes(stored); refreshNotifs(); } return notif; @@ -9011,6 +9023,58 @@ export function messagesFeature(ctx) { const deadline = Date.now() + READY_MS; for (const pk of pks) warmAvatar(pk, deadline).catch(() => {}); } + // Keep only what rendering and opening a thread need. Signatures and relay + // metadata make this cache unnecessarily large; the relays remain the + // source of truth whenever the live notification refresh runs. + const savedNotifNote = (ev) => ev && ev.id ? ({ + id: ev.id, pubkey: ev.pubkey, kind: ev.kind, created_at: ev.created_at, + content: ev.content || '', tags: ev.tags || [], + }) : null; + function persistNotifs() { + if (!notif) return; + const wanted = new Set(); + for (const x of notif.items) { + const id = x.what === 'reply' || x.what === 'mention' ? x.id : x.target; + if (id) wanted.add(id); + } + const notes = {}; + for (const id of wanted) if (notifNotes.has(id)) notes[id] = savedNotifNote(notifNotes.get(id)); + const s2 = st(); + s2.notifs = notif.items; s2.notifsPk = identityPk(); + s2.notifNoteCache = notes; s2.notifNotesPk = identityPk(); + save(s2); + } + function cacheNotifNotes(evs) { + if (!notif || !(evs || []).length) return false; + const byId = new Map((evs || []).filter((ev) => ev && ev.id).map((ev) => [ev.id, savedNotifNote(ev)])); + let changed = false; + const wanted = new Set(); + for (const x of notif.items) wanted.add(x.what === 'reply' || x.what === 'mention' ? x.id : x.target); + for (const [id, note] of byId) { + if (!wanted.has(id) || notifNotes.has(id)) continue; + notifNotes.set(id, note); + quoted.set(id, { status: 'ready', ev: note, at: Date.now() }); + changed = true; + } + if (changed) persistNotifs(); + return changed; + } + // Old notification rows did not carry their note. Upgrade them in one + // bounded batch while the bell/chat home is visible, before the user opens + // the page. New rows take the same path as soon as they arrive. + function warmNotifNotes(items) { + const ids = [...new Set((items || []) + .filter((x) => x && x.target && x.what !== 'reply' && x.what !== 'mention' && !notifNotes.has(x.target)) + .map((x) => x.target).filter((id) => !notifNoteAsking.has(id)))].slice(0, NOTIF_KEEP); + if (!ids.length) return; + for (const id of ids) notifNoteAsking.add(id); + (async () => { + const got = []; + for (let i = 0; i < ids.length; i += 60) + got.push(...await queryOn(zapRelays(), { ids: ids.slice(i, i + 60) }, 4500).catch(() => [])); + if (cacheNotifNotes(got)) scheduleRepaint(); + })().finally(() => { for (const id of ids) notifNoteAsking.delete(id); }); + } // What an event says happened, or null if it isn't about you after all. function notifItem(ev) { const my = myPubkeys(); @@ -9057,11 +9121,12 @@ export function messagesFeature(ctx) { kept.push(x); } c.items = kept.slice(0, NOTIF_KEEP); - // the rows already say who; a reply row also wants its reader-facing text, - // which it carries — the events themselves are not kept - const s2 = st(); s2.notifs = c.items; s2.notifsPk = identityPk(); save(s2); - // a reply is a note we can open straight away; keep it in hand + // Keep the small notification rows and one deduplicated copy of each note + // they show. A reply is also a note we can open straight away. for (const ev of evs) if (ev.kind === 1) notifNotes.set(ev.id, ev); + cacheNotifNotes(evs); + warmNotifNotes(c.items); + persistNotifs(); return true; } const notifNotes = new Map(); // reply id -> event, for opening the thread |
