import apiClient from '../utils/apiClient'; const API_URL = '/api/notifications'; // Тип для уведомления export interface Notification { id: number; userId: number; type: string; title: string; message: string; serverId?: number; ticketId?: number; checkId?: number; actionUrl?: string; icon?: string; color?: string; isRead: boolean; createdAt: string; } // Тип для PushSubscription export interface PushSubscriptionData { endpoint: string; keys: { p256dh: string; auth: string; }; } // Получить токен из localStorage const getAuthHeader = () => { const token = localStorage.getItem('access_token'); return token ? { Authorization: `Bearer ${token}` } : {}; }; // Получить список уведомлений export const getNotifications = async (params?: { page?: number; limit?: number; unreadOnly?: boolean; type?: string; }): Promise<{ notifications: Notification[]; total: number; unreadCount: number }> => { try { const response = await apiClient.get(API_URL, { headers: getAuthHeader(), params }); // Проверяем структуру ответа if (response.data && response.data.success !== false) { // Адаптируем формат ответа от сервера const data = response.data; return { notifications: data.data || [], total: data.pagination?.total || 0, unreadCount: data.unreadCount || 0 }; } else { console.error('Ошибка ответа:', response.data); throw new Error(response.data?.message || 'Ошибка сервера'); } } catch (error) { console.error('Ошибка загрузки уведомлений:', error); // Возвращаем пустой объект вместо выброса ошибки return { notifications: [], total: 0, unreadCount: 0 }; } }; // Получить количество непрочитанных export const getUnreadCount = async (): Promise => { try { const response = await apiClient.get(`${API_URL}/unread-count`, { headers: getAuthHeader() }); return response.data.count || 0; } catch (error) { console.error('Ошибка получения счетчика:', error); return 0; } }; // Пометить уведомление как прочитанное export const markAsRead = async (id: number): Promise => { await apiClient.post(`${API_URL}/${id}/read`, {}, { headers: getAuthHeader() }); }; // Пометить все как прочитанные export const markAllAsRead = async (): Promise => { await apiClient.post(`${API_URL}/read-all`, {}, { headers: getAuthHeader() }); }; // Удалить уведомление export const deleteNotification = async (id: number): Promise => { await apiClient.delete(`${API_URL}/${id}`, { headers: getAuthHeader() }); }; // Удалить все прочитанные export const deleteAllRead = async (): Promise => { await apiClient.delete(`${API_URL}/read/all`, { headers: getAuthHeader() }); }; // Получить публичный VAPID ключ export const getVapidKey = async (): Promise => { const response = await apiClient.get(`${API_URL}/vapid-key`, { headers: getAuthHeader() }); return response.data.publicKey; }; // Подписаться на Push-уведомления export const subscribePush = async (subscription: PushSubscriptionData): Promise => { await apiClient.post(`${API_URL}/subscribe-push`, { subscription }, { headers: getAuthHeader() }); }; // Отписаться от Push-уведомлений export const unsubscribePush = async (endpoint: string): Promise => { await apiClient.delete(`${API_URL}/unsubscribe-push`, { headers: getAuthHeader(), data: { endpoint } }); }; // Запросить разрешение на Push-уведомления и зарегистрировать Service Worker export const requestPushPermission = async (): Promise => { if (!('Notification' in window)) { console.error('❌ Браузер не поддерживает уведомления'); return false; } if (!('serviceWorker' in navigator)) { console.error('❌ Браузер не поддерживает Service Worker'); return false; } // Запрос разрешения console.log('📝 Запрашиваем разрешение на уведомления...'); const permission = await Notification.requestPermission(); console.log('📝 Результат запроса разрешения:', permission); if (permission !== 'granted') { console.log('❌ Пользователь отклонил разрешение на уведомления'); return false; } try { // Регистрация Service Worker console.log('📝 Регистрируем Service Worker...'); const registration = await navigator.serviceWorker.register('/service-worker.js'); console.log('✅ Service Worker зарегистрирован:', registration); console.log('📝 Ожидаем готовности Service Worker...'); await navigator.serviceWorker.ready; console.log('✅ Service Worker готов'); // Получаем публичный VAPID ключ console.log('📝 Получаем VAPID ключ...'); const vapidPublicKey = await getVapidKey(); console.log('✅ VAPID ключ получен:', vapidPublicKey.substring(0, 20) + '...'); // Создаём подписку console.log('📝 Создаём Push подписку...'); const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(vapidPublicKey) as BufferSource }); console.log('✅ Push подписка создана:', subscription.endpoint); // Отправляем подписку на сервер console.log('📝 Отправляем подписку на сервер...'); const subscriptionData: PushSubscriptionData = { endpoint: subscription.endpoint, keys: { p256dh: arrayBufferToBase64(subscription.getKey('p256dh')!), auth: arrayBufferToBase64(subscription.getKey('auth')!) } }; await subscribePush(subscriptionData); console.log('✅ Push-уведомления успешно подключены'); return true; } catch (error) { console.error('❌ Ошибка подключения Push-уведомлений:', error); return false; } }; // Утилита: конвертация base64 VAPID ключа в Uint8Array function urlBase64ToUint8Array(base64String: string): Uint8Array { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/-/g, '+') .replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; } // Утилита: конвертация ArrayBuffer в base64 function arrayBufferToBase64(buffer: ArrayBuffer): string { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return window.btoa(binary); }