'use client'; import { useSyncExternalStore } from 'react'; const disabledKey = 'site-push-disabled'; const preferenceEvent = 'notifications:preference'; export function sitePushDisabled() { return localStorage.getItem(disabledKey) === '1'; } export function setSitePushDisabled(disabled: boolean) { if (disabled) localStorage.setItem(disabledKey, '1'); else localStorage.removeItem(disabledKey); window.dispatchEvent(new Event(preferenceEvent)); } function subscribePreference(onChange: () => void) { window.addEventListener(preferenceEvent, onChange); window.addEventListener('storage', onChange); return () => { window.removeEventListener(preferenceEvent, onChange); window.removeEventListener('storage', onChange); }; } export function useSitePushDisabled() { return useSyncExternalStore(subscribePreference, sitePushDisabled, () => false); } // Serialize subscription changes so pausing also stops an in-flight automatic sync. let pending: Promise = Promise.resolve(); function updateSubscription(operation: () => Promise): Promise { const result = pending.then(operation, operation); pending = result.catch(() => undefined); return result; } export function disableSitePush(userId?: string) { return updateSubscription(async () => { if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.getRegistration('/'); const subscription = await registration?.pushManager.getSubscription(); if (subscription && !await subscription.unsubscribe()) throw new Error('push-unsubscribe-failed'); } if (userId) sessionStorage.removeItem(`site-push-synced-${userId}`); setSitePushDisabled(true); }); } // All notification features share one browser permission and root service worker. export function syncSitePush(userId: string, resume = false) { return updateSubscription(async () => { if (!resume && sitePushDisabled()) return; const configResponse = await fetch('/api/commission/push-subscription', { cache: 'no-store' }); if (!configResponse.ok) throw new Error('push-config-failed'); const config = await configResponse.json() as { publicKey: string; canAdmin: boolean }; const registration = await navigator.serviceWorker.register('/commission-push-sw.js', { scope: '/' }); const subscription = await registration.pushManager.getSubscription() ?? await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: config.publicKey }); const data = subscription.toJSON(); const registrations = [ { url: '/api/comments/push-subscription', body: data }, { url: '/api/commission/push-subscription', body: { ...data, scope: config.canAdmin ? 'admin' : 'customer' } }, ]; const responses = await Promise.all(registrations.map(feature => fetch(feature.url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(feature.body) }))); if (responses.some(response => !response.ok)) throw new Error('push-subscription-failed'); sessionStorage.setItem(`site-push-synced-${userId}`, '1'); }); }