const STORAGE_KEY = 'farmaclic.subscriptions.v1'; export function getStoredSubscriptions() { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return []; const parsed = JSON.parse(raw); return Array.isArray(parsed) ? parsed : []; } catch { return []; } } function storageKey(nregistro, pharmacyId = null) { return pharmacyId ? `${nregistro}:${pharmacyId}` : nregistro; } export function isSubscribedLocally(nregistro, pharmacyId = null) { return getStoredSubscriptions().includes(storageKey(nregistro, pharmacyId)); } function setStoredSubscriptions(list) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify([...new Set(list)])); } catch {} } export function pushSupported() { return ( typeof window !== 'undefined' && 'serviceWorker' in navigator && 'PushManager' in window && 'Notification' in window ); } export async function getVapidPublicKey() { const res = await fetch('/api/notifications/vapid-public-key', { credentials: 'include' }); if (!res.ok) { const message = res.status === 503 ? 'Las notificaciones push no están configuradas en el servidor' : `No se pudo obtener la clave VAPID (HTTP ${res.status})`; throw new Error(message); } const { publicKey } = await res.json(); if (!publicKey) throw new Error('El servidor devolvió una clave VAPID vacía'); return publicKey; } export function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - (base64String.length % 4)) % 4); const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); const raw = atob(base64); const arr = new Uint8Array(raw.length); for (let i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i); return arr; } async function getOrCreateSubscription() { if (!pushSupported()) { throw new Error('Las notificaciones push no son compatibles con este navegador'); } if (window.isSecureContext === false) { throw new Error('Las notificaciones push requieren HTTPS (o localhost)'); } const reg = await navigator.serviceWorker.ready; let sub = await reg.pushManager.getSubscription(); if (sub) return sub; if (Notification.permission === 'denied') { throw new Error('Permiso de notificaciones denegado — actívalo en la configuración del navegador'); } if (Notification.permission === 'default') { const result = await Notification.requestPermission(); if (result !== 'granted') { throw new Error('Notification permission was not granted'); } } const publicKey = await getVapidPublicKey(); sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(publicKey), }); return sub; } export async function subscribeToPush(medicineNregistro, medicineName, pharmacyId = null) { const subscription = await getOrCreateSubscription(); const res = await fetch('/api/notifications/subscribe', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ medicine_nregistro: medicineNregistro, medicine_name: medicineName || null, subscription, pharmacy_id: pharmacyId || null, }), }); if (!res.ok) { const detail = await res.text().catch(() => ''); throw new Error(`Suscripción fallida (HTTP ${res.status}) ${detail}`); } setStoredSubscriptions([...getStoredSubscriptions(), storageKey(medicineNregistro, pharmacyId)]); } export async function unsubscribeFromPush(medicineNregistro, pharmacyId = null) { let endpoint = null; if (pushSupported()) { try { const reg = await navigator.serviceWorker.ready; const sub = await reg.pushManager.getSubscription(); endpoint = sub?.endpoint || null; } catch {} } if (endpoint) { await fetch('/api/notifications/unsubscribe', { method: 'DELETE', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ medicine_nregistro: medicineNregistro, endpoint, pharmacy_id: pharmacyId || null, }), }).catch(() => {}); } const key = storageKey(medicineNregistro, pharmacyId); setStoredSubscriptions(getStoredSubscriptions().filter(n => n !== key)); }