// Abonnement Web Push côté navigateur (P4-C). Enregistre le service worker, demande la permission, // s'abonne avec la clé VAPID publique du serveur, et transmet l'abonnement au daemon. import { api } from './api'; import type { VapidKeyResponse } from '@arboretum/shared'; const SW_URL = '/sw.js'; /** Push nécessite service worker + PushManager + Notification (et un contexte sécurisé : localhost ou HTTPS). */ export function pushSupported(): boolean { return 'serviceWorker' in navigator && 'PushManager' in window && 'Notification' in window; } export function notificationPermission(): NotificationPermission { return 'Notification' in window ? Notification.permission : 'denied'; } export async function registerServiceWorker(): Promise { if (!('serviceWorker' in navigator)) return null; try { return await navigator.serviceWorker.register(SW_URL); } catch { return null; } } // La clé VAPID publique est transmise en base64url ; PushManager attend un BufferSource // adossé à un ArrayBuffer (pas SharedArrayBuffer) → on type explicitement le buffer. function urlBase64ToUint8Array(base64: string): Uint8Array { const padding = '='.repeat((4 - (base64.length % 4)) % 4); const b64 = (base64 + padding).replace(/-/g, '+').replace(/_/g, '/'); const raw = atob(b64); const out = new Uint8Array(new ArrayBuffer(raw.length)); for (let i = 0; i < raw.length; i++) out[i] = raw.charCodeAt(i); return out; } export async function currentSubscription(): Promise { if (!pushSupported()) return null; const reg = await navigator.serviceWorker.ready; return reg.pushManager.getSubscription(); } export type EnableResult = 'enabled' | 'denied' | 'unsupported'; export async function enablePush(): Promise { if (!pushSupported()) return 'unsupported'; const permission = await Notification.requestPermission(); if (permission !== 'granted') return 'denied'; await registerServiceWorker(); const reg = await navigator.serviceWorker.ready; const { key } = await api.get('/api/v1/push/vapid-public-key'); const sub = (await reg.pushManager.getSubscription()) ?? (await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(key) })); const json = sub.toJSON(); const keys = json.keys ?? {}; await api.post('/api/v1/push/subscribe', { endpoint: sub.endpoint, keys: { p256dh: keys.p256dh ?? '', auth: keys.auth ?? '' } }); return 'enabled'; } export async function disablePush(): Promise { const sub = await currentSubscription(); if (!sub) return; try { await api.post('/api/v1/push/unsubscribe', { endpoint: sub.endpoint }); } catch { // le retrait local prime ; un échec réseau sur l'unsubscribe serveur n'est pas bloquant } await sub.unsubscribe(); }