From b5801d628ba6ab4f8e66c768cd81caea251dca7d Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Mon, 16 Feb 2026 17:20:30 -0300 Subject: [PATCH 1/2] Add lazy connection toggle implementation This depends on NetBirdSDK at least at commit 1024d4569, which exports lazy connection variables (whether if it's enabled and the inactivity threshold) both for Android and iOS --- .../Source/App/ViewModels/MainViewModel.swift | 19 +++++++++++++-- NetBird/Source/App/Views/AdvancedView.swift | 24 +++++++++++++++---- NetbirdKit/EnvVarPackager.swift | 8 ++++++- NetbirdKit/GlobalConstants.swift | 2 ++ 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/NetBird/Source/App/ViewModels/MainViewModel.swift b/NetBird/Source/App/ViewModels/MainViewModel.swift index b476bb9..3d8fbd4 100644 --- a/NetBird/Source/App/ViewModels/MainViewModel.swift +++ b/NetBird/Source/App/ViewModels/MainViewModel.swift @@ -99,7 +99,8 @@ class ViewModel: ObservableObject { } } @Published var forceRelayConnection = true - @Published var showForceRelayAlert = false + @Published var enableLazyConnection = true + @Published var showConfigChangeAlert = false @Published var showRosenpassChangedAlert = false @Published var networkUnavailable = false @@ -130,6 +131,7 @@ class ViewModel: ObservableObject { // forceRelayConnection uses UserDefaults (not SDK), so it's safe to load during init self.forceRelayConnection = self.getForcedRelayConnectionEnabled() + self.enableLazyConnection = self.getLazyConnectionEnabled() $setupKey .removeDuplicates() @@ -371,7 +373,7 @@ class ViewModel: ObservableObject { let userDefaults = UserDefaults(suiteName: GlobalConstants.userPreferencesSuiteName) userDefaults?.set(isEnabled, forKey: GlobalConstants.keyForceRelayConnection) self.forceRelayConnection = isEnabled - self.showForceRelayAlert = true + self.showConfigChangeAlert = true } func getForcedRelayConnectionEnabled() -> Bool { @@ -386,6 +388,19 @@ class ViewModel: ObservableObject { #endif } + func setEnableLazyConnection(isEnabled: Bool) { + let userDefaults = UserDefaults(suiteName: GlobalConstants.userPreferencesSuiteName) + userDefaults?.set(isEnabled, forKey: GlobalConstants.keyEnableLazyConnection) + self.enableLazyConnection = isEnabled + self.showConfigChangeAlert = true + } + + func getLazyConnectionEnabled() -> Bool { + let userDefaults = UserDefaults(suiteName: GlobalConstants.userPreferencesSuiteName) + userDefaults?.register(defaults: [GlobalConstants.keyEnableLazyConnection: true]) + return userDefaults?.bool(forKey: GlobalConstants.keyEnableLazyConnection) ?? true + } + func getDefaultStatus() -> StatusDetails { return StatusDetails(ip: "", fqdn: "", managementStatus: .disconnected, peerInfo: []) } diff --git a/NetBird/Source/App/Views/AdvancedView.swift b/NetBird/Source/App/Views/AdvancedView.swift index f8f8ecf..8da9766 100644 --- a/NetBird/Source/App/Views/AdvancedView.swift +++ b/NetBird/Source/App/Views/AdvancedView.swift @@ -131,6 +131,20 @@ struct AdvancedView: View { viewModel.setForcedRelayConnection(isEnabled: value) } + Toggle(isOn: $viewModel.enableLazyConnection) { + Text("Enable lazy connection") + .multilineTextAlignment(.leading) + .font(.system(size: 18, weight: .regular)) + .foregroundColor(Color("TextSecondary")) + .padding(.top, 3) + .fixedSize(horizontal: false, vertical: true) + } + .toggleStyle(SwitchToggleStyle(tint: .orange)) + .padding(.top, 10) + .onChange(of: viewModel.enableLazyConnection) { value in + viewModel.setEnableLazyConnection(isEnabled: value) + } + Spacer() } .padding([.leading, .trailing], UIScreen.main.bounds.width * 0.10) @@ -144,10 +158,10 @@ struct AdvancedView: View { LogLevelAlert() } - alertOverlay(isPresented: viewModel.showForceRelayAlert, onDismiss: { - viewModel.showForceRelayAlert = false + alertOverlay(isPresented: viewModel.showConfigChangeAlert, onDismiss: { + viewModel.showConfigChangeAlert = false }) { - ForceRelayAlert() + ConfigChangeAlert() } } .onAppear { @@ -293,7 +307,7 @@ struct AdvancedView: View { } } -struct ForceRelayAlert: View { +struct ConfigChangeAlert: View { @EnvironmentObject var viewModel: ViewModel var body: some View { @@ -308,7 +322,7 @@ struct ForceRelayAlert: View { HStack { Spacer() Button(action: { - viewModel.showForceRelayAlert = false + viewModel.showConfigChangeAlert = false }) { Text("OK") .padding() diff --git a/NetbirdKit/EnvVarPackager.swift b/NetbirdKit/EnvVarPackager.swift index 817d6e2..b9765af 100644 --- a/NetbirdKit/EnvVarPackager.swift +++ b/NetbirdKit/EnvVarPackager.swift @@ -24,8 +24,14 @@ class EnvVarPackager { defaults.register(defaults: [GlobalConstants.keyForceRelayConnection: defaultForceRelay]) let forceRelayConnection = defaults.bool(forKey: GlobalConstants.keyForceRelayConnection) + let defaultLazyConnection = true + defaults.register(defaults: [GlobalConstants.keyEnableLazyConnection: defaultLazyConnection]) + let isLazyConnectionEnabled = defaults.bool(forKey: GlobalConstants.keyEnableLazyConnection) + envList.put(NetBirdSDKGetEnvKeyNBForceRelay(), value: String(forceRelayConnection)) - + envList.put(NetBirdSDKGetEnvKeyNBLazyConn(), value: String(isLazyConnectionEnabled)) + envList.put(NetBirdSDKGetEnvKeyNBInactivityThreshold(), value: String(5)) + return envList } } diff --git a/NetbirdKit/GlobalConstants.swift b/NetbirdKit/GlobalConstants.swift index ba7cdeb..cf77dda 100644 --- a/NetbirdKit/GlobalConstants.swift +++ b/NetbirdKit/GlobalConstants.swift @@ -13,6 +13,8 @@ struct GlobalConstants { #endif static let keyForceRelayConnection = "isConnectionForceRelayed" + static let keyEnableLazyConnection = "isLazyConnectionEnabled" + static let keyLoginRequired = "netbird.loginRequired" static let keyNetworkUnavailable = "netbird.networkUnavailable" From c609d2440e90be003bce43ff569a507f5d9421d2 Mon Sep 17 00:00:00 2001 From: Diego Romar Date: Mon, 16 Feb 2026 17:24:05 -0300 Subject: [PATCH 2/2] Add unit tests for enabling lazy connection --- NetBirdTests/GlobalConstantsTests.swift | 4 ++++ NetBirdTests/SharedUserDefaultsTests.swift | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/NetBirdTests/GlobalConstantsTests.swift b/NetBirdTests/GlobalConstantsTests.swift index 3902cc1..fccf331 100644 --- a/NetBirdTests/GlobalConstantsTests.swift +++ b/NetBirdTests/GlobalConstantsTests.swift @@ -12,6 +12,10 @@ final class GlobalConstantsTests: XCTestCase { XCTAssertEqual(GlobalConstants.keyForceRelayConnection, "isConnectionForceRelayed") } + func testEnableLazyConnectionKey() { + XCTAssertEqual(GlobalConstants.keyEnableLazyConnection, "isLazyConnectionEnabled") + } + func testLoginRequiredKey() { XCTAssertEqual(GlobalConstants.keyLoginRequired, "netbird.loginRequired") } diff --git a/NetBirdTests/SharedUserDefaultsTests.swift b/NetBirdTests/SharedUserDefaultsTests.swift index c3c60bb..b1f681b 100644 --- a/NetBirdTests/SharedUserDefaultsTests.swift +++ b/NetBirdTests/SharedUserDefaultsTests.swift @@ -21,6 +21,7 @@ final class SharedUserDefaultsTests: XCTestCase { override func tearDown() { userDefaults?.removeObject(forKey: GlobalConstants.keyLoginRequired) userDefaults?.removeObject(forKey: GlobalConstants.keyForceRelayConnection) + userDefaults?.removeObject(forKey: GlobalConstants.keyEnableLazyConnection) super.tearDown() } @@ -58,4 +59,12 @@ final class SharedUserDefaultsTests: XCTestCase { let value = defaults.bool(forKey: GlobalConstants.keyForceRelayConnection) XCTAssertTrue(value, "Force relay connection should default to true") } + + func testEnableLazyConnectionDefaultsToTrue() throws { + let defaults = try XCTUnwrap(userDefaults) + defaults.removeObject(forKey: GlobalConstants.keyEnableLazyConnection) + defaults.register(defaults: [GlobalConstants.keyEnableLazyConnection: true]) + let value = defaults.bool(forKey: GlobalConstants.keyEnableLazyConnection) + XCTAssertTrue(value, "Enable lazy connection should default to true") + } }