mirror of
https://github.com/Dvorinka/beszel.git
synced 2026-06-04 13:22:57 +00:00
Initial commit: Beszel fork with Domain Locker integration
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user