story-research-zapwall/lib/notifications.ts

174 lines
4.7 KiB
TypeScript

import type { Event } from 'nostr-tools'
import { nostrService } from './nostr'
import { zapVerificationService } from './zapVerification'
import type { Notification } from '@/types/notifications'
import { createSubscription } from '@/types/nostr-tools-extended'
import { getPrimaryRelaySync } from './config'
function createZapReceiptFilters(userPubkey: string) {
return [
{
kinds: [9735], // Zap receipt
'#p': [userPubkey], // Receipts targeting this user
},
]
}
async function buildPaymentNotification(event: Event, userPubkey: string): Promise<Notification | null> {
const paymentInfo = zapVerificationService.extractPaymentInfo(event)
if (paymentInfo?.recipient !== userPubkey) {
return null
}
let articleTitle: string | undefined
if (paymentInfo.articleId) {
try {
const article = await nostrService.getArticleById(paymentInfo.articleId)
articleTitle = article?.title
} catch (e) {
console.error('Error loading article for notification:', e)
}
}
return {
id: event.id,
type: 'payment',
title: 'New Payment Received',
message: articleTitle
? `Vous avez reçu un zap de ${paymentInfo.amount} sats pour "${articleTitle}"`
: `Vous avez reçu un zap de ${paymentInfo.amount} sats`,
timestamp: event.created_at,
read: false,
...(paymentInfo.articleId ? { articleId: paymentInfo.articleId } : {}),
...(articleTitle ? { articleTitle } : {}),
amount: paymentInfo.amount,
fromPubkey: paymentInfo.payer,
}
}
function registerZapSubscription(
sub: import('@/types/nostr-tools-extended').Subscription,
userPubkey: string,
onNotification: (notification: Notification) => void
) {
sub.on('event', (event: Event) => {
void buildPaymentNotification(event, userPubkey)
.then((notification) => {
if (notification) {
onNotification(notification)
}
})
.catch((error) => {
console.error('Error processing zap receipt notification:', error)
})
})
}
/**
* Service for monitoring and managing notifications
*/
export class NotificationService {
private subscriptions: Map<string, () => void> = new Map()
/**
* Subscribe to zap receipts (payments) for a user's articles
*/
subscribeToPayments(
userPubkey: string,
onNotification: (notification: Notification) => void
): () => void {
const pool = nostrService.getPool()
if (!pool) {
return () => {}
}
const filters = createZapReceiptFilters(userPubkey)
const relayUrl = getPrimaryRelaySync()
const sub = createSubscription(pool, [relayUrl], filters)
registerZapSubscription(sub, userPubkey, onNotification)
const unsubscribe = () => {
sub.unsub()
}
return unsubscribe
}
/**
* Stop all subscriptions
*/
stopAll(): void {
this.subscriptions.forEach((unsubscribe) => unsubscribe())
this.subscriptions.clear()
}
}
export const notificationService = new NotificationService()
/**
* Load stored notifications from IndexedDB
*/
export async function loadStoredNotifications(userPubkey: string): Promise<Notification[]> {
try {
const { storageService } = await import('./storage/indexedDB')
const key = `notifications_${userPubkey}`
const stored = await storageService.get<Notification[]>(key, 'notifications_storage')
return stored ?? []
} catch (error) {
console.error('Error loading stored notifications:', error)
return []
}
}
/**
* Save notifications to IndexedDB
*/
export async function saveNotifications(userPubkey: string, notifications: Notification[]): Promise<void> {
try {
const { storageService } = await import('./storage/indexedDB')
const key = `notifications_${userPubkey}`
await storageService.set(key, notifications, 'notifications_storage')
} catch (error) {
console.error('Error saving notifications:', error)
}
}
/**
* Mark notification as read
*/
export function markNotificationAsRead(
userPubkey: string,
notificationId: string,
notifications: Notification[]
): Notification[] {
const updated = notifications.map((n) =>
n.id === notificationId ? { ...n, read: true } : n
)
saveNotifications(userPubkey, updated)
return updated
}
/**
* Mark all notifications as read
*/
export function markAllAsRead(userPubkey: string, notifications: Notification[]): Notification[] {
const updated = notifications.map((n) => ({ ...n, read: true }))
saveNotifications(userPubkey, updated)
return updated
}
/**
* Delete a notification
*/
export function deleteNotification(
userPubkey: string,
notificationId: string,
notifications: Notification[]
): Notification[] {
const updated = notifications.filter((n) => n.id !== notificationId)
saveNotifications(userPubkey, updated)
return updated
}