diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt index 4b483ba28f5..0f554a0a651 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt @@ -242,9 +242,12 @@ fun ConnectTabScreen(viewModel: MainViewModel) { resolveGatewayConnectConfig( useSetupCode = inputMode == ConnectInputMode.SetupCode, setupCode = setupCode, - manualHost = manualHostInput, - manualPort = manualPortInput, - manualTls = manualTlsInput, + savedManualHost = manualHost, + savedManualPort = manualPort.toString(), + savedManualTls = manualTls, + manualHostInput = manualHostInput, + manualPortInput = manualPortInput, + manualTlsInput = manualTlsInput, fallbackBootstrapToken = gatewayBootstrapToken, fallbackToken = gatewayToken, fallbackPassword = passwordInput, diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt index adfc32f1cab..15fbfa4f347 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayConfigResolver.kt @@ -37,9 +37,12 @@ private val gatewaySetupJson = Json { ignoreUnknownKeys = true } internal fun resolveGatewayConnectConfig( useSetupCode: Boolean, setupCode: String, - manualHost: String, - manualPort: String, - manualTls: Boolean, + savedManualHost: String, + savedManualPort: String, + savedManualTls: Boolean, + manualHostInput: String, + manualPortInput: String, + manualTlsInput: Boolean, fallbackBootstrapToken: String, fallbackToken: String, fallbackPassword: String, @@ -70,18 +73,23 @@ internal fun resolveGatewayConnectConfig( ) } - val manualUrl = composeGatewayManualUrl(manualHost, manualPort, manualTls) ?: return null + val manualUrl = composeGatewayManualUrl(manualHostInput, manualPortInput, manualTlsInput) ?: return null val parsed = parseGatewayEndpoint(manualUrl) ?: return null + val savedManualEndpoint = + composeGatewayManualUrl(savedManualHost, savedManualPort, savedManualTls) + ?.let(::parseGatewayEndpoint) + val preserveBootstrapToken = + savedManualEndpoint != null && + savedManualEndpoint.host == parsed.host && + savedManualEndpoint.port == parsed.port && + savedManualEndpoint.tls == parsed.tls && + fallbackToken.isBlank() && + fallbackPassword.isBlank() return GatewayConnectConfig( host = parsed.host, port = parsed.port, tls = parsed.tls, - bootstrapToken = - if (fallbackToken.isBlank() && fallbackPassword.isBlank()) { - fallbackBootstrapToken.trim() - } else { - "" - }, + bootstrapToken = if (preserveBootstrapToken) fallbackBootstrapToken.trim() else "", token = fallbackToken.trim(), password = fallbackPassword.trim(), ) diff --git a/apps/android/app/src/test/java/ai/openclaw/app/ui/GatewayConfigResolverTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/ui/GatewayConfigResolverTest.kt index d6c6c22a8fb..34764625e71 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/ui/GatewayConfigResolverTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/ui/GatewayConfigResolverTest.kt @@ -155,9 +155,12 @@ class GatewayConfigResolverTest { resolveGatewayConnectConfig( useSetupCode = true, setupCode = setupCode, - manualHost = "", - manualPort = "", - manualTls = true, + savedManualHost = "", + savedManualPort = "", + savedManualTls = true, + manualHostInput = "", + manualPortInput = "", + manualTlsInput = true, fallbackBootstrapToken = "", fallbackToken = "shared-token", fallbackPassword = "shared-password", @@ -180,9 +183,12 @@ class GatewayConfigResolverTest { resolveGatewayConnectConfig( useSetupCode = true, setupCode = setupCode, - manualHost = "", - manualPort = "", - manualTls = true, + savedManualHost = "", + savedManualPort = "", + savedManualTls = true, + manualHostInput = "", + manualPortInput = "", + manualTlsInput = true, fallbackBootstrapToken = "", fallbackToken = "shared-token", fallbackPassword = "shared-password", @@ -202,9 +208,12 @@ class GatewayConfigResolverTest { resolveGatewayConnectConfig( useSetupCode = false, setupCode = "", - manualHost = "192.168.31.100", - manualPort = "18789", - manualTls = false, + savedManualHost = "192.168.31.100", + savedManualPort = "18789", + savedManualTls = false, + manualHostInput = "192.168.31.100", + manualPortInput = "18789", + manualTlsInput = false, fallbackBootstrapToken = "bootstrap-1", fallbackToken = "", fallbackPassword = "", @@ -224,9 +233,12 @@ class GatewayConfigResolverTest { resolveGatewayConnectConfig( useSetupCode = false, setupCode = "", - manualHost = "192.168.31.100", - manualPort = "18789", - manualTls = false, + savedManualHost = "192.168.31.100", + savedManualPort = "18789", + savedManualTls = false, + manualHostInput = "192.168.31.100", + manualPortInput = "18789", + manualTlsInput = false, fallbackBootstrapToken = "bootstrap-1", fallbackToken = "", fallbackPassword = "password-1", @@ -237,6 +249,27 @@ class GatewayConfigResolverTest { assertEquals("password-1", resolved?.password) } + @Test + fun resolveGatewayConnectConfigManualDropsBootstrapTokenWhenEndpointChanges() { + val resolved = + resolveGatewayConnectConfig( + useSetupCode = false, + setupCode = "", + savedManualHost = "192.168.31.100", + savedManualPort = "18789", + savedManualTls = false, + manualHostInput = "192.168.31.101", + manualPortInput = "18789", + manualTlsInput = false, + fallbackBootstrapToken = "bootstrap-1", + fallbackToken = "", + fallbackPassword = "", + ) + + assertEquals("", resolved?.bootstrapToken) + assertEquals("192.168.31.101", resolved?.host) + } + private fun encodeSetupCode(payloadJson: String): String { return Base64.getUrlEncoder().withoutPadding().encodeToString(payloadJson.toByteArray(Charsets.UTF_8)) }