/** * React хук для управления Web Push уведомлениями */ import { useState, useEffect, useCallback } from 'react'; import { isPushSupported, getNotificationPermission, requestNotificationPermission, registerServiceWorker, subscribeToPush, unsubscribeFromPush, getCurrentSubscription, serializeSubscription, } from '@/lib/push'; import { apiClient } from '@/lib/api'; import { toast } from 'sonner'; import { getDeviceGroupId, getDeviceName, getInstallationId, isMessengerLaunch, type NotificationAppKind, } from '@/lib/notifications/installations'; interface UseWebPushReturn { isSupported: boolean; isSubscribed: boolean; isLoading: boolean; permission: NotificationPermission; subscribe: () => Promise; unsubscribe: () => Promise; requestPermission: () => Promise; } /** * Хук для управления Web Push подписками */ export function useWebPush({ appKind = 'main', enabled = true, }: { appKind?: NotificationAppKind; enabled?: boolean; } = {}): UseWebPushReturn { const [isSupported] = useState(() => isPushSupported()); const [isSubscribed, setIsSubscribed] = useState(false); const [isLoading, setIsLoading] = useState(true); const [permission, setPermission] = useState('default'); const [swRegistration, setSwRegistration] = useState(null); const [vapidPublicKey, setVapidPublicKey] = useState(null); // Инициализация: проверка поддержки и текущего статуса useEffect(() => { if (!enabled || !isSupported) { setIsLoading(false); return; } async function init() { try { setIsLoading(true); // Получаем разрешение const perm = getNotificationPermission(); setPermission(perm); // Получаем VAPID ключ const response = await apiClient.getVapidPublicKey(); setVapidPublicKey(response.vapid_public_key); // Регистрируем Service Worker const registration = await registerServiceWorker(appKind); setSwRegistration(registration); // Проверяем текущую подписку const subscription = await getCurrentSubscription(registration); setIsSubscribed(subscription !== null); if (appKind === 'main' || isMessengerLaunch()) { await apiClient.heartbeatNotificationInstallation({ installation_id: getInstallationId(appKind), device_group_id: getDeviceGroupId(), app_kind: appKind, device_name: getDeviceName(), installed: appKind === 'messenger' ? isMessengerLaunch() : true, }); } console.log('[useWebPush] Инициализация завершена, подписка:', subscription !== null); } catch (error) { console.error('[useWebPush] Ошибка инициализации:', error); toast.error('Не удалось инициализировать уведомления'); } finally { setIsLoading(false); } } init(); }, [appKind, enabled, isSupported]); // Запрос разрешения на уведомления const requestPermission = useCallback(async (): Promise => { const perm = await requestNotificationPermission(); setPermission(perm); return perm; }, []); // Подписаться на push уведомления const subscribe = useCallback(async (): Promise => { if (!isSupported || !swRegistration || !vapidPublicKey) { toast.error('Web Push не поддерживается'); return false; } setIsLoading(true); try { // Запрашиваем разрешение если нужно if (permission !== 'granted') { const perm = await requestPermission(); if (perm !== 'granted') { toast.error('Разрешение на уведомления отклонено'); return false; } } // Создаем подписку const subscription = await subscribeToPush(swRegistration, vapidPublicKey); const subscriptionData = serializeSubscription(subscription); // Отправляем на сервер await apiClient.subscribePush({ ...subscriptionData, device_name: getDeviceName(), installation_id: getInstallationId(appKind), device_group_id: getDeviceGroupId(), app_kind: appKind, installed: appKind === 'main' || isMessengerLaunch(), }); setIsSubscribed(true); toast.success('Подписка на уведомления активирована'); return true; } catch (error) { console.error('[useWebPush] Ошибка подписки:', error); toast.error('Не удалось подписаться на уведомления'); return false; } finally { setIsLoading(false); } }, [appKind, isSupported, swRegistration, vapidPublicKey, permission, requestPermission]); // Отписаться от push уведомлений const unsubscribe = useCallback(async (): Promise => { if (!isSupported || !swRegistration) { return false; } setIsLoading(true); try { // Получаем текущую подписку для endpoint const subscription = await getCurrentSubscription(swRegistration); const endpoint = subscription?.endpoint; // Удаляем локальную подписку const success = await unsubscribeFromPush(swRegistration); if (success && endpoint) { // Удаляем на сервере только это конкретное устройство await apiClient.unsubscribePush(endpoint); setIsSubscribed(false); toast.success('Подписка отменена'); return true; } return false; } catch (error) { console.error('[useWebPush] Ошибка отписки:', error); toast.error('Не удалось отписаться от уведомлений'); return false; } finally { setIsLoading(false); } }, [isSupported, swRegistration]); return { isSupported, isSubscribed, isLoading, permission, subscribe, unsubscribe, requestPermission, }; }