Skip to content

Commit 28084cb

Browse files
authored
android: do not stop running on login, and edit prefs after startLogi… (#659)
android: do not stop running on login, and edit prefs after startLoginInteractive Previously: start, edit prefs with wantRunning=false, then startLoginInteractive Now: 1. editPrefs() with WantRunning=true, LoggedOut=false if AuthKey != null 2. start() -> boots tailscaled 3. startLoginInteractive() Do not call wantRunning=false; the route clearing issue requiring that is resolved. This also: -add deepCopy function which copies MaskedPrefs. Note that .copy() does not copy the non-constructor parameters -removes InternalExitNodePriorSet in MaskedPrefs, since this can't be set on the client Updates tailscale/corp#24002 Signed-off-by: kari-ts <[email protected]>
1 parent 296b582 commit 28084cb

File tree

2 files changed

+65
-45
lines changed

2 files changed

+65
-45
lines changed

android/src/main/java/com/tailscale/ipn/ui/model/Ipn.kt

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
package com.tailscale.ipn.ui.model
55

66
import android.net.Uri
7-
import java.util.UUID
87
import kotlinx.serialization.Serializable
98
import kotlinx.serialization.Transient
9+
import java.util.UUID
1010

1111
class Ipn {
1212

@@ -95,11 +95,11 @@ class Ipn {
9595
var ExitNodeIDSet: Boolean? = null,
9696
var ExitNodeAllowLANAccessSet: Boolean? = null,
9797
var WantRunningSet: Boolean? = null,
98+
var LoggedOutSet: Boolean? = null,
9899
var ShieldsUpSet: Boolean? = null,
99100
var AdvertiseRoutesSet: Boolean? = null,
100101
var ForceDaemonSet: Boolean? = null,
101102
var HostnameSet: Boolean? = null,
102-
var InternalExitNodePriorSet: Boolean? = null,
103103
) {
104104

105105
var ControlURL: String? = null
@@ -126,12 +126,6 @@ class Ipn {
126126
ExitNodeIDSet = true
127127
}
128128

129-
var InternalExitNodePrior: String? = null
130-
set(value) {
131-
field = value
132-
InternalExitNodePriorSet = true
133-
}
134-
135129
var ExitNodeAllowLANAccess: Boolean? = null
136130
set(value) {
137131
field = value
@@ -144,6 +138,12 @@ class Ipn {
144138
WantRunningSet = true
145139
}
146140

141+
var LoggedOut: Boolean? = null
142+
set(value) {
143+
field = value
144+
LoggedOutSet = true
145+
}
146+
147147
var ShieldsUp: Boolean? = null
148148
set(value) {
149149
field = value
@@ -238,3 +238,20 @@ class Persist {
238238
var Provider: String = "",
239239
)
240240
}
241+
242+
fun Ipn.MaskedPrefs.deepCopy(): Ipn.MaskedPrefs {
243+
return Ipn.MaskedPrefs().also {
244+
if (this.ControlURLSet == true) it.ControlURL = this.ControlURL
245+
if (this.RouteAllSet == true) it.RouteAll = this.RouteAll
246+
if (this.CorpDNSSet == true) it.CorpDNS = this.CorpDNS
247+
if (this.ExitNodeIDSet == true) it.ExitNodeID = this.ExitNodeID
248+
if (this.ExitNodeAllowLANAccessSet == true)
249+
it.ExitNodeAllowLANAccess = this.ExitNodeAllowLANAccess
250+
if (this.WantRunningSet == true) it.WantRunning = this.WantRunning
251+
if (this.LoggedOutSet == true) it.LoggedOut = this.LoggedOut
252+
if (this.ShieldsUpSet == true) it.ShieldsUp = this.ShieldsUp
253+
if (this.AdvertiseRoutesSet == true) it.AdvertiseRoutes = this.AdvertiseRoutes
254+
if (this.ForceDaemonSet == true) it.ForceDaemon = this.ForceDaemon
255+
if (this.HostnameSet == true) it.Hostname = this.Hostname
256+
}
257+
}

android/src/main/java/com/tailscale/ipn/ui/viewModel/IpnViewModel.kt

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.tailscale.ipn.ui.localapi.Client
1111
import com.tailscale.ipn.ui.model.Ipn
1212
import com.tailscale.ipn.ui.model.IpnLocal
1313
import com.tailscale.ipn.ui.model.UserID
14+
import com.tailscale.ipn.ui.model.deepCopy
1415
import com.tailscale.ipn.ui.notifier.Notifier
1516
import com.tailscale.ipn.ui.util.AdvertisedRoutesHelper
1617
import com.tailscale.ipn.ui.util.LoadingIndicator
@@ -144,52 +145,54 @@ open class IpnViewModel : ViewModel() {
144145

145146
// Login/Logout
146147

148+
/**
149+
* Order of operations:
150+
* 1. editPrefs() with maskedPrefs (to allow ControlURL override), WantRunning=true, LoggedOut=false if AuthKey != null
151+
* 2. start() starts the LocalBackend state machine
152+
* 3. startLoginInteractive() is currently required for bother interactive and non-interactive (using auth key) login
153+
*
154+
* Any failure short‑circuits the chain and invokes completionHandler once.
155+
*/
147156
fun login(
148157
maskedPrefs: Ipn.MaskedPrefs? = null,
149158
authKey: String? = null,
150159
completionHandler: (Result<Unit>) -> Unit = {}
151160
) {
161+
val client = Client(viewModelScope)
152162

153-
val loginAction = {
154-
Client(viewModelScope).startLoginInteractive { result ->
155-
result
156-
.onSuccess { TSLog.d(TAG, "Login started: $it") }
157-
.onFailure { TSLog.e(TAG, "Error starting login: ${it.message}") }
158-
completionHandler(result)
159-
}
160-
}
161-
162-
// Need to stop running before logging in to clear routes:
163-
// https://linear.app/tailscale/issue/ENG-3441/routesdns-is-not-cleared-when-switching-profiles-or-reauthenticating
164-
val stopThenLogin = {
165-
Client(viewModelScope).editPrefs(Ipn.MaskedPrefs().apply { WantRunning = false }) { result ->
166-
result
167-
.onSuccess { loginAction() }
168-
.onFailure { TSLog.e(TAG, "Error setting wantRunning to false: ${it.message}") }
169-
}
170-
}
171-
172-
val startAction = {
173-
Client(viewModelScope).start(Ipn.Options(AuthKey = authKey)) { start ->
174-
start.onFailure { completionHandler(Result.failure(it)) }.onSuccess { stopThenLogin() }
175-
}
163+
val finalMaskedPrefs = maskedPrefs?.deepCopy() ?: Ipn.MaskedPrefs()
164+
finalMaskedPrefs.WantRunning = true
165+
if (authKey != null) {
166+
finalMaskedPrefs.LoggedOut = false
176167
}
177168

178-
// If an MDM control URL is set, we will always use that in lieu of anything the user sets.
179-
var prefs = maskedPrefs
180-
val mdmControlURL = MDMSettings.loginURL.flow.value.value
181-
182-
if (mdmControlURL != null) {
183-
prefs = prefs ?: Ipn.MaskedPrefs()
184-
prefs.ControlURL = mdmControlURL
185-
TSLog.d(TAG, "Overriding control URL with MDM value: $mdmControlURL")
169+
client.editPrefs(finalMaskedPrefs) { editResult ->
170+
editResult
171+
.onFailure {
172+
TSLog.e(TAG, "editPrefs() failed: ${it.message}")
173+
completionHandler(Result.failure(it))
174+
}
175+
.onSuccess {
176+
val opts = Ipn.Options(UpdatePrefs = editResult.getOrThrow(), AuthKey = authKey)
177+
client.start(opts) { startResult ->
178+
startResult
179+
.onFailure {
180+
TSLog.e(TAG, "start() failed: ${it.message}")
181+
completionHandler(Result.failure(it))
182+
}
183+
.onSuccess {
184+
client.startLoginInteractive { loginResult ->
185+
loginResult
186+
.onFailure {
187+
TSLog.e(TAG, "startLoginInteractive() failed: ${it.message}")
188+
completionHandler(Result.failure(it))
189+
}
190+
.onSuccess { completionHandler(Result.success(Unit)) }
191+
}
192+
}
193+
}
194+
}
186195
}
187-
188-
prefs?.let {
189-
Client(viewModelScope).editPrefs(it) { result ->
190-
result.onFailure { completionHandler(Result.failure(it)) }.onSuccess { startAction() }
191-
}
192-
} ?: run { startAction() }
193196
}
194197

195198
fun loginWithAuthKey(authKey: String, completionHandler: (Result<Unit>) -> Unit = {}) {

0 commit comments

Comments
 (0)