diff --git a/apps/android/app/src/main/java/ai/openclaw/android/gateway/GatewaySession.kt b/apps/android/app/src/main/java/ai/openclaw/android/gateway/GatewaySession.kt index b7040d2ae27..0f8bb1ac7ed 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/gateway/GatewaySession.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/gateway/GatewaySession.kt @@ -336,7 +336,7 @@ class GatewaySession( deviceAuthStore.saveToken(deviceId, authRole, deviceToken) } val rawCanvas = obj["canvasHostUrl"].asStringOrNull() - canvasHostUrl = normalizeCanvasHostUrl(rawCanvas, endpoint) + canvasHostUrl = normalizeCanvasHostUrl(rawCanvas, endpoint, isTlsConnection = tls != null) val sessionDefaults = obj["snapshot"].asObjectOrNull() ?.get("sessionDefaults").asObjectOrNull() @@ -625,24 +625,33 @@ class GatewaySession( return parts.joinToString("|") } - private fun normalizeCanvasHostUrl(raw: String?, endpoint: GatewayEndpoint): String? { + private fun normalizeCanvasHostUrl( + raw: String?, + endpoint: GatewayEndpoint, + isTlsConnection: Boolean, + ): String? { val trimmed = raw?.trim().orEmpty() val parsed = trimmed.takeIf { it.isNotBlank() }?.let { runCatching { java.net.URI(it) }.getOrNull() } val host = parsed?.host?.trim().orEmpty() val port = parsed?.port ?: -1 val scheme = parsed?.scheme?.trim().orEmpty().ifBlank { "http" } - // Detect TLS reverse proxy: endpoint on port 443, or domain-based host - val tls = endpoint.port == 443 || endpoint.host.contains(".") - - // If raw URL is a non-loopback address AND we're behind TLS reverse proxy, - // fix the port (gateway sends its internal port like 18789, but we need 443 via Caddy) + // If raw URL is a non-loopback address and this connection uses TLS, + // normalize scheme/port to the endpoint we actually connected to. if (trimmed.isNotBlank() && !isLoopbackHost(host)) { - if (tls && port > 0 && port != 443) { - // Rewrite the URL to use the reverse proxy port instead of the raw gateway port + val needsTlsRewrite = + isTlsConnection && + ( + !scheme.equals("https", ignoreCase = true) || + (port > 0 && port != endpoint.port) || + (port <= 0 && endpoint.port != 443) + ) + if (needsTlsRewrite) { val fixedScheme = "https" val formattedHost = if (host.contains(":")) "[${host}]" else host - return "$fixedScheme://$formattedHost" + val fixedPort = endpoint.port + val portSuffix = if (fixedPort == 443) "" else ":$fixedPort" + return "$fixedScheme://$formattedHost$portSuffix" } return trimmed } @@ -653,11 +662,10 @@ class GatewaySession( ?: endpoint.host.trim() if (fallbackHost.isEmpty()) return trimmed.ifBlank { null } - // When connecting through a reverse proxy (TLS on standard port), use the - // connection endpoint's scheme and port instead of the raw canvas port. - val fallbackScheme = if (tls) "https" else scheme - // Behind reverse proxy, always use the proxy port (443), not the raw canvas port - val fallbackPort = if (tls) endpoint.port else (endpoint.canvasPort ?: endpoint.port) + // For TLS connections, use the connected endpoint's scheme/port instead of raw canvas metadata. + val fallbackScheme = if (isTlsConnection) "https" else scheme + // For TLS, always use the connected endpoint port. + val fallbackPort = if (isTlsConnection) endpoint.port else (endpoint.canvasPort ?: endpoint.port) val formattedHost = if (fallbackHost.contains(":")) "[${fallbackHost}]" else fallbackHost val portSuffix = if ((fallbackScheme == "https" && fallbackPort == 443) || (fallbackScheme == "http" && fallbackPort == 80)) "" else ":$fallbackPort" return "$fallbackScheme://$formattedHost$portSuffix"