/** * Notification service - stores and manages notifications in IndexedDB */ import { createIndexedDBHelper, type IndexedDBHelper } from './helpers/indexedDBHelper' const DB_NAME = 'nostr_notifications' const DB_VERSION = 1 const STORE_NAME = 'notifications' export type NotificationType = | 'purchase' // Achat | 'review' // Avis | 'sponsoring' // Sponsoring | 'review_tip' // Remerciement (tip sur un avis) | 'payment_note' // Note de paiement | 'published' // Objet publié avec succès (passé de published: false à liste de relais) export interface Notification { id: string // Unique notification ID type: NotificationType objectType: string // Type d'objet (author, series, publication, etc.) objectId: string // ID de l'objet dans objectCache eventId: string // ID de l'événement Nostr timestamp: number // Date de création de la notification (milliseconds) read: boolean // Si la notification a été lue data?: Record // Données supplémentaires (relais publiés, montant, etc.) // Compatibilité avec l'ancien format title?: string message?: string articleId?: string articleTitle?: string amount?: number fromPubkey?: string } class NotificationService { private readonly dbHelper: IndexedDBHelper constructor() { this.dbHelper = createIndexedDBHelper({ dbName: DB_NAME, version: DB_VERSION, storeName: STORE_NAME, keyPath: 'id', indexes: [ { name: 'type', keyPath: 'type', unique: false }, { name: 'objectId', keyPath: 'objectId', unique: false }, { name: 'eventId', keyPath: 'eventId', unique: false }, { name: 'timestamp', keyPath: 'timestamp', unique: false }, { name: 'read', keyPath: 'read', unique: false }, { name: 'objectType', keyPath: 'objectType', unique: false }, ], }) } /** * Create a new notification * Utilise writeService pour écrire via Web Worker */ async createNotification(params: { type: NotificationType objectType: string objectId: string eventId: string data?: Record }): Promise { try { const { type, objectType, objectId, eventId, data } = params // Check if notification already exists for this event const existing = await this.getNotificationByEventId(eventId) if (existing) { return // Notification already exists } // Utiliser writeService pour créer la notification via Web Worker const { writeService } = await import('./writeService') await writeService.createNotification(type, objectType, objectId, eventId, data) } catch (error) { console.error('[NotificationService] Error creating notification:', error) } } /** * Get notification by event ID */ async getNotificationByEventId(eventId: string): Promise { try { return await this.dbHelper.getByIndex('eventId', eventId) } catch (error) { console.error('[NotificationService] Error getting notification by event ID:', error) return null } } /** * Get all notifications for a user */ async getAllNotifications(limit: number = 100): Promise { try { const notifications: Notification[] = [] const store = await this.dbHelper.getStore('readonly') const index = store.index('timestamp') return new Promise((resolve, reject) => { const request = index.openCursor(null, 'prev') // Descending order (newest first) request.onsuccess = (event: globalThis.Event): void => { const cursor = (event.target as IDBRequest).result if (cursor) { notifications.push(cursor.value as Notification) if (notifications.length < limit) { cursor.continue() } else { resolve(notifications) } } else { resolve(notifications) } } request.onerror = (): void => { if (request.error) { reject(request.error) } else { reject(new Error('Unknown error opening cursor')) } } }) } catch (error) { console.error('[NotificationService] Error getting all notifications:', error) return [] } } /** * Get unread notifications count */ async getUnreadCount(): Promise { try { return await this.dbHelper.countByIndex('read', IDBKeyRange.only(false)) } catch (error) { console.error('[NotificationService] Error getting unread count:', error) return 0 } } /** * Mark notification as read */ async markAsRead(notificationId: string): Promise { try { const notification = await this.dbHelper.get(notificationId) if (!notification) { throw new Error('Notification not found') } const updatedNotification: Notification = { ...notification, read: true, } await this.dbHelper.put(updatedNotification) } catch (error) { console.error('[NotificationService] Error marking notification as read:', error) throw error } } /** * Mark all notifications as read */ async markAllAsRead(): Promise { try { const notifications = await this.getAllNotifications(10000) const unreadNotifications = notifications.filter((n) => !n.read) await Promise.all( unreadNotifications.map((notification) => { const updatedNotification: Notification = { ...notification, read: true, } return this.dbHelper.put(updatedNotification) }) ) } catch (error) { console.error('[NotificationService] Error marking all notifications as read:', error) throw error } } /** * Delete notification */ async deleteNotification(notificationId: string): Promise { try { await this.dbHelper.delete(notificationId) } catch (error) { console.error('[NotificationService] Error deleting notification:', error) throw error } } } export const notificationService = new NotificationService()