Not authorized
This commit is contained in:
parent
88bfee1ea7
commit
297ac864a4
6 changed files with 61 additions and 19 deletions
|
|
@ -20,9 +20,12 @@ import androidx.room.migration.Migration
|
|||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import io.heckel.ntfy.service.isConnectionRefused
|
||||
import io.heckel.ntfy.service.NotAuthorizedException
|
||||
import io.heckel.ntfy.service.WebSocketNotSupportedException
|
||||
import io.heckel.ntfy.service.hasCause
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.lang.reflect.Type
|
||||
import java.net.ConnectException
|
||||
|
||||
@Entity(indices = [Index(value = ["baseUrl", "topic"], unique = true), Index(value = ["upConnectorToken"], unique = true)])
|
||||
data class Subscription(
|
||||
|
|
@ -105,7 +108,15 @@ data class ConnectionDetails(
|
|||
}
|
||||
|
||||
fun isConnectionRefused(): Boolean {
|
||||
return error?.let { isConnectionRefused(it) } ?: false
|
||||
return error?.hasCause<ConnectException>() ?: false
|
||||
}
|
||||
|
||||
fun isWebSocketNotSupported(): Boolean {
|
||||
return error?.hasCause<WebSocketNotSupportedException>() ?: false
|
||||
}
|
||||
|
||||
fun isNotAuthorized(): Boolean {
|
||||
return error?.hasCause<NotAuthorizedException>() ?: false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package io.heckel.ntfy.service
|
|||
|
||||
import okhttp3.internal.http2.StreamResetException
|
||||
import java.io.EOFException
|
||||
import java.net.ConnectException
|
||||
|
||||
interface Connection {
|
||||
fun start()
|
||||
|
|
@ -10,6 +9,26 @@ interface Connection {
|
|||
fun since(): String?
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown when the server does not support WebSocket connections.
|
||||
* This typically happens when the server returns a non-101 response during the WebSocket upgrade.
|
||||
*/
|
||||
class WebSocketNotSupportedException(
|
||||
responseCode: Int,
|
||||
responseMessage: String?,
|
||||
cause: Throwable? = null
|
||||
) : Exception("WebSocket upgrade failed with HTTP $responseCode: $responseMessage", cause)
|
||||
|
||||
|
||||
/**
|
||||
* Exception thrown when the server responds with HTTP 401/403
|
||||
*/
|
||||
class NotAuthorizedException(
|
||||
responseCode: Int,
|
||||
responseMessage: String?,
|
||||
cause: Throwable? = null
|
||||
) : Exception("User not authorized, HTTP $responseCode: $responseMessage", cause)
|
||||
|
||||
/**
|
||||
* Represents a unique connection identifier that changes every time a
|
||||
* connection needs to be re-established.
|
||||
|
|
@ -25,16 +44,9 @@ data class ConnectionId(
|
|||
val reconnectVersion: Long // Incremented to force reconnection for this baseUrl
|
||||
)
|
||||
|
||||
/**
|
||||
* Checks if the throwable or any of its causes is of the specified type.
|
||||
*/
|
||||
inline fun <reified T : Throwable> Throwable.hasCause(): Boolean {
|
||||
var current: Throwable? = this
|
||||
while (current != null) {
|
||||
if (current is T) return true
|
||||
current = current.cause
|
||||
}
|
||||
return false
|
||||
fun isResponseCode(response: okhttp3.Response?, vararg codes: Int): Boolean {
|
||||
val responseCode = response?.code ?: return false
|
||||
return responseCode in codes
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -46,9 +58,13 @@ fun isConnectionBrokenException(t: Throwable): Boolean {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns true if the exception indicates the connection was refused
|
||||
* (e.g., server is down or address is incorrect).
|
||||
* Checks if the throwable or any of its causes is of the specified type.
|
||||
*/
|
||||
fun isConnectionRefused(t: Throwable): Boolean {
|
||||
return t.hasCause<ConnectException>()
|
||||
}
|
||||
inline fun <reified T : Throwable> Throwable.hasCause(): Boolean {
|
||||
var current: Throwable? = this
|
||||
while (current != null) {
|
||||
if (current is T) return true
|
||||
current = current.cause
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -80,6 +80,7 @@ class JsonConnection(
|
|||
retryMillis = nextRetryMillis(retryMillis, startTime)
|
||||
val nextRetryTime = System.currentTimeMillis() + retryMillis
|
||||
val error = if (isConnectionBrokenException(e)) null else e
|
||||
// FIXME add NotAuthorizedException
|
||||
connectionDetailsListener(subscriptionIds, ConnectionState.CONNECTING, error, nextRetryTime)
|
||||
Log.w(TAG, "[$url] Retrying connection in ${retryMillis / 1000}s ...")
|
||||
delay(retryMillis)
|
||||
|
|
|
|||
|
|
@ -185,7 +185,17 @@ class WsConnection(
|
|||
errorCount++
|
||||
val retrySeconds = RETRY_SECONDS.getOrNull(errorCount) ?: RETRY_SECONDS.last()
|
||||
val nextRetryTime = System.currentTimeMillis() + (retrySeconds * 1000L)
|
||||
val error = if (isConnectionBrokenException(t)) null else t
|
||||
|
||||
// Special cases:
|
||||
// - Ignore broken connections in the UI, we don't want to show warning icons
|
||||
// - Handle authentication errors
|
||||
// - Handle servers that do not support WebSockets
|
||||
val error = when {
|
||||
isConnectionBrokenException(t) -> null
|
||||
isResponseCode(response, 401, 403) -> NotAuthorizedException(response!!.code, response.message, t)
|
||||
isResponseCode(response, 101) -> WebSocketNotSupportedException(response!!.code, response.message, t)
|
||||
else -> t
|
||||
}
|
||||
connectionDetailsListener(subscriptionIds, ConnectionState.CONNECTING, error, nextRetryTime)
|
||||
scheduleReconnect(retrySeconds)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -197,6 +197,8 @@ class ConnectionErrorFragment : DialogFragment() {
|
|||
if (details != null && details.hasError()) {
|
||||
errorTextView.text = when {
|
||||
details.isConnectionRefused() -> getString(R.string.connection_error_dialog_connection_refused)
|
||||
details.isWebSocketNotSupported() -> getString(R.string.connection_error_dialog_websocket_not_supported)
|
||||
details.isNotAuthorized() -> getString(R.string.connection_error_dialog_not_authorized)
|
||||
else -> getErrorDisplayText(details.error)
|
||||
}
|
||||
val stackTrace = details.getStackTraceString()
|
||||
|
|
|
|||
|
|
@ -293,6 +293,8 @@
|
|||
<string name="connection_error_dialog_title">Connection error</string>
|
||||
<string name="connection_error_dialog_message">There was a problem connecting to %1$s. The app will keep trying to reconnect in the background.</string>
|
||||
<string name="connection_error_dialog_connection_refused">Connection refused. The server may be down or the address may be incorrect.</string>
|
||||
<string name="connection_error_dialog_websocket_not_supported">WebSocket not supported. The server may not support WebSocket connections, or the address may be incorrect.</string>
|
||||
<string name="connection_error_dialog_not_authorized">Not authorized. The server returned a HTTP 401/403 response. Please check if your username and password are correct.</string>
|
||||
<string name="connection_error_dialog_copy">Copy</string>
|
||||
<string name="connection_error_dialog_retry_now">Retry now</string>
|
||||
<string name="connection_error_dialog_retry_countdown">Retrying in %1$ds…</string>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue