Initial commit: Beszel fork with Domain Locker integration

This commit is contained in:
Tomas Dvorak
2026-04-21 15:39:43 +02:00
commit 363d708e91
440 changed files with 160889 additions and 0 deletions
@@ -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
}