diff --git a/app/src/main/kotlin/io/flamenet/messenger/app/MessengerViewModel.kt b/app/src/main/kotlin/io/flamenet/messenger/app/MessengerViewModel.kt index 6d7e879..6c21d79 100644 --- a/app/src/main/kotlin/io/flamenet/messenger/app/MessengerViewModel.kt +++ b/app/src/main/kotlin/io/flamenet/messenger/app/MessengerViewModel.kt @@ -2829,7 +2829,20 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) { 409 -> "That handle is already taken. Try a different one." 400, 422 -> "That server couldn't accept those details. " + "Check the handle and try again." - 403 -> "That isn't allowed on this server." + // 🔴 **Say what the relay said.** A 403 is a rule refusing this + // specific thing, and the relay writes a sentence that names it + // and what to do — "this account already has 3 registered devices, + // remove one from the Devices screen". "That isn't allowed on this + // server" replaces an answer somebody can act on with one they + // cannot, and leaves them guessing at a rule nobody stated. + // + // ⚠️ Only when it really is a sentence. `body` falls back to the + // raw payload when there is no `message`, so anything that looks + // like JSON or a bare code keeps the generic line rather than + // putting a fragment on screen. + 403 -> failure.body + .takeIf { it.isNotBlank() && !it.trimStart().startsWith("{") && " " in it } + ?: "That isn't allowed on this server." in 500..599 -> "That server is having trouble right now. " + "Try again in a moment." else -> "That server couldn't complete this. Try again in a moment." diff --git a/client/src/main/kotlin/io/flamenet/client/RelayAccounts.kt b/client/src/main/kotlin/io/flamenet/client/RelayAccounts.kt index 0c74859..d72fc6e 100644 --- a/client/src/main/kotlin/io/flamenet/client/RelayAccounts.kt +++ b/client/src/main/kotlin/io/flamenet/client/RelayAccounts.kt @@ -174,7 +174,18 @@ class RelayAccounts( // Play Store to fix a typo. code == "challenge_required" -> RelayException.ChallengeRequired(message) code == "challenge_failed" -> RelayException.ChallengeFailed(message) - it.code == 401 || it.code == 403 -> RelayException.Unauthorized(message) + // 🔴 **A 403 is not an expired session.** 401 means the token + // was refused and signing in again is the fix. 403 means a + // RULE refused this — too many devices, a retired device, an + // unverified address — and the relay writes a sentence saying + // which. Reporting those as "your session expired, sign out and + // back in" sends somebody round a loop that cannot end: they + // sign in again, hit the same rule, and are told the same + // wrong thing. Observed tonight on a fresh install. + it.code == 401 -> RelayException.Unauthorized(message) + it.code == 403 && code == "email_unverified" -> + RelayException.EmailUnverified(message) + it.code == 403 -> RelayException.Http(403, message) it.code == 404 -> RelayException.NotFound(path) it.code == 409 -> RelayException.Http(409, message) it.code == 429 ->