mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
fix(android): stabilize gateway operator reconnect
This commit is contained in:
@@ -144,10 +144,6 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
|
||||
runtime.setTalkEnabled(enabled)
|
||||
}
|
||||
|
||||
fun logGatewayDebugSnapshot(source: String = "manual") {
|
||||
runtime.logGatewayDebugSnapshot(source)
|
||||
}
|
||||
|
||||
fun refreshGatewayConnection() {
|
||||
runtime.refreshGatewayConnection()
|
||||
}
|
||||
|
||||
@@ -328,13 +328,20 @@ class NodeRuntime(context: Context) {
|
||||
|
||||
private fun updateStatus() {
|
||||
_isConnected.value = operatorConnected
|
||||
val operator = operatorStatusText.trim()
|
||||
val node = nodeStatusText.trim()
|
||||
_statusText.value =
|
||||
when {
|
||||
operatorConnected && _nodeConnected.value -> "Connected"
|
||||
operatorConnected && !_nodeConnected.value -> "Connected (node offline)"
|
||||
!operatorConnected && _nodeConnected.value -> "Connected (operator offline)"
|
||||
operatorStatusText.isNotBlank() && operatorStatusText != "Offline" -> operatorStatusText
|
||||
else -> nodeStatusText
|
||||
!operatorConnected && _nodeConnected.value ->
|
||||
if (operator.isNotEmpty() && operator != "Offline") {
|
||||
"Connected (operator: $operator)"
|
||||
} else {
|
||||
"Connected (operator offline)"
|
||||
}
|
||||
operator.isNotBlank() && operator != "Offline" -> operator
|
||||
else -> node
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,17 +621,14 @@ class NodeRuntime(context: Context) {
|
||||
prefs.setTalkEnabled(value)
|
||||
}
|
||||
|
||||
fun logGatewayDebugSnapshot(source: String = "manual") {
|
||||
val flowToken = gatewayToken.value.trim()
|
||||
val loadedToken = prefs.loadGatewayToken().orEmpty()
|
||||
Log.i(
|
||||
"OpenClawGatewayDebug",
|
||||
"source=$source manualEnabled=${manualEnabled.value} host=${manualHost.value} port=${manualPort.value} tls=${manualTls.value} flowTokenLen=${flowToken.length} loadTokenLen=${loadedToken.length} connected=${isConnected.value} status=${statusText.value}",
|
||||
)
|
||||
}
|
||||
|
||||
fun refreshGatewayConnection() {
|
||||
val endpoint = connectedEndpoint ?: return
|
||||
val endpoint =
|
||||
connectedEndpoint ?: run {
|
||||
_statusText.value = "Failed: no cached gateway endpoint"
|
||||
return
|
||||
}
|
||||
operatorStatusText = "Connecting…"
|
||||
updateStatus()
|
||||
val token = prefs.loadGatewayToken()
|
||||
val password = prefs.loadGatewayPassword()
|
||||
val tls = connectionManager.resolveTlsParams(endpoint)
|
||||
|
||||
@@ -62,6 +62,11 @@ class GatewaySession(
|
||||
private val onInvoke: (suspend (InvokeRequest) -> InvokeResult)? = null,
|
||||
private val onTlsFingerprint: ((stableId: String, fingerprint: String) -> Unit)? = null,
|
||||
) {
|
||||
private companion object {
|
||||
// Keep connect timeout above observed gateway unauthorized close on lower-end devices.
|
||||
private const val CONNECT_RPC_TIMEOUT_MS = 12_000L
|
||||
}
|
||||
|
||||
data class InvokeRequest(
|
||||
val id: String,
|
||||
val nodeId: String,
|
||||
@@ -302,26 +307,13 @@ class GatewaySession(
|
||||
val identity = identityStore.loadOrCreate()
|
||||
val storedToken = deviceAuthStore.loadToken(identity.deviceId, options.role)
|
||||
val trimmedToken = token?.trim().orEmpty()
|
||||
val authToken = if (storedToken.isNullOrBlank()) trimmedToken else storedToken
|
||||
// QR/setup/manual shared token must take precedence; stale role tokens can survive re-onboarding.
|
||||
val authToken = if (trimmedToken.isNotBlank()) trimmedToken else storedToken.orEmpty()
|
||||
val payload = buildConnectParams(identity, connectNonce, authToken, password?.trim())
|
||||
var res = request("connect", payload, timeoutMs = 8_000)
|
||||
val res = request("connect", payload, timeoutMs = CONNECT_RPC_TIMEOUT_MS)
|
||||
if (!res.ok) {
|
||||
val msg = res.error?.message ?: "connect failed"
|
||||
val hasStoredToken = !storedToken.isNullOrBlank()
|
||||
val canRetryWithShared = hasStoredToken && trimmedToken.isNotBlank()
|
||||
if (canRetryWithShared) {
|
||||
val sharedPayload = buildConnectParams(identity, connectNonce, trimmedToken, password?.trim())
|
||||
val sharedRes = request("connect", sharedPayload, timeoutMs = 8_000)
|
||||
if (!sharedRes.ok) {
|
||||
val retryMsg = sharedRes.error?.message ?: msg
|
||||
throw IllegalStateException(retryMsg)
|
||||
}
|
||||
// Stored device token was bypassed successfully; clear stale token for future connects.
|
||||
deviceAuthStore.clearToken(identity.deviceId, options.role)
|
||||
res = sharedRes
|
||||
} else {
|
||||
throw IllegalStateException(msg)
|
||||
}
|
||||
throw IllegalStateException(msg)
|
||||
}
|
||||
handleConnectSuccess(res, identity.deviceId)
|
||||
connectDeferred.complete(Unit)
|
||||
|
||||
@@ -168,6 +168,11 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
validationText = null
|
||||
return@Button
|
||||
}
|
||||
if (statusText.contains("operator offline", ignoreCase = true)) {
|
||||
validationText = null
|
||||
viewModel.refreshGatewayConnection()
|
||||
return@Button
|
||||
}
|
||||
|
||||
val config =
|
||||
resolveGatewayConnectConfig(
|
||||
@@ -397,15 +402,6 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
|
||||
HorizontalDivider(color = mobileBorder)
|
||||
|
||||
Text(
|
||||
"Debug snapshot: mode=${if (inputMode == ConnectInputMode.SetupCode) "setup" else "manual"}, manualEnabled=$manualEnabled, tokenLen=${gatewayToken.trim().length}",
|
||||
style = mobileCaption1,
|
||||
color = mobileTextSecondary,
|
||||
)
|
||||
TextButton(onClick = { viewModel.logGatewayDebugSnapshot(source = "connect_tab") }) {
|
||||
Text("Log gateway debug snapshot", style = mobileCallout.copy(fontWeight = FontWeight.SemiBold), color = mobileAccent)
|
||||
}
|
||||
|
||||
TextButton(onClick = { viewModel.setOnboardingCompleted(false) }) {
|
||||
Text("Run onboarding again", style = mobileCallout.copy(fontWeight = FontWeight.SemiBold), color = mobileAccent)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user