story-research-zapwall/lib/notifications.ts
2025-12-22 09:48:57 +01:00

161 lines
4.3 KiB
TypeScript

import type { Event } from 'nostr-tools'
import { SimplePool } from 'nostr-tools'
import { nostrService } from './nostr'
import { zapVerificationService } from './zapVerification'
import type { Notification } from '@/types/notifications'
const RELAY_URL = process.env.NEXT_PUBLIC_NOSTR_RELAY_URL || 'wss://relay.damus.io'
/**
* 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 () => {}
}
// Subscribe to zap receipts targeting this user
const filters = [
{
kinds: [9735], // Zap receipt
'#p': [userPubkey], // Receipts targeting this user
},
]
const sub = (pool as any).sub([RELAY_URL], filters)
sub.on('event', async (event: Event) => {
try {
// Extract payment info from zap receipt
const paymentInfo = zapVerificationService.extractPaymentInfo(event)
if (!paymentInfo || paymentInfo.recipient !== userPubkey) {
return
}
// Get article info if available
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)
}
}
// Create notification
const notification: Notification = {
id: event.id,
type: 'payment',
title: 'New Payment Received',
message: articleTitle
? `You received ${paymentInfo.amount} sats for "${articleTitle}"`
: `You received ${paymentInfo.amount} sats`,
timestamp: event.created_at,
read: false,
articleId: paymentInfo.articleId || undefined,
articleTitle,
amount: paymentInfo.amount,
fromPubkey: paymentInfo.payer,
}
onNotification(notification)
} catch (error) {
console.error('Error processing zap receipt notification:', error)
}
})
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 localStorage
*/
export function loadStoredNotifications(userPubkey: string): Notification[] {
try {
const key = `notifications_${userPubkey}`
const stored = localStorage.getItem(key)
if (stored) {
return JSON.parse(stored) as Notification[]
}
} catch (error) {
console.error('Error loading stored notifications:', error)
}
return []
}
/**
* Save notifications to localStorage
*/
export function saveNotifications(userPubkey: string, notifications: Notification[]): void {
try {
const key = `notifications_${userPubkey}`
localStorage.setItem(key, JSON.stringify(notifications))
} 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
}