const LS_KEY = 'shine-ui-webpush-subscription-v1'; function urlBase64ToUint8Array(base64String) { 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; } export async function initPwaPush({ authService, onLog = null }) { const log = (entry) => { if (typeof onLog === 'function') onLog(entry); }; if (!('serviceWorker' in navigator) || !('PushManager' in window)) { log({ level: 'warn', source: 'web-push', message: 'Web Push недоступен: нет serviceWorker или PushManager', }); return; } const vapidPublicKey = window.__SHINE_WEBPUSH_VAPID_PUBLIC_KEY__ || ''; if (!vapidPublicKey) { log({ level: 'warn', source: 'web-push', message: 'Web Push отключен: не задан публичный VAPID ключ', }); return; } try { const registration = await navigator.serviceWorker.register('./firebase-messaging-sw.js'); log({ level: 'info', source: 'web-push', message: 'Service Worker зарегистрирован', details: { scope: registration.scope }, }); const permission = await Notification.requestPermission(); if (permission !== 'granted') { log({ level: 'warn', source: 'web-push', message: `Разрешение на уведомления: ${permission}`, }); return; } log({ level: 'info', source: 'web-push', message: 'Разрешение на уведомления получено', }); let sub = await registration.pushManager.getSubscription(); let isNewSubscription = false; if (!sub) { sub = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(vapidPublicKey), }); isNewSubscription = true; } log({ level: 'info', source: 'web-push', message: isNewSubscription ? 'Создана новая push-подписка' : 'Найдена существующая push-подписка', }); const serialized = JSON.stringify(sub); const prevSerialized = localStorage.getItem(LS_KEY); if (prevSerialized !== serialized) { localStorage.setItem(LS_KEY, serialized); log({ level: 'info', source: 'web-push', message: 'Push-подписка изменилась относительно прошлого запуска', }); } else { log({ level: 'info', source: 'web-push', message: 'Push-подписка не изменилась, но в MVP включена принудительная отправка на сервер', }); } const json = sub.toJSON(); const endpoint = json.endpoint || ''; const p256dhKey = json.keys?.p256dh || ''; const authKey = json.keys?.auth || ''; if (!endpoint || !p256dhKey || !authKey) { log({ level: 'warn', source: 'web-push', message: 'Подписка неполная: endpoint/p256dh/auth отсутствуют', }); return; } log({ level: 'info', source: 'web-push', message: 'Push-токен получен, отправка на сервер', details: { endpoint }, }); await authService.upsertPushToken({ endpoint, p256dhKey, authKey, platform: 'web', userAgent: navigator.userAgent || '', }); log({ level: 'info', source: 'web-push', message: 'Push-подписка успешно отправлена на сервер', }); } catch (error) { log({ level: 'error', source: 'web-push', message: 'Ошибка инициализации Web Push', details: error?.message || 'unknown', }); } }