mirror of
https://github.com/Dvorinka/beszel.git
synced 2026-06-04 21:32:57 +00:00
Initial commit: Beszel fork with Domain Locker integration
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
webpush "github.com/SherClockHolmes/webpush-go"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
)
|
||||
|
||||
// PushNotification represents a push notification message
|
||||
type PushNotification struct {
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
Badge string `json:"badge,omitempty"`
|
||||
Image string `json:"image,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Data map[string]string `json:"data,omitempty"`
|
||||
Actions []Action `json:"actions,omitempty"`
|
||||
RequireInteraction bool `json:"requireInteraction,omitempty"`
|
||||
}
|
||||
|
||||
// Action represents a notification action button
|
||||
type Action struct {
|
||||
Action string `json:"action"`
|
||||
Title string `json:"title"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
}
|
||||
|
||||
// PushSubscription represents a browser push subscription
|
||||
type PushSubscription struct {
|
||||
ID string `json:"id" db:"id"`
|
||||
UserID string `json:"user" db:"user"`
|
||||
Endpoint string `json:"endpoint" db:"endpoint"`
|
||||
P256dh string `json:"p256dh" db:"p256dh"`
|
||||
Auth string `json:"auth" db:"auth"`
|
||||
Created time.Time `json:"created" db:"created"`
|
||||
}
|
||||
|
||||
// PushService handles push notifications
|
||||
type PushService struct {
|
||||
app core.App
|
||||
vapidPriv string
|
||||
vapidPub string
|
||||
}
|
||||
|
||||
// NewPushService creates a new push notification service
|
||||
func NewPushService(app core.App) *PushService {
|
||||
// Generate or load VAPID keys
|
||||
// In production, load from BESZEL_VAPID_PRIVATE_KEY env var
|
||||
privKey, pubKey := generateVAPIDKeys()
|
||||
|
||||
return &PushService{
|
||||
app: app,
|
||||
vapidPriv: privKey,
|
||||
vapidPub: pubKey,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterSubscription registers a push subscription for a user
|
||||
func (s *PushService) RegisterSubscription(userID string, sub *webpush.Subscription) error {
|
||||
collection, err := s.app.FindCollectionByNameOrId("push_subscriptions")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if subscription already exists
|
||||
existing, _ := s.app.FindFirstRecordByFilter("push_subscriptions",
|
||||
"user = {:user} && endpoint = {:endpoint}",
|
||||
map[string]interface{}{"user": userID, "endpoint": sub.Endpoint})
|
||||
|
||||
if existing != nil {
|
||||
// Update existing
|
||||
existing.Set("p256dh", sub.Keys.P256dh)
|
||||
existing.Set("auth", sub.Keys.Auth)
|
||||
return s.app.Save(existing)
|
||||
}
|
||||
|
||||
// Create new subscription
|
||||
record := core.NewRecord(collection)
|
||||
record.Set("user", userID)
|
||||
record.Set("endpoint", sub.Endpoint)
|
||||
record.Set("p256dh", sub.Keys.P256dh)
|
||||
record.Set("auth", sub.Keys.Auth)
|
||||
record.Set("created", time.Now())
|
||||
|
||||
return s.app.Save(record)
|
||||
}
|
||||
|
||||
// UnregisterSubscription removes a push subscription
|
||||
func (s *PushService) UnregisterSubscription(userID string, endpoint string) error {
|
||||
record, err := s.app.FindFirstRecordByFilter("push_subscriptions",
|
||||
"user = {:user} && endpoint = {:endpoint}",
|
||||
map[string]interface{}{"user": userID, "endpoint": endpoint})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.app.Delete(record)
|
||||
}
|
||||
|
||||
// SendNotification sends a push notification to a user
|
||||
func (s *PushService) SendNotification(userID string, notification *PushNotification) error {
|
||||
// Get all subscriptions for user
|
||||
records, err := s.app.FindAllRecords("push_subscriptions",
|
||||
dbx.NewExp("user = {:user}", dbx.Params{"user": userID}),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(notification)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
sub := &webpush.Subscription{
|
||||
Endpoint: record.GetString("endpoint"),
|
||||
Keys: webpush.Keys{
|
||||
P256dh: record.GetString("p256dh"),
|
||||
Auth: record.GetString("auth"),
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := webpush.SendNotification(payload, sub, &webpush.Options{
|
||||
Subscriber: "beszel@localhost",
|
||||
VAPIDPublicKey: s.vapidPub,
|
||||
VAPIDPrivateKey: s.vapidPriv,
|
||||
TTL: 30,
|
||||
})
|
||||
if err != nil {
|
||||
// Log error but continue trying other subscriptions
|
||||
continue
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BroadcastNotification sends notification to all users
|
||||
func (s *PushService) BroadcastNotification(notification *PushNotification) error {
|
||||
records, err := s.app.FindAllRecords("push_subscriptions")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(notification)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
sub := &webpush.Subscription{
|
||||
Endpoint: record.GetString("endpoint"),
|
||||
Keys: webpush.Keys{
|
||||
P256dh: record.GetString("p256dh"),
|
||||
Auth: record.GetString("auth"),
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := webpush.SendNotification(payload, sub, &webpush.Options{
|
||||
Subscriber: "beszel@localhost",
|
||||
VAPIDPublicKey: s.vapidPub,
|
||||
VAPIDPrivateKey: s.vapidPriv,
|
||||
TTL: 30,
|
||||
})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// generateVAPIDKeys generates or loads VAPID keys for web push
|
||||
func generateVAPIDKeys() (privateKey, publicKey string) {
|
||||
// Check for environment variable first
|
||||
if envKey := os.Getenv("BESZEL_VAPID_PRIVATE_KEY"); envKey != "" {
|
||||
// If private key provided, we need to derive public key
|
||||
// For now, return empty public key - will be handled by webpush lib
|
||||
return envKey, ""
|
||||
}
|
||||
|
||||
// Generate new VAPID key pair
|
||||
privKey, pubKey, err := webpush.GenerateVAPIDKeys()
|
||||
if err != nil {
|
||||
// Return empty keys if generation fails
|
||||
return "", ""
|
||||
}
|
||||
|
||||
return privKey, pubKey
|
||||
}
|
||||
|
||||
// GetVAPIDPublicKey returns the VAPID public key for client subscription
|
||||
func (s *PushService) GetVAPIDPublicKey() string {
|
||||
return s.vapidPub
|
||||
}
|
||||
Reference in New Issue
Block a user