198 lines
7.2 KiB
TypeScript
198 lines
7.2 KiB
TypeScript
/**
|
||
* 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<boolean>;
|
||
unsubscribe: () => Promise<boolean>;
|
||
requestPermission: () => Promise<NotificationPermission>;
|
||
}
|
||
|
||
/**
|
||
* Хук для управления 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<NotificationPermission>('default');
|
||
const [swRegistration, setSwRegistration] = useState<ServiceWorkerRegistration | null>(null);
|
||
const [vapidPublicKey, setVapidPublicKey] = useState<string | null>(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<NotificationPermission> => {
|
||
const perm = await requestNotificationPermission();
|
||
setPermission(perm);
|
||
return perm;
|
||
}, []);
|
||
|
||
// Подписаться на push уведомления
|
||
const subscribe = useCallback(async (): Promise<boolean> => {
|
||
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<boolean> => {
|
||
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,
|
||
};
|
||
}
|