Files
netbird/client/android/profile_manager.go
Zoltán Papp 8c3214601e [client] Extract the mobile profile manager into client/mobile
The Android and iOS gomobile bindings carried two near-identical copies of
the profile manager. Move the shared implementation into a new client/mobile
package and reduce both bindings to thin adapters that only translate to
gomobile-friendly types (gomobile binds per package, so the Profile /
ProfileArray wrappers have to stay platform-side).

Also bring the account-email layer over to the shared package: an SSO login
records the account under <stem>.account.json so the next login can pass it
as an OIDC login_hint. Logout keeps it, profile removal drops it. The suffix
deliberately differs from .state.json, which the engine's state manager owns
in the same directory on mobile.

Adds profilemanager.Prefs (namespaced per-profile preference store) and its
cleanup in ServiceManager.RemoveProfile, exposed through the shared manager
as ProfilePrefs.
2026-08-24 12:23:51 +02:00

139 lines
4.2 KiB
Go

//go:build android
package android
import (
"github.com/netbirdio/netbird/client/mobile"
)
const (
// Android uses a single user context per app.
androidUsername = "android"
)
// Profile represents a profile for gomobile.
type Profile struct {
ID string
Name string
// Email is the account this profile last logged in with, "" if it never
// completed an SSO login. Kept across logouts; cleared when the profile is
// removed. See client/mobile/profile_state.go.
Email string
IsActive bool
}
// ProfileArray wraps profiles for gomobile compatibility (gomobile cannot
// bind Go slices directly).
type ProfileArray struct {
items []*Profile
}
// Length returns the number of profiles.
func (p *ProfileArray) Length() int {
return len(p.items)
}
// Get returns the profile at index i, or nil if out of range.
func (p *ProfileArray) Get(i int) *Profile {
if i < 0 || i >= len(p.items) {
return nil
}
return p.items[i]
}
// ProfileManager adapts the shared mobile profile manager (client/mobile) to
// gomobile-friendly types. See that package for the on-disk layout and
// semantics.
type ProfileManager struct {
impl *mobile.ProfileManager
}
// NewProfileManager creates a new profile manager for Android. configDir is
// the app's files directory.
func NewProfileManager(configDir string) *ProfileManager {
return &ProfileManager{impl: mobile.NewProfileManager(configDir, androidUsername)}
}
// ListProfiles returns all available profiles, including the default profile,
// with their active status set.
func (pm *ProfileManager) ListProfiles() (*ProfileArray, error) {
profiles, err := pm.impl.ListProfiles()
if err != nil {
return nil, err
}
items := make([]*Profile, 0, len(profiles))
for i := range profiles {
items = append(items, fromMobileProfile(&profiles[i]))
}
return &ProfileArray{items: items}, nil
}
// GetActiveProfile returns the currently active profile.
func (pm *ProfileManager) GetActiveProfile() (*Profile, error) {
p, err := pm.impl.GetActiveProfile()
if err != nil {
return nil, err
}
return fromMobileProfile(p), nil
}
// SwitchProfile records the given profile ID as the active profile. The caller
// must stop the VPN tunnel before switching.
func (pm *ProfileManager) SwitchProfile(id string) error {
return pm.impl.SwitchProfile(id)
}
// AddProfile creates a new profile with the given display name and a
// generated ID.
func (pm *ProfileManager) AddProfile(profileName string) error {
_, err := pm.impl.AddProfile(profileName)
return err
}
// RenameProfile changes the display name of the profile identified by id. The
// on-disk filename (the ID) is left unchanged.
func (pm *ProfileManager) RenameProfile(id string, newName string) error {
return pm.impl.RenameProfile(id, newName)
}
// LogoutProfile clears authentication data for a profile, forcing a re-login.
// The management URL and other settings are preserved.
func (pm *ProfileManager) LogoutProfile(id string) error {
return pm.impl.LogoutProfile(id)
}
// RemoveProfile deletes a profile. The default profile and the active profile
// cannot be removed.
func (pm *ProfileManager) RemoveProfile(id string) error {
return pm.impl.RemoveProfile(id)
}
// GetConfigPath returns the config file path for the given profile ID. Java
// should call this instead of constructing paths with Preferences.configFile().
func (pm *ProfileManager) GetConfigPath(id string) (string, error) {
return pm.impl.GetConfigPath(id)
}
// GetStateFilePath returns the state file path for the given profile ID. Java
// should call this instead of constructing paths with Preferences.stateFile().
func (pm *ProfileManager) GetStateFilePath(id string) (string, error) {
return pm.impl.GetStateFilePath(id)
}
// GetActiveConfigPath returns the config file path for the currently active
// profile.
func (pm *ProfileManager) GetActiveConfigPath() (string, error) {
return pm.impl.GetActiveConfigPath()
}
// GetActiveStateFilePath returns the state file path for the currently active
// profile.
func (pm *ProfileManager) GetActiveStateFilePath() (string, error) {
return pm.impl.GetActiveStateFilePath()
}
func fromMobileProfile(p *mobile.Profile) *Profile {
return &Profile{ID: p.ID, Name: p.Name, Email: p.Email, IsActive: p.IsActive}
}