Files
netbird/client/ui/tray_notify.go
Zoltán Papp 73cffdb702 Add peer-to-peer file drop
Files move directly between peers over the overlay, with no server in the
path. The receiver listens on the WireGuard address only, so the port is
unreachable from outside the tunnel, and every offer is matched to a known
peer before anything is read.

Consent is the default: an offer carries metadata alone, and no payload
moves until the receiver accepts. Policy is per profile and device-local —
off, ask, or auto-accept, with per-sender exceptions on top.

Policy and history live in the profile's preferences, so removing a profile
takes its file drop state with it. Transfers interrupted by a restart are
settled on load; nothing survives to finish them, and left alone they would
sit in the log as permanently pending.

The Android bindings pull payload bytes through a chunk-returning stream:
gomobile copies a []byte argument into a fresh Java array and never copies
it back, so a fill-my-buffer method would hand back the right length with
no data.
2026-08-16 22:04:38 +02:00

83 lines
2.7 KiB
Go

//go:build !android && !ios && !freebsd && !js
package main
import (
"context"
log "github.com/sirupsen/logrus"
"github.com/wailsapp/wails/v3/pkg/services/notifications"
"github.com/netbirdio/netbird/client/ui/services"
)
const notifyIDDaemonOutdated = "netbird-daemon-outdated"
// sendFn fits both NotificationService.SendNotification and SendNotificationWithActions.
type sendFn func(notifications.NotificationOptions) error
// safeSendNotification sends a best-effort OS notification, swallowing errors and panics.
//
// The panic guard is load-bearing on Linux: when Wails' notifier fails to
// connect the session bus at startup (headless, unreachable
// DBUS_SESSION_BUS_ADDRESS) it stays registered with a nil *dbus.Conn, so the
// next send nil-derefs inside godbus. Because sends run on a Wails
// event-dispatch goroutine that panic is fatal process-wide; recover() turns
// it into a logged no-op.
func safeSendNotification(send sendFn, what string, opts notifications.NotificationOptions) (err error) {
if services.ShuttingDown() {
return nil
}
defer func() {
if r := recover(); r != nil {
log.Errorf("notify %s: recovered from panic (notification bus unavailable): %v", what, r)
err = nil
}
}()
if err := send(opts); err != nil {
log.Errorf("notify %s: %v", what, err)
return err
}
return nil
}
// registerNotificationResponses installs the single Wails notification-response
// callback and fans it out per category; a second OnNotificationResponse call
// would silently replace the first, so every category must branch here.
func (t *Tray) registerNotificationResponses() {
if t.svc.Notifier == nil {
return
}
t.svc.Notifier.OnNotificationResponse(func(result notifications.NotificationResult) {
if result.Error != nil {
log.Debugf("notification response error: %v", result.Error)
return
}
switch result.Response.CategoryID {
case notifyCategorySessionWarning:
t.handleSessionWarningResponse(result.Response)
case notifyCategoryFileDropOffer:
t.handleFileDropResponse(result.Response)
}
})
}
// notifyIfDaemonOutdated probes the daemon once and fires an OS toast when it
// is reachable but too old for this UI. A probe error means the daemon isn't
// reachable (not outdated), so it is left to the normal connection flow.
func notifyIfDaemonOutdated(compat *services.Compat, notifier *Notifier, loc *Localizer) {
ready, err := compat.DaemonReady(context.Background())
if err != nil {
log.Debugf("daemon compatibility probe: %v", err)
return
}
if ready {
return
}
_ = safeSendNotification(notifier.SendNotification, "daemon-outdated", notifications.NotificationOptions{
ID: notifyIDDaemonOutdated,
Title: loc.T("notify.daemonOutdated.title"),
Body: loc.T("notify.daemonOutdated.body"),
})
}