113 lines
3.4 KiB
TypeScript
113 lines
3.4 KiB
TypeScript
'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<string, string> }) => 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);
|
|
}
|
|
}
|