diff --git a/android/src/main/java/com/tailscale/ipn/ui/model/Ipn.kt b/android/src/main/java/com/tailscale/ipn/ui/model/Ipn.kt index 338b7a9..e533ab8 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/model/Ipn.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/model/Ipn.kt @@ -95,6 +95,7 @@ class Ipn { var ExitNodeIDSet: Boolean? = null, var ExitNodeAllowLANAccessSet: Boolean? = null, var WantRunningSet: Boolean? = null, + var LoggedOutSet: Boolean? = null, var ShieldsUpSet: Boolean? = null, var AdvertiseRoutesSet: Boolean? = null, var ForceDaemonSet: Boolean? = null, @@ -144,6 +145,12 @@ class Ipn { WantRunningSet = true } + var LoggedOut: Boolean? = null + set (value) { + field = value + LoggedOutSet = true + } + var ShieldsUp: Boolean? = null set(value) { field = value diff --git a/android/src/main/java/com/tailscale/ipn/ui/viewModel/IpnViewModel.kt b/android/src/main/java/com/tailscale/ipn/ui/viewModel/IpnViewModel.kt index 20230f6..08b0da6 100644 --- a/android/src/main/java/com/tailscale/ipn/ui/viewModel/IpnViewModel.kt +++ b/android/src/main/java/com/tailscale/ipn/ui/viewModel/IpnViewModel.kt @@ -144,52 +144,60 @@ open class IpnViewModel : ViewModel() { // Login/Logout + /** + * Order of operations: + * 1. editPrefs() with WantRunning=true, LoggedOut=false if AuthKey != null + * 2. start() -> boots tailscaled; if an AuthKey was provided and WantRunning/LoggedOut were set + * to true/false in step 1, backend will attempt to log in immediately. + * 3. startLoginInteractive() if LoggedOut wasn't set to false in step 1 and the user will be + * directed to a URL to complete authentication + * + * Any failure short‑circuits the chain and invokes completionHandler once. + */ fun login( maskedPrefs: Ipn.MaskedPrefs? = null, authKey: String? = null, completionHandler: (Result) -> Unit = {} ) { + val client = Client(viewModelScope) - val loginAction = { - Client(viewModelScope).startLoginInteractive { result -> - result - .onSuccess { TSLog.d(TAG, "Login started: $it") } - .onFailure { TSLog.e(TAG, "Error starting login: ${it.message}") } - completionHandler(result) - } - } - - // Need to stop running before logging in to clear routes: - // https://linear.app/tailscale/issue/ENG-3441/routesdns-is-not-cleared-when-switching-profiles-or-reauthenticating - val stopThenLogin = { - Client(viewModelScope).editPrefs(Ipn.MaskedPrefs().apply { WantRunning = false }) { result -> - result - .onSuccess { loginAction() } - .onFailure { TSLog.e(TAG, "Error setting wantRunning to false: ${it.message}") } - } - } - - val startAction = { - Client(viewModelScope).start(Ipn.Options(AuthKey = authKey)) { start -> - start.onFailure { completionHandler(Result.failure(it)) }.onSuccess { stopThenLogin() } - } + val finalMaskedPrefs = maskedPrefs?.copy() ?: Ipn.MaskedPrefs() + finalMaskedPrefs.WantRunning = true + if (authKey != null) { + finalMaskedPrefs.LoggedOut = false } - // If an MDM control URL is set, we will always use that in lieu of anything the user sets. - var prefs = maskedPrefs - val mdmControlURL = MDMSettings.loginURL.flow.value.value - - if (mdmControlURL != null) { - prefs = prefs ?: Ipn.MaskedPrefs() - prefs.ControlURL = mdmControlURL - TSLog.d(TAG, "Overriding control URL with MDM value: $mdmControlURL") + client.editPrefs(finalMaskedPrefs) { editResult -> + editResult + .onFailure { + TSLog.e(TAG, "editPrefs() failed: ${it.message}") + completionHandler(Result.failure(it)) + } + .onSuccess { + val opts = Ipn.Options(UpdatePrefs = editResult.getOrThrow(), AuthKey = authKey) + client.start(opts) { startResult -> + startResult + .onFailure { + TSLog.e(TAG, "start() failed: ${it.message}") + completionHandler(Result.failure(it)) + } + .onSuccess { + if (authKey.isNullOrEmpty()) { + client.startLoginInteractive { loginResult -> + loginResult + .onFailure { + TSLog.e(TAG, "startLoginInteractive() failed: ${it.message}") + completionHandler(Result.failure(it)) + } + .onSuccess { completionHandler(Result.success(Unit)) } + } + } else { + completionHandler(Result.success(Unit)) + } + } + } + } } - - prefs?.let { - Client(viewModelScope).editPrefs(it) { result -> - result.onFailure { completionHandler(Result.failure(it)) }.onSuccess { startAction() } - } - } ?: run { startAction() } } fun loginWithAuthKey(authKey: String, completionHandler: (Result) -> Unit = {}) {