reconnect: не уходить в «ошибку авторизации» при коротком обрыве

После короткого обрыва сервер ещё держит старую сессию и на рукопожатии
присылает MSG_ERROR. Клиент трактовал любую ошибку до STATE_READY как
фатальную (stopFatal → STATE_AUTH_FAILED) и прекращал переподключения.

Теперь ошибки рукопожатия (MSG_ERROR, протокольные сбои, слишком большой
кадр) считаются временными, если успешная сессия уже была (everReady):
показываем STATE_ERROR и продолжаем автопереподключение. Фатально — только
до первой успешной авторизации (реально неверные креды).
This commit is contained in:
ua1zbe
2026-10-09 13:05:05 +03:00
parent 491dff03db
commit 203e5f6ed5

View File

@@ -85,6 +85,15 @@ class ReflectorClient(
@Volatile @Volatile
private var fatal = false private var fatal = false
/**
* Была ли хотя бы одна успешная сессия за текущее подключение.
* Если да — креды уже проверены, поэтому ошибки на рукопожатии считаем
* временными (сервер мог ещё держать старую сессию после обрыва) и
* продолжаем автопереподключение вместо показа «ошибки авторизации».
*/
@Volatile
private var everReady = false
@Volatile @Volatile
private var tcpSocket: Socket? = null private var tcpSocket: Socket? = null
private var udpSocket: DatagramSocket? = null private var udpSocket: DatagramSocket? = null
@@ -107,6 +116,7 @@ class ReflectorClient(
this.settings = settings this.settings = settings
manualDisconnect.set(false) manualDisconnect.set(false)
fatal = false fatal = false
everReady = false
if (running.get()) { if (running.get()) {
// Перезапуск с новыми настройками (например, смена профиля) // Перезапуск с новыми настройками (например, смена профиля)
running.set(false) running.set(false)
@@ -399,6 +409,7 @@ class ReflectorClient(
serverCodecs = info.codecs serverCodecs = info.codecs
callbacks.onServerInfo(info) callbacks.onServerInfo(info)
state = STATE_READY state = STATE_READY
everReady = true
callbacks.onState(STATE_READY) callbacks.onState(STATE_READY)
// Сначала сообщаем о себе (версия ПО для дашборда), затем // Сначала сообщаем о себе (версия ПО для дашборда), затем
// выбираем TG для передачи и подписываемся на прослушивание // выбираем TG для передачи и подписываемся на прослушивание
@@ -441,9 +452,17 @@ class ReflectorClient(
ReflectorProtocol.MSG_ERROR -> { ReflectorProtocol.MSG_ERROR -> {
val err = MsgParser.errorMessage(msg.fields) val err = MsgParser.errorMessage(msg.fields)
Log.w(TAG, "MSG_ERROR: $err") Log.w(TAG, "MSG_ERROR: $err (state=$state, everReady=$everReady)")
if (state < STATE_READY) { if (state < STATE_READY) {
stopFatal(err.ifEmpty { "Access denied" }) if (everReady) {
// Креды проверены ранее: ошибка на рукопожатии почти
// всегда временная (сервер ещё держит старую сессию
// после короткого обрыва) — переподключаемся заново.
callbacks.onState(STATE_ERROR, err.ifEmpty { "Переподключение" })
abortSockets()
} else {
stopFatal(err.ifEmpty { "Access denied" })
}
return false return false
} }
callbacks.onError(err) callbacks.onError(err)
@@ -452,7 +471,13 @@ class ReflectorClient(
else -> Unit // неизвестные сообщения игнорируются else -> Unit // неизвестные сообщения игнорируются
} }
} catch (e: Exception) { } catch (e: Exception) {
stopFatal("Ошибка протокола: ${e.message}") if (everReady) {
// Сбой после успешного подключения — не фатально, переподключаемся.
callbacks.onState(STATE_ERROR, "Ошибка протокола: ${e.message}")
abortSockets()
} else {
stopFatal("Ошибка протокола: ${e.message}")
}
return false return false
} }
return true return true
@@ -478,7 +503,12 @@ class ReflectorClient(
} }
private fun protocolError(detail: String): Boolean { private fun protocolError(detail: String): Boolean {
stopFatal(detail) if (everReady) {
callbacks.onState(STATE_ERROR, detail)
abortSockets()
} else {
stopFatal(detail)
}
return false return false
} }