diff options
Diffstat (limited to 'src/state/queries')
-rw-r--r-- | src/state/queries/notifications/feed.ts | 51 | ||||
-rw-r--r-- | src/state/queries/notifications/types.ts | 5 | ||||
-rw-r--r-- | src/state/queries/notifications/unread.tsx | 17 | ||||
-rw-r--r-- | src/state/queries/notifications/util.ts | 3 |
4 files changed, 53 insertions, 23 deletions
diff --git a/src/state/queries/notifications/feed.ts b/src/state/queries/notifications/feed.ts index a74670b5b..b6aa3d753 100644 --- a/src/state/queries/notifications/feed.ts +++ b/src/state/queries/notifications/feed.ts @@ -16,7 +16,7 @@ * 3. Don't call this query's `refetch()` if you're trying to sync latest; call `checkUnread()` instead. */ -import {useEffect} from 'react' +import {useEffect, useRef} from 'react' import {AppBskyFeedDefs} from '@atproto/api' import { useInfiniteQuery, @@ -49,6 +49,8 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) { const threadMutes = useMutedThreads() const unreads = useUnreadNotificationsApi() const enabled = opts?.enabled !== false + // state tracked across page fetches + const pageState = useRef({pageNum: 0, hasMarkedRead: false}) const query = useInfiniteQuery< FeedPage, @@ -60,17 +62,44 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) { staleTime: STALE.INFINITY, queryKey: RQKEY(), async queryFn({pageParam}: {pageParam: RQPageParam}) { - let page = await fetchPage({ - limit: PAGE_SIZE, - cursor: pageParam, - queryClient, - moderationOpts, - threadMutes, - }) + let page + if (!pageParam) { + // for the first page, we check the cached page held by the unread-checker first + page = unreads.getCachedUnreadPage() + // reset the page state + pageState.current = {pageNum: 0, hasMarkedRead: false} + } + if (!page) { + page = await fetchPage({ + limit: PAGE_SIZE, + cursor: pageParam, + queryClient, + moderationOpts, + threadMutes, + }) + } - // if the first page has an unread, mark all read - if (!pageParam && page.items[0] && !page.items[0].notification.isRead) { - unreads.markAllRead() + // NOTE + // this section checks to see if we need to mark notifs read + // we want to wait until we've seen a read notification because + // of a timing challenge; marking read on the first page would + // cause subsequent pages of unread notifs to incorrectly come + // back as "read". we use page 6 as an abort condition, which means + // after ~180 notifs we give up on tracking unread state correctly + // -prf + if (!pageState.current.hasMarkedRead) { + let hasMarkedRead = false + if ( + pageState.current.pageNum > 5 || + page.items.some(item => item.notification.isRead) + ) { + unreads.markAllRead() + hasMarkedRead = true + } + pageState.current = { + pageNum: pageState.current.pageNum + 1, + hasMarkedRead, + } } return page diff --git a/src/state/queries/notifications/types.ts b/src/state/queries/notifications/types.ts index 0e88f1071..b52341115 100644 --- a/src/state/queries/notifications/types.ts +++ b/src/state/queries/notifications/types.ts @@ -28,7 +28,10 @@ export interface FeedPage { } export interface CachedFeedPage { - sessDid: string // used to invalidate on session changes + /** + * if true, the cached page is recent enough to use as the response + */ + usableInFeed: boolean syncedAt: Date data: FeedPage | undefined } diff --git a/src/state/queries/notifications/unread.tsx b/src/state/queries/notifications/unread.tsx index 6c130aaea..ba38463f2 100644 --- a/src/state/queries/notifications/unread.tsx +++ b/src/state/queries/notifications/unread.tsx @@ -37,7 +37,7 @@ const apiContext = React.createContext<ApiContext>({ }) export function Provider({children}: React.PropsWithChildren<{}>) { - const {hasSession, currentAccount} = useSession() + const {hasSession} = useSession() const queryClient = useQueryClient() const moderationOpts = useModerationOpts() const threadMutes = useMutedThreads() @@ -46,7 +46,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { const checkUnreadRef = React.useRef<ApiContext['checkUnread'] | null>(null) const cacheRef = React.useRef<CachedFeedPage>({ - sessDid: currentAccount?.did || '', + usableInFeed: false, syncedAt: new Date(), data: undefined, }) @@ -65,7 +65,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { React.useEffect(() => { const listener = ({data}: MessageEvent) => { cacheRef.current = { - sessDid: currentAccount?.did || '', + usableInFeed: false, syncedAt: new Date(), data: undefined, } @@ -75,7 +75,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { return () => { broadcast.removeEventListener('message', listener) } - }, [setNumUnread, currentAccount]) + }, [setNumUnread]) // create API const api = React.useMemo<ApiContext>(() => { @@ -119,7 +119,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) { const lastIndexed = page.items[0] && new Date(page.items[0].notification.indexedAt) cacheRef.current = { - sessDid: currentAccount?.did || '', + usableInFeed: !!invalidate, // will be used immediately data: page, syncedAt: !lastIndexed || now > lastIndexed ? now : lastIndexed, } @@ -136,14 +136,13 @@ export function Provider({children}: React.PropsWithChildren<{}>) { }, getCachedUnreadPage() { - // return cached page if was for the current user - // (protects against session changes serving data from the past session) - if (cacheRef.current.sessDid === currentAccount?.did) { + // return cached page if it's marked as fresh enough + if (cacheRef.current.usableInFeed) { return cacheRef.current.data } }, } - }, [setNumUnread, queryClient, moderationOpts, threadMutes, currentAccount]) + }, [setNumUnread, queryClient, moderationOpts, threadMutes]) checkUnreadRef.current = api.checkUnread return ( diff --git a/src/state/queries/notifications/util.ts b/src/state/queries/notifications/util.ts index b8f320473..48e1b8dd8 100644 --- a/src/state/queries/notifications/util.ts +++ b/src/state/queries/notifications/util.ts @@ -119,8 +119,7 @@ function groupNotifications( Math.abs(ts2 - ts) < MS_2DAY && notif.reason === groupedNotif.notification.reason && notif.reasonSubject === groupedNotif.notification.reasonSubject && - notif.author.did !== groupedNotif.notification.author.did && - notif.isRead === groupedNotif.notification.isRead + notif.author.did !== groupedNotif.notification.author.did ) { groupedNotif.additional = groupedNotif.additional || [] groupedNotif.additional.push(notif) |