this repo has no description
at fix-ts-uint8array 103 lines 3.4 kB view raw
1import { useState, useEffect, useCallback } from 'react'; 2import { apiClient } from '../api/client'; 3 4interface PushPreferences { 5 reminder_enabled: boolean; 6 reminder_time: string; // "HH:MM" in UTC 7} 8 9function urlBase64ToUint8Array(base64String: string): Uint8Array { 10 const padding = '='.repeat((4 - (base64String.length % 4)) % 4); 11 const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); 12 const raw = atob(base64); 13 const arr = new Uint8Array(raw.length); 14 for (let i = 0; i < raw.length; i++) { 15 arr[i] = raw.charCodeAt(i); 16 } 17 return arr; 18} 19 20export function localTimeToUtc(localTime: string): string { 21 const [hours, minutes] = localTime.split(':').map(Number); 22 const now = new Date(); 23 now.setHours(hours, minutes, 0, 0); 24 const utcH = now.getUTCHours().toString().padStart(2, '0'); 25 const utcM = now.getUTCMinutes().toString().padStart(2, '0'); 26 return `${utcH}:${utcM}`; 27} 28 29export function utcTimeToLocal(utcTime: string): string { 30 const [hours, minutes] = utcTime.split(':').map(Number); 31 const now = new Date(); 32 now.setUTCHours(hours, minutes, 0, 0); 33 const localH = now.getHours().toString().padStart(2, '0'); 34 const localM = now.getMinutes().toString().padStart(2, '0'); 35 return `${localH}:${localM}`; 36} 37 38export function usePushNotifications() { 39 const [permission, setPermission] = useState<NotificationPermission>( 40 typeof window !== 'undefined' && 'Notification' in window 41 ? Notification.permission 42 : 'denied', 43 ); 44 const [preferences, setPreferences] = useState<PushPreferences | null>(null); 45 const [loading, setLoading] = useState(true); 46 47 useEffect(() => { 48 apiClient 49 .get<PushPreferences>('/api/push/preferences') 50 .then(setPreferences) 51 .catch(() => {}) 52 .finally(() => setLoading(false)); 53 }, []); 54 55 const subscribe = useCallback(async () => { 56 if (!('Notification' in window) || !('serviceWorker' in navigator)) return; 57 58 const perm = await Notification.requestPermission(); 59 setPermission(perm); 60 if (perm !== 'granted') return; 61 62 const { vapid_public_key } = await apiClient.get<{ vapid_public_key: string }>( 63 '/api/push/vapid-key', 64 ); 65 66 const applicationServerKey = urlBase64ToUint8Array(vapid_public_key); 67 68 const registration = await navigator.serviceWorker.ready; 69 const subscription = await registration.pushManager.subscribe({ 70 userVisibleOnly: true, 71 applicationServerKey: applicationServerKey.buffer as ArrayBuffer, 72 }); 73 74 const json = subscription.toJSON(); 75 await apiClient.post('/api/push/subscribe', { 76 endpoint: json.endpoint, 77 p256dh: json.keys?.p256dh, 78 auth: json.keys?.auth, 79 }); 80 }, []); 81 82 const unsubscribe = useCallback(async () => { 83 if (!('serviceWorker' in navigator)) return; 84 85 try { 86 const registration = await navigator.serviceWorker.ready; 87 const subscription = await registration.pushManager.getSubscription(); 88 if (subscription) { 89 await subscription.unsubscribe(); 90 } 91 } catch { 92 // ignore errors 93 } 94 await apiClient.delete('/api/push/subscribe'); 95 }, []); 96 97 const updatePreferences = useCallback(async (prefs: PushPreferences) => { 98 await apiClient.put('/api/push/preferences', prefs); 99 setPreferences(prefs); 100 }, []); 101 102 return { permission, preferences, loading, subscribe, unsubscribe, updatePreferences }; 103}