mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-06 21:49:08 +02:00
126 lines
4.1 KiB
Go
126 lines
4.1 KiB
Go
//go:build !android && !ios && !freebsd && !js
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/wailsapp/wails/v3/pkg/application"
|
|
|
|
"github.com/netbirdio/netbird/client/ui/authsession"
|
|
"github.com/netbirdio/netbird/client/ui/services"
|
|
)
|
|
|
|
// onSystemEvent fires an OS notification for daemon SystemEvents that carry
|
|
// a user-facing message, mirroring the legacy event.Manager behaviour: gated
|
|
// by the user's "Notifications" toggle, with CRITICAL events bypassing the
|
|
// gate. Update-related events are skipped here because trayUpdater produces
|
|
// its own richer notification when EventUpdateState fires.
|
|
func (t *Tray) onSystemEvent(ev *application.CustomEvent) {
|
|
se, ok := ev.Data.(services.SystemEvent)
|
|
if !ok {
|
|
return
|
|
}
|
|
// Session-warning and deadline-rejected events carry no UserMessage —
|
|
// the tray builds the localised notification body locally from metadata.
|
|
// Every other event needs a non-empty UserMessage to show anything meaningful.
|
|
isSessionWarning := se.Metadata[authsession.MetaWarning] == "true"
|
|
isDeadlineRejected := se.Metadata[authsession.MetaDeadlineRejected] != ""
|
|
if !isSessionWarning && !isDeadlineRejected && se.UserMessage == "" {
|
|
return
|
|
}
|
|
if shouldSkipSystemEvent(se) {
|
|
return
|
|
}
|
|
|
|
critical := se.Severity == "critical"
|
|
t.profileMu.Lock()
|
|
enabled := t.notificationsEnabled
|
|
t.profileMu.Unlock()
|
|
if !enabled && !critical {
|
|
return
|
|
}
|
|
|
|
// Session-warning events come in two flavours; detect via the stable
|
|
// metadata flags rather than category/severity so a future reword on
|
|
// the daemon side still routes here.
|
|
// - T-WarningLead (MetaSessionWarning + no MetaSessionFinal) →
|
|
// interactive "Extend now / Dismiss" OS notification. Title and
|
|
// body are built locally from i18n + metadata so the text follows
|
|
// the active UI language regardless of what the daemon (which has
|
|
// no locale context) writes into UserMessage.
|
|
// - T-FinalWarningLead (MetaSessionFinal=true) → auto-open the
|
|
// SessionExpiration dialog. No OS notification here; the
|
|
// dialog is the last-chance reminder, doubling up would be noise.
|
|
if isDeadlineRejected {
|
|
t.notify(
|
|
t.loc.T("notify.sessionDeadlineRejected.title"),
|
|
t.loc.T("notify.sessionDeadlineRejected.body"),
|
|
notifyIDSessionExpired,
|
|
)
|
|
return
|
|
}
|
|
|
|
if se.Metadata != nil && se.Metadata[authsession.MetaWarning] == "true" {
|
|
if se.Metadata[authsession.MetaFinal] == "true" {
|
|
t.openSessionExpiration()
|
|
return
|
|
}
|
|
t.notifySessionWarning(
|
|
t.loc.T("notify.sessionWarning.title"),
|
|
t.buildSessionWarningBody(se.Metadata),
|
|
)
|
|
return
|
|
}
|
|
|
|
body := se.UserMessage
|
|
if id := se.Metadata["id"]; id != "" {
|
|
body += fmt.Sprintf(" ID: %s", id)
|
|
}
|
|
t.notify(eventTitle(se), body, notifyIDEvent+se.ID)
|
|
}
|
|
|
|
// eventTitle composes a notification title from a SystemEvent's severity and
|
|
// category — "Critical: DNS", "Warning: Authentication", etc. — matching the
|
|
// format the legacy Fyne event.Manager produced.
|
|
func eventTitle(e services.SystemEvent) string {
|
|
prefix := titleCase(e.Severity)
|
|
if prefix == "" {
|
|
prefix = "Info"
|
|
}
|
|
category := titleCase(e.Category)
|
|
if category == "" {
|
|
category = "System"
|
|
}
|
|
return prefix + ": " + category
|
|
}
|
|
|
|
func titleCase(s string) string {
|
|
if s == "" {
|
|
return ""
|
|
}
|
|
return strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
|
|
}
|
|
|
|
// shouldSkipSystemEvent reports whether a daemon SystemEvent must not
|
|
// surface as a tray notification. Three sources are filtered out:
|
|
// - update-available announcements (trayUpdater emits its own richer
|
|
// notification when EventUpdateState fires)
|
|
// - install-progress signals (consumed by the install-progress window)
|
|
// - the ::/0 partner of an exit-node default-route event (the 0.0.0.0/0
|
|
// partner already drove the user-facing toast, so the v6 row is
|
|
// suppressed to avoid a duplicate notification)
|
|
func shouldSkipSystemEvent(se services.SystemEvent) bool {
|
|
if _, isUpdate := se.Metadata["new_version_available"]; isUpdate {
|
|
return true
|
|
}
|
|
if _, isProgress := se.Metadata["progress_window"]; isProgress {
|
|
return true
|
|
}
|
|
if se.Category == "network" && se.Metadata["network"] == "::/0" {
|
|
return true
|
|
}
|
|
return false
|
|
}
|