# Wails Go API surface for the React frontend All bindings live under `frontend/bindings/github.com/netbirdio/netbird/client/ui-wails/services/`. Import them as: ```ts import { Connection, Peers, Networks, Settings, Profiles, Debug, Update, Forwarding } from "./bindings/github.com/netbirdio/netbird/client/ui-wails/services"; import * as $models from "./bindings/github.com/netbirdio/netbird/client/ui-wails/services/models"; ``` Every method returns `$CancellablePromise` (Wails3 wrapper around a Promise — call `.cancel()` to abort the underlying gRPC stream / call). ## Push events Subscribe with the Wails event API: `import { Events } from "@wailsio/runtime"`. | Event name | Payload type | Fires on | |---|---|---| | `netbird:status` | `Status` | Daemon connection-state change (Connected / Connecting / Disconnected / Idle), peer-list change, address change, management/signal flip. **Replaces polling**. | | `netbird:event` | `SystemEvent` | One push per daemon-emitted event (DNS/network/auth/connectivity/system). Drives toasts and the event log. | | `netbird:update:available` | `UpdateAvailable` | Daemon detected a new version. Show the update menu/banner. | | `netbird:update:progress` | `UpdateProgress` | `action:"show"` → open the update progress page; `action:"hide"` → close. | Calling `Peers.Watch()` once at boot starts both backend stream loops; both self-restart with backoff on errors. ## Connection lifecycle — `Connection` ```ts Connection.Up(p: UpParams): Promise Connection.Down(): Promise Connection.Login(p: LoginParams): Promise Connection.WaitSSOLogin(p: WaitSSOParams): Promise // returns email/userInfo Connection.Logout(p: LogoutParams): Promise ``` - **Up flow**: call `Login` first; if `LoginResult.needsSsoLogin === true` open `verificationUriComplete` in the browser, then call `WaitSSOLogin` with `{ userCode: LoginResult.userCode, hostname: ... }`. Once that resolves call `Up`. - **Down flow**: just `Down()`. The daemon transitions to `Idle`. ```ts class LoginParams { profileName, username, managementUrl, setupKey, preSharedKey, hostname, hint: string } class LoginResult { needsSsoLogin: boolean; userCode, verificationUri, verificationUriComplete: string } class WaitSSOParams { userCode, hostname: string } class UpParams { profileName, username: string } class LogoutParams { profileName, username: string } ``` ## Status / peer list — `Peers` ```ts Peers.Get(): Promise // one-shot snapshot Peers.Watch(): Promise // call once at boot to enable push events ``` ```ts class Status { status: string // "Idle" | "Connecting" | "Connected" | "SessionExpired" (see below) daemonVersion: string management: PeerLink signal: PeerLink local: LocalPeer peers: PeerStatus[] events: SystemEvent[] } class PeerLink { url: string connected: boolean } class LocalPeer { ip, pubKey, fqdn: string networks: string[] } class PeerStatus { ip, pubKey, fqdn: string connStatus: string // "Connected" | "Connecting" | "Idle" connStatusUpdateUnix: number // unix seconds relayed: boolean localIceCandidateType, remoteIceCandidateType: string localIceCandidateEndpoint, remoteIceCandidateEndpoint: string bytesRx, bytesTx: number latencyMs: number relayAddress: string // populated when relayed lastHandshakeUnix: number rosenpassEnabled: boolean networks: string[] } class SystemEvent { id: string severity: string // "info" | "warning" | "error" | "critical" category: string // "network" | "dns" | "authentication" | "connectivity" | "system" message: string // technical / log message userMessage: string // human-friendly message — render this timestamp: number // unix seconds metadata: Record } ``` ### Connection-state values The `Status.status` field uses these literal strings (from the daemon): | Value | Meaning | |---|---| | `"Idle"` | Disconnected — Up not invoked, or Down completed | | `"Connecting"` | Up in progress | | `"Connected"` | Tunnel up | | `"SessionExpired"` | SSO token expired — needs Login again | (The Fyne UI also reads a synthetic `"Error"` label for some failed states; check `events` for details.) ### ICE candidate type values `localIceCandidateType` / `remoteIceCandidateType` are pion/ICE strings: `"host"`, `"srflx"`, `"prflx"`, `"relay"`, or `""` while connecting. ## Networks — `Networks` ```ts Networks.List(): Promise Networks.Select(p: SelectNetworksParams): Promise Networks.Deselect(p: SelectNetworksParams): Promise ``` ```ts class Network { id, range: string // range is a CIDR selected: boolean domains: string[] // empty unless this is a domain network resolvedIps: Record // domain -> IPs } class SelectNetworksParams { networkIds: string[] append: boolean // false = replace selection, true = merge with existing all: boolean // true = ignore networkIds and target every network (Select-All / Deselect-All) } ``` The Fyne UI's All / Overlapping / Exit-node tabs are filters over the same `List()` result: - **Exit-node**: `range === "0.0.0.0/0" || range === "::/0"` - **Overlapping**: client-side detection of CIDR overlap among `range` values - **All**: everything ## Forwarding / exposed services — `Forwarding` ```ts Forwarding.List(): Promise ``` ```ts class ForwardingRule { protocol: string // "tcp" | "udp" destinationPort: PortInfo translatedAddress, translatedHostname: string translatedPort: PortInfo } class PortInfo { // exactly one field is populated port?: number range?: PortRange } class PortRange { start, end: number } ``` ## Profiles — `Profiles` ```ts Profiles.List(username: string): Promise Profiles.GetActive(): Promise Profiles.Switch(p: ProfileRef): Promise Profiles.Add(p: ProfileRef): Promise Profiles.Remove(p: ProfileRef): Promise Profiles.Username(): Promise // current OS username ``` ```ts class Profile { name: string; isActive: boolean } class ProfileRef { profileName, username: string } class ActiveProfile { profileName, username: string } ``` ## Settings / config — `Settings` ```ts Settings.GetConfig(p: ConfigParams): Promise Settings.SetConfig(p: SetConfigParams): Promise Settings.GetFeatures(): Promise ``` ```ts class ConfigParams { profileName, username: string } // identifies which profile's config class Config { managementUrl, adminUrl, configFile, logFile, preSharedKey: string interfaceName: string; wireguardPort, mtu: number disableAutoConnect, serverSshAllowed: boolean rosenpassEnabled, rosenpassPermissive: boolean disableNotifications, lazyConnectionEnabled, blockInbound: boolean networkMonitor, disableClientRoutes, disableServerRoutes: boolean disableDns, blockLanAccess: boolean enableSshRoot, enableSshSftp: boolean enableSshLocalPortForwarding, enableSshRemotePortForwarding: boolean disableSshAuth: boolean sshJwtCacheTtl: number } class SetConfigParams { // identity (always required) profileName, username: string // any field below is optional — only the ones you set are pushed to the daemon managementUrl?, adminUrl?, ... // ... same shape as Config } class Features { // feature flags from the daemon — hide UI sections when these are true disableProfiles, disableUpdateSettings, disableNetworks: boolean } ``` `SetConfig` is partial — supply only the fields you want to change, plus `profileName` + `username`. Booleans use Go pointer-presence under the hood; on the TS side undefined / missing means "leave as-is". ## Debug bundle / log level — `Debug` ```ts Debug.GetLogLevel(): Promise Debug.SetLogLevel(lvl: LogLevel): Promise Debug.Bundle(p: DebugBundleParams): Promise ``` ```ts class LogLevel { level: string } // "trace" | "debug" | "info" | "warning" | "error" | "panic" class DebugBundleParams { anonymize: boolean systemInfo: boolean uploadUrl: string // empty string = no upload logFileCount: number // 0 = default } class DebugBundleResult { path: string // local path of the generated bundle uploadedKey: string // populated when uploadUrl was set uploadFailureReason: string // populated on upload error } ``` ## Update flow — `Update` ```ts Update.Trigger(): Promise // start the install Update.GetInstallerResult(): Promise // poll the install outcome (long-running) ``` ```ts class UpdateResult { success: boolean; errorMsg: string } class UpdateAvailable { // payload of "netbird:update:available" version: string enforced: boolean // true = management server requires it } class UpdateProgress { // payload of "netbird:update:progress" action: string // "show" | "hide" version: string } ``` Typical flow: 1. Listen for `"netbird:update:available"` → show the "Update X.Y.Z" affordance. 2. User clicks → call `Update.Trigger()`. 3. The page that shows the install progress polls `GetInstallerResult()` (15-min timeout). On `success: true` the daemon will exit; the app should `app.Quit()` (or restart). On `success: false` show `errorMsg`. ## Toast notifications The tray sends OS notifications via `application/services/notifications` automatically for `netbird:event` events that have `userMessage`. The frontend doesn't need to do anything for that; the data is also delivered via `netbird:event` if you want to render an in-window log.