diff --git a/src/state/queries/notifications/unread.tsx b/src/state/queries/notifications/unread.tsx index bf7505f..3e1d08f 100644 --- a/src/state/queries/notifications/unread.tsx +++ b/src/state/queries/notifications/unread.tsx @@ -15,6 +15,9 @@ import {useQueryClient} from '@tanstack/react-query' import {EventEmitter} from 'eventemitter3' import BroadcastChannel from '#/lib/broadcast' +import {constellation} from '#/lib/microcosm' +import {MICROCOSM_ENABLED} from '#/lib/microcosm/config' +import {eventActorDid, subscribe} from '#/lib/microcosm/spacedust' import {resetBadgeCount} from '#/lib/notifications/notifications' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {truncateAndInvalidate} from '#/state/queries/util' @@ -83,7 +86,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { if (!hasSession || !checkUnreadRef.current) { return } - checkUnreadRef.current() // fire on init + void checkUnreadRef.current() // fire on init const interval = setInterval( () => checkUnreadRef.current?.({isPoll: true}), UPDATE_INTERVAL, @@ -91,6 +94,43 @@ export function Provider({children}: React.PropsWithChildren<{}>) { return () => clearInterval(interval) }, [hasSession]) + // Real-time refresh: when microcosm's Spacedust firehose reports an + // interaction targeting us, refresh the unread count immediately (debounced) + // instead of waiting for the next poll. The existing checkUnread path still + // computes the actual (hydrated, moderated) count — Spacedust is just the + // trigger. + const viewerDid = agent.did + useEffect(() => { + if (!MICROCOSM_ENABLED || !hasSession || !viewerDid) return + let timer: ReturnType | undefined + const sub = subscribe( + { + wantedSources: [ + constellation.Sources.likes, + constellation.Sources.reposts, + constellation.Sources.replies, + constellation.Sources.followers, + ], + wantedSubjectDids: [viewerDid], + }, + { + onEvent(e) { + if (e.link?.operation !== 'create') return + if (eventActorDid(e) === viewerDid) return // ignore self + // Debounce bursts into a single refresh. + if (timer) clearTimeout(timer) + timer = setTimeout(() => { + void checkUnreadRef.current?.({isPoll: true}) + }, 1500) + }, + }, + ) + return () => { + if (timer) clearTimeout(timer) + sub.close() + } + }, [hasSession, viewerDid]) + // listen for broadcasts useEffect(() => { const listener = ({data}: MessageEvent) => {