'use client'; import { initializeApp, getApps, type FirebaseApp } from 'firebase/app'; import { getMessaging, getToken, isSupported, onMessage, type Messaging } from 'firebase/messaging'; import { apiFetch } from '@/lib/api'; import type { FirebasePublicConfig } from '@/lib/firebase-config'; const SW_PATH = '/firebase-messaging-sw.js'; const REGISTERED_TOKEN_KEY = 'lendry_fcm_token'; let messagingInstance: Messaging | null = null; function getFirebaseApp(config: FirebasePublicConfig): FirebaseApp { const existing = getApps()[0]; if (existing) return existing; return initializeApp({ apiKey: config.apiKey, authDomain: `${config.projectId}.firebaseapp.com`, projectId: config.projectId, messagingSenderId: config.messagingSenderId, appId: config.appId }); } async function ensureServiceWorker() { if (!('serviceWorker' in navigator)) return null; const registration = await navigator.serviceWorker.register(SW_PATH, { scope: '/' }); await navigator.serviceWorker.ready; return registration; } export async function registerFirebasePush(authToken: string, config: FirebasePublicConfig) { if (typeof window === 'undefined') return null; if (!config.enabled) return null; const supported = await isSupported(); if (!supported) return null; if (!('Notification' in window)) return null; const permission = Notification.permission === 'granted' ? 'granted' : await Notification.requestPermission(); if (permission !== 'granted') return null; const registration = await ensureServiceWorker(); if (!registration) return null; const app = getFirebaseApp(config); messagingInstance = getMessaging(app); const fcmToken = await getToken(messagingInstance, { vapidKey: config.vapidKey, serviceWorkerRegistration: registration }); if (!fcmToken) return null; const previousToken = window.localStorage.getItem(REGISTERED_TOKEN_KEY); if (previousToken && previousToken !== fcmToken) { try { await apiFetch('/notifications/push/register', { method: 'DELETE', body: JSON.stringify({ token: previousToken }) }, authToken); } catch { // ignore stale unregister errors } } await apiFetch('/notifications/push/register', { method: 'POST', body: JSON.stringify({ token: fcmToken, platform: 'WEB', deviceLabel: navigator.userAgent.slice(0, 120) }) }, authToken); window.localStorage.setItem(REGISTERED_TOKEN_KEY, fcmToken); return fcmToken; } export function subscribeFirebaseForegroundMessages(listener: (payload: { title: string; message: string; data?: Record }) => void) { if (!messagingInstance) return () => undefined; return onMessage(messagingInstance, (payload) => { listener({ title: payload.notification?.title ?? payload.data?.title ?? 'Уведомление', message: payload.notification?.body ?? payload.data?.message ?? '', data: payload.data }); }); } export async function unregisterFirebasePush(authToken: string | null) { const stored = typeof window !== 'undefined' ? window.localStorage.getItem(REGISTERED_TOKEN_KEY) : null; if (!stored || !authToken) return; try { await apiFetch('/notifications/push/register', { method: 'DELETE', body: JSON.stringify({ token: stored }) }, authToken); } catch { // ignore logout cleanup errors } finally { window.localStorage.removeItem(REGISTERED_TOKEN_KEY); } }