//go:build !android && !ios && !freebsd && !js package main import ( "context" "errors" "sync/atomic" log "github.com/sirupsen/logrus" "github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/services/notifications" ) var errNotificationsUnavailable = errors.New("notifications unavailable") // Notifier wraps the Wails notification service so an unavailable backend // disables notifications instead of aborting the app. Startup fails for // environment reasons (a bare unbundled binary on macOS has no bundle // identifier, a headless Linux session has no D-Bus session bus), and Wails // treats a service startup error as fatal. After a failed startup every call // is a no-op: on macOS, touching UNUserNotificationCenter without a bundle // identifier raises an Objective-C exception that recover() cannot catch. type Notifier struct { inner *notifications.NotificationService available atomic.Bool } func newNotifier() *Notifier { return &Notifier{inner: notifications.New()} } // ServiceName implements the Wails service-name hook for startup logs. func (n *Notifier) ServiceName() string { return n.inner.ServiceName() } // ServiceStartup starts the platform notifier, downgrading failure to a // warning so the app keeps running without notifications. func (n *Notifier) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { if err := n.inner.ServiceStartup(ctx, options); err != nil { log.Warnf("notifications disabled: %v", err) return nil } n.available.Store(true) return nil } func (n *Notifier) ServiceShutdown() error { if !n.available.Load() { return nil } return n.inner.ServiceShutdown() } func (n *Notifier) CheckNotificationAuthorization() (bool, error) { if !n.available.Load() { return false, errNotificationsUnavailable } return n.inner.CheckNotificationAuthorization() } func (n *Notifier) RequestNotificationAuthorization() (bool, error) { if !n.available.Load() { return false, errNotificationsUnavailable } return n.inner.RequestNotificationAuthorization() } // SendNotification delivers a notification, silently dropping it when the // backend never started (notifications are best-effort everywhere). func (n *Notifier) SendNotification(options notifications.NotificationOptions) error { if !n.available.Load() { log.Debugf("notifications disabled, dropping %q", options.ID) return nil } return n.inner.SendNotification(options) } func (n *Notifier) SendNotificationWithActions(options notifications.NotificationOptions) error { if !n.available.Load() { log.Debugf("notifications disabled, dropping %q", options.ID) return nil } return n.inner.SendNotificationWithActions(options) } func (n *Notifier) RegisterNotificationCategory(category notifications.NotificationCategory) error { if !n.available.Load() { return nil } return n.inner.RegisterNotificationCategory(category) } // OnNotificationResponse registers the response callback. Pure Go state, so // it is safe (and simply inert) when the backend never started. // //wails:ignore func (n *Notifier) OnNotificationResponse(callback func(result notifications.NotificationResult)) { n.inner.OnNotificationResponse(callback) }