Files
evgeniyChepelev 0327624b30 Multi-profiles (#80)
* fix layout on ipad

* Create multi profiles

* multi-profile server config, connection cache, and auth fixes

 Split ProfilesView into ProfilesListView, AddProfileSheet, ProfileBadge, AddProfileViewModel
- Add server URL + setup key fields to Add Profile screen
- Store per-profile connection data (ip/fqdn/managementURL) as typed model in ProfileConnectionCache
- Show cached connection info immediately on profile switch; empty if no prior data
- Fix profile deletion persistence via tombstone in profiles.json
- Fix logout to remove both netbird.cfg and state.json (cfg holds auth tokens)
- Preserve managementURL in UI after logout via cache fallback
- Guard polling from overwriting new profile's data during disconnect/reconnect cycle

* fix(multi-profile): reinitialize VPN adapter on profile switch and show current server URL

* Update project.pbxproj

* fix(multi-profile): prevent default server overwrite on logout/re-login

* Update PacketTunnelProvider.swift

* fix(multi-profile): address code review findings

- Normalize only scheme and host to lowercase when saving management
  server URL; previously the full URL was lowercased which could corrupt
  case-sensitive paths on self-hosted servers
- Move switchConnectionInfo(to:) inside the do-block so the connection
  UI is only updated after a successful profile switch
- Add ProfileConnectionCache.remove(for:) and clearConnectionData(for:)
  to prevent stale ip/fqdn/managementURL from persisting after a profile
  is deleted or logged out; call them from removeProfile and logoutProfile
- Use ASWebAuthenticationSession.Callback.customScheme on iOS 17.4+,
  falling back to the deprecated callbackURLScheme initializer on older
  versions; add a comment explaining why "http" is used and that a
  proper fix requires custom-scheme support in the SDK
- Guard presentationAnchor against a missing key window with
  assertionFailure in debug builds instead of silently returning an
  empty UIWindow
2026-04-22 10:06:51 +02:00

84 lines
2.8 KiB
Swift

//
// AddProfileViewModel.swift
// NetBird
//
import Foundation
#if os(iOS)
@MainActor
class AddProfileViewModel: ObservableObject {
@Published var isLoading = false
@Published var isSuccess = false
@Published var profileError: String?
@Published var urlError: String?
@Published var setupKeyError: String?
@Published var generalError: String?
@Published var ssoNotSupportedError: String?
private let defaultManagementServerUrl = "https://api.netbird.io"
func create(name: String, serverUrl: String, setupKey: String) {
clearErrors()
// 1. Create profile directory
do {
try ProfileManager.shared.addProfile(name)
} catch {
profileError = error.localizedDescription
return
}
// 2. Get config path for the new profile
guard let configPath = ProfileManager.shared.configPath(for: name) else {
profileError = "Unable to access profile directory"
try? ProfileManager.shared.removeProfile(name)
return
}
// 3. Configure the management server for this profile
let serverVM = ServerViewModel(configurationFilePath: configPath, deviceName: Device.getName())
let trimmed = serverUrl.trimmingCharacters(in: .whitespacesAndNewlines)
var urlComponents = URLComponents(string: trimmed)
if let scheme = urlComponents?.scheme { urlComponents?.scheme = scheme.lowercased() }
if let host = urlComponents?.host { urlComponents?.host = host.lowercased() }
let trimmedUrl = urlComponents?.string ?? trimmed
let finalUrl = trimmedUrl.isEmpty ? defaultManagementServerUrl : trimmedUrl
let key = setupKey.trimmingCharacters(in: .whitespacesAndNewlines)
isLoading = true
Task {
if !key.isEmpty {
await serverVM.loginWithSetupKey(managementServerUrl: finalUrl, setupKey: key)
} else {
await serverVM.changeManagementServerAddress(managementServerUrl: finalUrl)
}
isLoading = false
if serverVM.isOperationSuccessful {
isSuccess = true
} else {
// Surface errors and rollback profile creation
urlError = serverVM.viewErrors.urlError
setupKeyError = serverVM.viewErrors.setupKeyError
generalError = serverVM.viewErrors.generalError
ssoNotSupportedError = serverVM.viewErrors.ssoNotSupportedError
try? ProfileManager.shared.removeProfile(name)
}
}
}
func clearErrors() {
profileError = nil
urlError = nil
setupKeyError = nil
generalError = nil
ssoNotSupportedError = nil
}
}
#endif