mirror of
https://github.com/fromchat-messenger/app.git
synced 2026-09-22 19:15:05 +03:00
Implement default calls port
Signed-off-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
@@ -129,9 +129,9 @@
|
||||
<string name="server_ip_label">IP или имя сервера</string>
|
||||
<string name="server_ip_hint">192.168.1.10</string>
|
||||
<string name="api_port_label">Порт</string>
|
||||
<string name="api_port_hint">8301</string>
|
||||
<string name="api_port_hint"></string>
|
||||
<string name="calls_port_label">Порт звонков</string>
|
||||
<string name="calls_port_hint">8302</string>
|
||||
<string name="calls_port_hint"></string>
|
||||
<string name="https_enabled">Защищённое соединение</string>
|
||||
<string name="server_config_https_hint">HTTPS. Отключайте только для HTTP без шифрования.</string>
|
||||
<string name="server_config_https_headline">HTTPS</string>
|
||||
|
||||
@@ -156,9 +156,9 @@
|
||||
<string name="server_ip_label">Server IP or hostname</string>
|
||||
<string name="server_ip_hint">192.168.1.10</string>
|
||||
<string name="api_port_label">Port</string>
|
||||
<string name="api_port_hint">8301</string>
|
||||
<string name="api_port_hint"></string>
|
||||
<string name="calls_port_label">Calls port</string>
|
||||
<string name="calls_port_hint">8302</string>
|
||||
<string name="calls_port_hint"></string>
|
||||
<string name="https_enabled">Secure connection</string>
|
||||
<string name="server_config_https_hint">Uses HTTPS. Turn off only for plain HTTP.</string>
|
||||
<string name="server_config_https_headline">HTTPS</string>
|
||||
|
||||
@@ -11,14 +11,19 @@ import kotlinx.serialization.json.Json
|
||||
import ru.fromchat.api.DeviceSessionInfo
|
||||
import ru.fromchat.ui.Theme
|
||||
|
||||
/** Default WebRTC / calls port when none is stored or the field is left empty in UI. */
|
||||
const val DEFAULT_CALLS_PORT = 8303
|
||||
|
||||
/**
|
||||
* Server configuration: [serverIp], HTTP(S) [apiPort], optional [callsPort] (WebRTC / HAProxy; null = calls disabled). First install defaults to fromchat.ru:443 with calls on 8302.
|
||||
* Server configuration: [serverIp], HTTP(S) [apiPort], [callsPort] for WebRTC / HAProxy (defaults to [DEFAULT_CALLS_PORT]).
|
||||
* [callsEnabled] is updated when saving server settings (probe on the calls port); defaults to true when unset.
|
||||
*/
|
||||
data class ServerConfigData(
|
||||
val serverIp: String,
|
||||
val apiPort: Int,
|
||||
val callsPort: Int?,
|
||||
val callsPort: Int,
|
||||
val httpsEnabled: Boolean,
|
||||
val callsEnabled: Boolean = true,
|
||||
)
|
||||
|
||||
object Settings {
|
||||
@@ -28,8 +33,9 @@ object Settings {
|
||||
private const val SERVER_URL_KEY = "server_url"
|
||||
private const val SERVER_IP_KEY = "server_ip"
|
||||
private const val API_PORT_KEY = "api_port"
|
||||
/** Stored as Int; values ≤ 0 mean unset (calls disabled). */
|
||||
/** Stored as Int; invalid values fall back to [DEFAULT_CALLS_PORT] when read. */
|
||||
private const val CALLS_PORT_KEY = "calls_port"
|
||||
private const val CALLS_ENABLED_KEY = "calls_enabled"
|
||||
private const val HTTPS_ENABLED_KEY = "https_enabled"
|
||||
private const val DEVICE_SESSIONS_CACHE_KEY = "device_sessions_cache_v1"
|
||||
private const val LAST_SERVER_INSTANCE_ID_KEY = "last_server_instance_id"
|
||||
@@ -105,7 +111,7 @@ object Settings {
|
||||
if (!settings.contains(SERVER_IP_KEY)) {
|
||||
settings.putString(SERVER_IP_KEY, "fromchat.ru")
|
||||
settings.putInt(API_PORT_KEY, 443)
|
||||
settings.putInt(CALLS_PORT_KEY, 8302)
|
||||
settings.putInt(CALLS_PORT_KEY, DEFAULT_CALLS_PORT)
|
||||
if (!settings.contains(HTTPS_ENABLED_KEY)) {
|
||||
settings.putBoolean(HTTPS_ENABLED_KEY, true)
|
||||
}
|
||||
@@ -116,21 +122,28 @@ object Settings {
|
||||
}
|
||||
val apiPort = settings.getInt(API_PORT_KEY, 443).coerceIn(1, 65535)
|
||||
val rawCalls = settings.getInt(CALLS_PORT_KEY, -1)
|
||||
val calls = if (rawCalls > 0 && rawCalls <= 65535) rawCalls else null
|
||||
val callsPort = if (rawCalls in 1..65535) rawCalls else DEFAULT_CALLS_PORT
|
||||
val https = settings.getBoolean(HTTPS_ENABLED_KEY, true)
|
||||
val callsEnabled =
|
||||
if (settings.contains(CALLS_ENABLED_KEY)) {
|
||||
settings.getBoolean(CALLS_ENABLED_KEY, true)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
ServerConfigData(
|
||||
serverIp = ip,
|
||||
apiPort = apiPort,
|
||||
callsPort = calls,
|
||||
callsPort = callsPort,
|
||||
httpsEnabled = https,
|
||||
callsEnabled = callsEnabled,
|
||||
)
|
||||
}
|
||||
set(value) = runIO {
|
||||
settings.putString(SERVER_IP_KEY, value.serverIp.trim())
|
||||
settings.putInt(API_PORT_KEY, value.apiPort.coerceIn(1, 65535))
|
||||
val callsStored = value.callsPort?.coerceIn(1, 65535) ?: -1
|
||||
settings.putInt(CALLS_PORT_KEY, callsStored)
|
||||
settings.putInt(CALLS_PORT_KEY, value.callsPort.coerceIn(1, 65535))
|
||||
settings.putBoolean(HTTPS_ENABLED_KEY, value.httpsEnabled)
|
||||
settings.putBoolean(CALLS_ENABLED_KEY, value.callsEnabled)
|
||||
}
|
||||
|
||||
/** Cached device sessions (JSON). Shown immediately while refreshing from the network. */
|
||||
|
||||
@@ -35,9 +35,9 @@ object Config {
|
||||
_serverConfig.value = config
|
||||
}
|
||||
|
||||
/** Voice/video calls (LiveKit) are enabled only when a calls (WebRTC) port is configured. */
|
||||
/** Voice/video calls when the last server-config apply probe to the calls port succeeded. */
|
||||
val callsEnabled: Boolean
|
||||
get() = config.callsPort != null
|
||||
get() = config.callsEnabled
|
||||
|
||||
/**
|
||||
* LiveKit signaling WebSocket URL: same host and API port as HTTPS reverse proxy (`/api/livekit/rtc`).
|
||||
|
||||
@@ -107,6 +107,7 @@ import dev.chrisbanes.haze.rememberHazeState
|
||||
import kotlin.time.TimeSource
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.compose.resources.getString
|
||||
@@ -114,13 +115,12 @@ import org.jetbrains.compose.resources.stringResource
|
||||
import ru.fromchat.Res
|
||||
import ru.fromchat.api.ApiClient
|
||||
import ru.fromchat.api.WebSocketManager
|
||||
import ru.fromchat.api_port_hint
|
||||
import ru.fromchat.api_port_label
|
||||
import ru.fromchat.back
|
||||
import ru.fromchat.calls_port_hint
|
||||
import ru.fromchat.calls_port_label
|
||||
import ru.fromchat.cancel
|
||||
import ru.fromchat.confirm
|
||||
import ru.fromchat.core.DEFAULT_CALLS_PORT
|
||||
import ru.fromchat.core.ServerConfigData
|
||||
import ru.fromchat.core.Settings
|
||||
import ru.fromchat.core.config.Config
|
||||
@@ -137,7 +137,6 @@ import ru.fromchat.server_config_snackbar_api_fail
|
||||
import ru.fromchat.server_config_snackbar_defaults
|
||||
import ru.fromchat.server_config_snackbar_ok_api_calls_bad
|
||||
import ru.fromchat.server_config_snackbar_ok_calls
|
||||
import ru.fromchat.server_config_snackbar_ok_calls_skip
|
||||
import ru.fromchat.server_config_subtitle
|
||||
import ru.fromchat.server_config_title
|
||||
import ru.fromchat.server_ip_hint
|
||||
@@ -206,6 +205,13 @@ private fun apiBaseUrlFor(config: ServerConfigData): String {
|
||||
return "$scheme://${config.serverIp}:${config.apiPort}/api"
|
||||
}
|
||||
|
||||
private suspend fun probeCallsReachable(config: ServerConfigData): Boolean {
|
||||
val urlScheme = if (config.httpsEnabled) "https" else "http"
|
||||
val host = config.serverIp.trim()
|
||||
val root = "$urlScheme://${hostForAuthority(host)}:${config.callsPort}/"
|
||||
return ApiClient.probeHttpGet(root)
|
||||
}
|
||||
|
||||
private fun resolvedApiPort(apiPortText: String): Int {
|
||||
val t = apiPortText.trim()
|
||||
if (t.isEmpty()) return 443
|
||||
@@ -213,9 +219,12 @@ private fun resolvedApiPort(apiPortText: String): Int {
|
||||
return n.takeIf { it in 1..65535 } ?: 443
|
||||
}
|
||||
|
||||
private fun resolvedCallsPort(callsPortText: String): Int? =
|
||||
if (callsPortText.isBlank()) null
|
||||
else callsPortText.trim().toIntOrNull()?.takeIf { it in 1..65535 }
|
||||
/** Blank calls field uses [DEFAULT_CALLS_PORT] instead of disabling calls. */
|
||||
private fun effectiveCallsPort(callsPortText: String): Int {
|
||||
val t = callsPortText.trim()
|
||||
if (t.isBlank()) return DEFAULT_CALLS_PORT
|
||||
return t.toIntOrNull()?.takeIf { it in 1..65535 } ?: DEFAULT_CALLS_PORT
|
||||
}
|
||||
|
||||
private suspend fun LazyListState.scrollFocusedItemIntoView(
|
||||
itemIndex: Int,
|
||||
@@ -421,7 +430,7 @@ fun ServerConfigScreen() {
|
||||
val c = Settings.serverConfig
|
||||
serverIp = c.serverIp
|
||||
apiPortText = c.apiPort.toString()
|
||||
callsPortText = c.callsPort?.toString().orEmpty()
|
||||
callsPortText = c.callsPort.toString()
|
||||
httpsEnabled = c.httpsEnabled
|
||||
}
|
||||
|
||||
@@ -447,7 +456,7 @@ fun ServerConfigScreen() {
|
||||
val callsPortError = callsPortText.isNotEmpty() && !isValidPortNumber(callsPortText)
|
||||
|
||||
val apiPortEffective = resolvedApiPort(apiPortText)
|
||||
val callsPortParsed = resolvedCallsPort(callsPortText)
|
||||
val callsPortParsed = effectiveCallsPort(callsPortText)
|
||||
val canApply =
|
||||
hostOk &&
|
||||
!apiPortError &&
|
||||
@@ -457,7 +466,7 @@ fun ServerConfigScreen() {
|
||||
val resetToDefaults = {
|
||||
serverIp = "fromchat.ru"
|
||||
apiPortText = "443"
|
||||
callsPortText = "8302"
|
||||
callsPortText = DEFAULT_CALLS_PORT.toString()
|
||||
httpsEnabled = true
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(strSnackbarDefaults)
|
||||
@@ -466,6 +475,8 @@ fun ServerConfigScreen() {
|
||||
|
||||
val verifyServer: () -> Unit = {
|
||||
scope.launch {
|
||||
// Do not block actual checks on snackbar dismissal.
|
||||
launch { snackbarHostState.showSnackbar("Checking...") }
|
||||
val host = serverIp.trim()
|
||||
if (host.isEmpty() || !isValidIpOrHostname(host)) {
|
||||
snackbarHostState.showSnackbar(strHostError)
|
||||
@@ -480,7 +491,7 @@ fun ServerConfigScreen() {
|
||||
return@launch
|
||||
}
|
||||
val apiPort = resolvedApiPort(apiPortText)
|
||||
val calls = resolvedCallsPort(callsPortText)
|
||||
val calls = effectiveCallsPort(callsPortText)
|
||||
val tentative = ServerConfigData(
|
||||
serverIp = host,
|
||||
apiPort = apiPort,
|
||||
@@ -488,32 +499,24 @@ fun ServerConfigScreen() {
|
||||
httpsEnabled = httpsEnabled,
|
||||
)
|
||||
val apiBase = apiBaseUrlFor(tentative)
|
||||
val pingMark = TimeSource.Monotonic.markNow()
|
||||
val id = runCatching { ApiClient.fetchServerInstanceId(apiBase) }
|
||||
.getOrNull()
|
||||
?.trim()
|
||||
.orEmpty()
|
||||
val pingMs = pingMark.elapsedNow().inWholeMilliseconds
|
||||
.toInt()
|
||||
.coerceAtLeast(0)
|
||||
if (id.isEmpty()) {
|
||||
snackbarHostState.showSnackbar(strSnackbarApiFail)
|
||||
return@launch
|
||||
}
|
||||
val urlScheme = if (httpsEnabled) "https" else "http"
|
||||
val callsOk = if (calls != null) {
|
||||
val root = "$urlScheme://${hostForAuthority(host)}:${calls}/"
|
||||
ApiClient.probeHttpGet(root)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val msg = when {
|
||||
calls == null ->
|
||||
getString(Res.string.server_config_snackbar_ok_calls_skip, pingMs)
|
||||
callsOk == true ->
|
||||
getString(Res.string.server_config_snackbar_ok_calls, pingMs)
|
||||
else -> strSnackbarOkApiCallsBad
|
||||
}
|
||||
|
||||
val msg = runCatching {
|
||||
withTimeout(3000) {
|
||||
val pingMark = TimeSource.Monotonic.markNow()
|
||||
val id = ApiClient.fetchServerInstanceId(apiBase)
|
||||
val pingMs = pingMark.elapsedNow().inWholeMilliseconds
|
||||
.toInt()
|
||||
.coerceAtLeast(0)
|
||||
if (id.isEmpty()) return@withTimeout strSnackbarApiFail
|
||||
|
||||
val urlScheme = if (httpsEnabled) "https" else "http"
|
||||
val root = "$urlScheme://${hostForAuthority(host)}:${calls}/"
|
||||
val callsOk = ApiClient.probeHttpGet(root)
|
||||
if (callsOk) getString(Res.string.server_config_snackbar_ok_calls, pingMs)
|
||||
else strSnackbarOkApiCallsBad
|
||||
}
|
||||
}.getOrElse { strSnackbarApiFail }
|
||||
|
||||
snackbarHostState.showSnackbar(msg)
|
||||
}
|
||||
}
|
||||
@@ -621,7 +624,8 @@ fun ServerConfigScreen() {
|
||||
|
||||
val bearer = ApiClient.token?.trim().orEmpty()
|
||||
if (bearer.isEmpty()) {
|
||||
Config.updateServerConfig(tentative)
|
||||
val callsOk = probeCallsReachable(tentative)
|
||||
Config.updateServerConfig(tentative.copy(callsEnabled = callsOk))
|
||||
Settings.lastKnownServerInstanceId = newId
|
||||
WebSocketManager.disconnect()
|
||||
withContext(Dispatchers.Main) {
|
||||
@@ -646,7 +650,8 @@ fun ServerConfigScreen() {
|
||||
return@launch
|
||||
}
|
||||
|
||||
Config.updateServerConfig(tentative)
|
||||
val callsOk = probeCallsReachable(tentative)
|
||||
Config.updateServerConfig(tentative.copy(callsEnabled = callsOk))
|
||||
Settings.lastKnownServerInstanceId = newId
|
||||
WebSocketManager.disconnect()
|
||||
WebSocketManager.connect(forceRestart = true)
|
||||
@@ -839,7 +844,7 @@ fun ServerConfigScreen() {
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
placeholder = { Text(stringResource(Res.string.api_port_hint)) },
|
||||
placeholder = { Text("8301") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
@@ -872,7 +877,7 @@ fun ServerConfigScreen() {
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
placeholder = { Text(stringResource(Res.string.calls_port_hint)) },
|
||||
placeholder = { Text(DEFAULT_CALLS_PORT.toString()) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusChanged {
|
||||
|
||||
Reference in New Issue
Block a user