mirror of
https://github.com/Dvorinka/beszel.git
synced 2026-06-04 05:12:56 +00:00
207 lines
5.1 KiB
TypeScript
207 lines
5.1 KiB
TypeScript
import { useState, useEffect, useCallback } from "react"
|
|
import pb from "@/lib/pocketbase"
|
|
|
|
export interface PushNotificationState {
|
|
isSupported: boolean
|
|
permission: NotificationPermission | null
|
|
subscription: PushSubscription | null
|
|
isRegistering: boolean
|
|
error: string | null
|
|
}
|
|
|
|
export function usePushNotifications() {
|
|
const [state, setState] = useState<PushNotificationState>({
|
|
isSupported: false,
|
|
permission: null,
|
|
subscription: null,
|
|
isRegistering: false,
|
|
error: null,
|
|
})
|
|
|
|
// Check if push notifications are supported
|
|
useEffect(() => {
|
|
const isSupported =
|
|
"serviceWorker" in navigator &&
|
|
"PushManager" in window &&
|
|
"Notification" in window
|
|
|
|
setState((prev) => ({
|
|
...prev,
|
|
isSupported,
|
|
permission: isSupported ? Notification.permission : null,
|
|
}))
|
|
|
|
if (isSupported) {
|
|
checkSubscription()
|
|
}
|
|
}, [])
|
|
|
|
// Check existing subscription
|
|
const checkSubscription = async () => {
|
|
try {
|
|
const registration = await navigator.serviceWorker.ready
|
|
const existingSub = await registration.pushManager.getSubscription()
|
|
setState((prev) => ({ ...prev, subscription: existingSub }))
|
|
} catch (err) {
|
|
console.error("Error checking subscription:", err)
|
|
}
|
|
}
|
|
|
|
// Register service worker
|
|
const registerServiceWorker = async () => {
|
|
try {
|
|
const registration = await navigator.serviceWorker.register("/sw.js")
|
|
console.log("Service Worker registered:", registration)
|
|
return registration
|
|
} catch (err) {
|
|
console.error("Service Worker registration failed:", err)
|
|
throw err
|
|
}
|
|
}
|
|
|
|
// Request permission and subscribe
|
|
const subscribe = async () => {
|
|
setState((prev) => ({ ...prev, isRegistering: true, error: null }))
|
|
|
|
try {
|
|
// Request notification permission
|
|
const permission = await Notification.requestPermission()
|
|
setState((prev) => ({ ...prev, permission }))
|
|
|
|
if (permission !== "granted") {
|
|
throw new Error("Notification permission denied")
|
|
}
|
|
|
|
// Register service worker
|
|
const registration = await registerServiceWorker()
|
|
|
|
// Get VAPID public key from server
|
|
const response = await fetch("/api/beszel/notifications/vapid-key", {
|
|
headers: {
|
|
Authorization: `Bearer ${pb.authStore.token}`,
|
|
},
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Failed to get VAPID key")
|
|
}
|
|
|
|
const { publicKey } = await response.json()
|
|
|
|
// Subscribe to push notifications
|
|
const subscription = await registration.pushManager.subscribe({
|
|
userVisibleOnly: true,
|
|
applicationServerKey: urlBase64ToUint8Array(publicKey),
|
|
})
|
|
|
|
// Send subscription to server
|
|
const saveResponse = await fetch("/api/beszel/notifications/subscribe", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${pb.authStore.token}`,
|
|
},
|
|
body: JSON.stringify(subscription),
|
|
})
|
|
|
|
if (!saveResponse.ok) {
|
|
throw new Error("Failed to save subscription")
|
|
}
|
|
|
|
setState((prev) => ({
|
|
...prev,
|
|
subscription,
|
|
isRegistering: false,
|
|
}))
|
|
|
|
return subscription
|
|
} catch (err) {
|
|
const errorMessage = err instanceof Error ? err.message : "Unknown error"
|
|
setState((prev) => ({
|
|
...prev,
|
|
isRegistering: false,
|
|
error: errorMessage,
|
|
}))
|
|
throw err
|
|
}
|
|
}
|
|
|
|
// Unsubscribe from push notifications
|
|
const unsubscribe = async () => {
|
|
setState((prev) => ({ ...prev, isRegistering: true, error: null }))
|
|
|
|
try {
|
|
const registration = await navigator.serviceWorker.ready
|
|
const subscription = await registration.pushManager.getSubscription()
|
|
|
|
if (subscription) {
|
|
await subscription.unsubscribe()
|
|
|
|
// Notify server to remove subscription
|
|
await fetch("/api/beszel/notifications/unsubscribe", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${pb.authStore.token}`,
|
|
},
|
|
body: JSON.stringify({ endpoint: subscription.endpoint }),
|
|
})
|
|
}
|
|
|
|
setState((prev) => ({
|
|
...prev,
|
|
subscription: null,
|
|
isRegistering: false,
|
|
}))
|
|
} catch (err) {
|
|
const errorMessage = err instanceof Error ? err.message : "Unknown error"
|
|
setState((prev) => ({
|
|
...prev,
|
|
isRegistering: false,
|
|
error: errorMessage,
|
|
}))
|
|
throw err
|
|
}
|
|
}
|
|
|
|
// Send test notification
|
|
const sendTestNotification = async () => {
|
|
try {
|
|
const response = await fetch("/api/beszel/notifications/test-push", {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${pb.authStore.token}`,
|
|
},
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Failed to send test notification")
|
|
}
|
|
} catch (err) {
|
|
console.error("Test notification failed:", err)
|
|
throw err
|
|
}
|
|
}
|
|
|
|
return {
|
|
...state,
|
|
subscribe,
|
|
unsubscribe,
|
|
sendTestNotification,
|
|
}
|
|
}
|
|
|
|
// Helper function to convert VAPID key
|
|
function urlBase64ToUint8Array(base64String: string): Uint8Array {
|
|
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
|
|
}
|