mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-07 18:42:25 +00:00
fix(android): keep gateway pairing off main thread
This commit is contained in:
@@ -4,6 +4,8 @@ import ai.openclaw.app.chat.ChatMessage
|
||||
import ai.openclaw.app.chat.ChatPendingToolCall
|
||||
import ai.openclaw.app.chat.ChatSessionEntry
|
||||
import ai.openclaw.app.chat.OutgoingAttachment
|
||||
import ai.openclaw.app.gateway.DeviceAuthStore
|
||||
import ai.openclaw.app.gateway.DeviceIdentityStore
|
||||
import ai.openclaw.app.gateway.GatewayEndpoint
|
||||
import ai.openclaw.app.gateway.GatewayUpdateAvailableSummary
|
||||
import ai.openclaw.app.node.CameraCaptureManager
|
||||
@@ -280,9 +282,51 @@ class MainViewModel(
|
||||
prefs.setGatewayPassword(value)
|
||||
}
|
||||
|
||||
/** Clears setup credentials through the runtime so active gateway sessions drop stale auth state. */
|
||||
fun resetGatewaySetupAuth() {
|
||||
ensureRuntime().resetGatewaySetupAuth()
|
||||
/** Clears setup credentials without starting the runtime just to discard first-run pairing auth. */
|
||||
private fun resetGatewaySetupAuth() {
|
||||
runtimeRef.value?.resetGatewaySetupAuth() ?: resetGatewaySetupAuthWithoutRuntime()
|
||||
}
|
||||
|
||||
private fun resetGatewaySetupAuthWithoutRuntime() {
|
||||
prefs.clearGatewaySetupAuth()
|
||||
val deviceId = DeviceIdentityStore(nodeApp).loadOrCreate().deviceId
|
||||
val deviceAuthStore = DeviceAuthStore(prefs)
|
||||
deviceAuthStore.clearToken(deviceId, "node")
|
||||
deviceAuthStore.clearToken(deviceId, "operator")
|
||||
}
|
||||
|
||||
fun saveGatewayConfigAndConnect(
|
||||
host: String,
|
||||
port: Int,
|
||||
tls: Boolean,
|
||||
token: String,
|
||||
bootstrapToken: String,
|
||||
password: String,
|
||||
resetSetupAuth: Boolean,
|
||||
) {
|
||||
// Gateway pairing touches encrypted prefs, identity files, and sockets; keep
|
||||
// the whole sequence off the Compose thread so retries cannot trigger ANRs.
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
if (resetSetupAuth) {
|
||||
resetGatewaySetupAuth()
|
||||
}
|
||||
prefs.setManualEnabled(true)
|
||||
prefs.setManualHost(host)
|
||||
prefs.setManualPort(port)
|
||||
prefs.setManualTls(tls)
|
||||
prefs.setGatewayBootstrapToken(bootstrapToken)
|
||||
prefs.setGatewayToken(token)
|
||||
prefs.setGatewayPassword(password)
|
||||
ensureRuntime()
|
||||
.connect(
|
||||
GatewayEndpoint.manual(host = host, port = port),
|
||||
NodeRuntime.GatewayConnectAuth(
|
||||
token = token.ifEmpty { null },
|
||||
bootstrapToken = bootstrapToken.ifEmpty { null },
|
||||
password = password.ifEmpty { null },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Marks onboarding complete and starts the runtime before UI observes connected-state flows. */
|
||||
@@ -295,10 +339,12 @@ class MainViewModel(
|
||||
|
||||
/** Re-enters gateway setup after disconnecting and clearing one-time setup credentials. */
|
||||
fun pairNewGateway() {
|
||||
runtimeRef.value?.disconnect()
|
||||
resetGatewaySetupAuth()
|
||||
_startOnboardingAtGatewaySetup.value = true
|
||||
prefs.setOnboardingCompleted(false)
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
runtimeRef.value?.disconnect()
|
||||
resetGatewaySetupAuth()
|
||||
prefs.setOnboardingCompleted(false)
|
||||
_startOnboardingAtGatewaySetup.value = true
|
||||
}
|
||||
}
|
||||
|
||||
/** Acknowledges the one-shot request that opens onboarding at the gateway setup step. */
|
||||
@@ -394,13 +440,21 @@ class MainViewModel(
|
||||
}
|
||||
|
||||
fun refreshGatewayConnection() {
|
||||
ensureRuntime().refreshGatewayConnection()
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
ensureRuntime().refreshGatewayConnection()
|
||||
}
|
||||
}
|
||||
|
||||
fun connect(endpoint: GatewayEndpoint) {
|
||||
ensureRuntime().connect(endpoint)
|
||||
}
|
||||
|
||||
fun connectInBackground(endpoint: GatewayEndpoint) {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
ensureRuntime().connect(endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
fun connect(
|
||||
endpoint: GatewayEndpoint,
|
||||
token: String?,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package ai.openclaw.app.ui
|
||||
|
||||
import ai.openclaw.app.MainViewModel
|
||||
import ai.openclaw.app.gateway.GatewayEndpoint
|
||||
import ai.openclaw.app.ui.mobileCardSurface
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
@@ -291,27 +290,14 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
}
|
||||
|
||||
validationText = null
|
||||
if (inputMode == ConnectInputMode.SetupCode) {
|
||||
// Setup-code auth should replace old bootstrap/shared credentials;
|
||||
// manual reconnects keep existing typed credentials.
|
||||
viewModel.resetGatewaySetupAuth()
|
||||
}
|
||||
viewModel.setManualEnabled(true)
|
||||
viewModel.setManualHost(config.host)
|
||||
viewModel.setManualPort(config.port)
|
||||
viewModel.setManualTls(config.tls)
|
||||
viewModel.setGatewayBootstrapToken(config.bootstrapToken)
|
||||
if (config.token.isNotBlank()) {
|
||||
viewModel.setGatewayToken(config.token)
|
||||
} else if (config.bootstrapToken.isNotBlank()) {
|
||||
viewModel.setGatewayToken("")
|
||||
}
|
||||
viewModel.setGatewayPassword(config.password)
|
||||
viewModel.connect(
|
||||
GatewayEndpoint.manual(host = config.host, port = config.port),
|
||||
token = config.token.ifEmpty { null },
|
||||
bootstrapToken = config.bootstrapToken.ifEmpty { null },
|
||||
password = config.password.ifEmpty { null },
|
||||
viewModel.saveGatewayConfigAndConnect(
|
||||
host = config.host,
|
||||
port = config.port,
|
||||
tls = config.tls,
|
||||
token = config.token,
|
||||
bootstrapToken = config.bootstrapToken,
|
||||
password = config.password,
|
||||
resetSetupAuth = inputMode == ConnectInputMode.SetupCode,
|
||||
)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().height(52.dp),
|
||||
|
||||
@@ -3,7 +3,6 @@ package ai.openclaw.app.ui
|
||||
import ai.openclaw.app.LocationMode
|
||||
import ai.openclaw.app.MainViewModel
|
||||
import ai.openclaw.app.SensitiveFeatureConfig
|
||||
import ai.openclaw.app.gateway.GatewayEndpoint
|
||||
import ai.openclaw.app.node.DeviceNotificationListenerService
|
||||
import ai.openclaw.app.ui.design.ClawDesignTheme
|
||||
import ai.openclaw.app.ui.design.ClawErrorState
|
||||
@@ -254,7 +253,7 @@ fun OnboardingFlow(
|
||||
onUseNearby = {
|
||||
val endpoint = gateways.firstOrNull() ?: return@GatewaySetupScreen
|
||||
attemptedConnect = true
|
||||
viewModel.connect(endpoint)
|
||||
viewModel.connectInBackground(endpoint)
|
||||
step = OnboardingStep.Recovery
|
||||
},
|
||||
onPair = {
|
||||
@@ -275,21 +274,14 @@ fun OnboardingFlow(
|
||||
setupError = null
|
||||
attemptedConnect = true
|
||||
connectAttemptStartedAtMs = SystemClock.elapsedRealtime()
|
||||
// Setup-code pairing replaces any stale shared credentials before
|
||||
// the bootstrap token is stored for the first authenticated connect.
|
||||
viewModel.resetGatewaySetupAuth()
|
||||
viewModel.setManualEnabled(true)
|
||||
viewModel.setManualHost(config.host)
|
||||
viewModel.setManualPort(config.port)
|
||||
viewModel.setManualTls(config.tls)
|
||||
viewModel.setGatewayBootstrapToken(config.bootstrapToken)
|
||||
viewModel.setGatewayToken(config.token)
|
||||
viewModel.setGatewayPassword(config.password)
|
||||
viewModel.connect(
|
||||
GatewayEndpoint.manual(host = config.host, port = config.port),
|
||||
token = config.token.ifEmpty { null },
|
||||
bootstrapToken = config.bootstrapToken.ifEmpty { null },
|
||||
password = config.password.ifEmpty { null },
|
||||
viewModel.saveGatewayConfigAndConnect(
|
||||
host = config.host,
|
||||
port = config.port,
|
||||
tls = config.tls,
|
||||
token = config.token,
|
||||
bootstrapToken = config.bootstrapToken,
|
||||
password = config.password,
|
||||
resetSetupAuth = true,
|
||||
)
|
||||
step = OnboardingStep.Recovery
|
||||
},
|
||||
@@ -317,11 +309,14 @@ fun OnboardingFlow(
|
||||
token = token,
|
||||
password = password,
|
||||
) ?: return@GatewayRecoveryScreen
|
||||
viewModel.connect(
|
||||
GatewayEndpoint.manual(host = config.host, port = config.port),
|
||||
token = config.token.ifEmpty { null },
|
||||
bootstrapToken = config.bootstrapToken.ifEmpty { null },
|
||||
password = config.password.ifEmpty { null },
|
||||
viewModel.saveGatewayConfigAndConnect(
|
||||
host = config.host,
|
||||
port = config.port,
|
||||
tls = config.tls,
|
||||
token = config.token,
|
||||
bootstrapToken = config.bootstrapToken,
|
||||
password = config.password,
|
||||
resetSetupAuth = false,
|
||||
)
|
||||
},
|
||||
onEdit = { step = OnboardingStep.Gateway },
|
||||
|
||||
@@ -8,7 +8,6 @@ import ai.openclaw.app.LocationMode
|
||||
import ai.openclaw.app.MainViewModel
|
||||
import ai.openclaw.app.NotificationPackageFilterMode
|
||||
import ai.openclaw.app.chat.ChatPendingToolCall
|
||||
import ai.openclaw.app.gateway.GatewayEndpoint
|
||||
import ai.openclaw.app.node.DeviceNotificationListenerService
|
||||
import ai.openclaw.app.ui.design.ClawDetailRow
|
||||
import ai.openclaw.app.ui.design.ClawIconBadge
|
||||
@@ -897,18 +896,14 @@ private fun GatewaySettingsScreen(
|
||||
.orEmpty()
|
||||
.ifEmpty { passwordInput.trim() }
|
||||
validationText = null
|
||||
viewModel.setManualEnabled(true)
|
||||
viewModel.setManualHost(endpointConfig.host)
|
||||
viewModel.setManualPort(endpointConfig.port)
|
||||
viewModel.setManualTls(endpointConfig.tls)
|
||||
viewModel.setGatewayBootstrapToken(bootstrapToken)
|
||||
viewModel.setGatewayToken(token)
|
||||
viewModel.setGatewayPassword(password)
|
||||
viewModel.connect(
|
||||
GatewayEndpoint.manual(host = endpointConfig.host, port = endpointConfig.port),
|
||||
token = token.ifEmpty { null },
|
||||
bootstrapToken = bootstrapToken.ifEmpty { null },
|
||||
password = password.ifEmpty { null },
|
||||
viewModel.saveGatewayConfigAndConnect(
|
||||
host = endpointConfig.host,
|
||||
port = endpointConfig.port,
|
||||
tls = endpointConfig.tls,
|
||||
token = token,
|
||||
bootstrapToken = bootstrapToken,
|
||||
password = password,
|
||||
resetSetupAuth = setup != null,
|
||||
)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
||||
Reference in New Issue
Block a user