Files
website/src/hooks/useNotifications.ts
2026-09-06 22:03:29 +02:00

82 lines
2.4 KiB
TypeScript

import { useQuery } from '@tanstack/react-query';
import { useNostr } from '@nostrify/react';
import type { NostrEvent } from '@nostrify/nostrify';
import { useCurrentUser } from './useCurrentUser';
import { useLocalStorage } from './useLocalStorage';
/** Nostr event kinds that commonly represent activity directed at a person. */
const NOTIFICATION_KINDS = [1, 3, 6, 7, 16, 9735] as const;
export type NotificationKind = 'mention' | 'reply' | 'reaction' | 'repost' | 'follow' | 'zap';
export interface Notification {
event: NostrEvent;
kind: NotificationKind;
}
function notificationKind(event: NostrEvent): NotificationKind {
switch (event.kind) {
case 3:
return 'follow';
case 7:
return 'reaction';
case 6:
case 16:
return 'repost';
case 9735:
return 'zap';
case 1:
return event.tags.some(([name]) => name === 'e') ? 'reply' : 'mention';
default:
return 'mention';
}
}
/**
* Fetches activity that explicitly tags the current user. Relays index the
* single-letter `p` tag, so this stays a narrow query instead of downloading a
* general timeline and filtering it in the browser.
*/
export function useNotifications() {
const { nostr } = useNostr();
const { user } = useCurrentUser();
return useQuery<Notification[]>({
queryKey: ['nostr', 'notifications', user?.pubkey ?? ''],
enabled: Boolean(user),
queryFn: async ({ signal }) => {
if (!user) return [];
const events = await nostr.query(
[{ kinds: [...NOTIFICATION_KINDS], '#p': [user.pubkey], limit: 100 }],
{
signal: AbortSignal.any([
signal,
AbortSignal.timeout(6000),
]),
},
);
const seen = new Set<string>();
return events
.filter((event) => event.pubkey !== user.pubkey && !seen.has(event.id) && seen.add(event.id))
.sort((left, right) => right.created_at - left.created_at)
.map((event) => ({ event, kind: notificationKind(event) }));
},
staleTime: 30_000,
});
}
/** Persists the most recent timestamp the account has explicitly acknowledged. */
export function useNotificationReadState() {
const { user } = useCurrentUser();
const [lastReadAt, setLastReadAt] = useLocalStorage<number>(
`nostr:notifications:last-read:${user?.pubkey ?? 'anonymous'}`,
0,
);
return {
lastReadAt,
markAllRead: () => setLastReadAt(Math.floor(Date.now() / 1000)),
};
}