mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-19 05:45:41 +00:00
fix(simkl): centralize oauth request policy
This commit is contained in:
parent
c1022e2eb5
commit
adcc829a1a
3 changed files with 70 additions and 17 deletions
|
|
@ -20,12 +20,18 @@ internal enum class SimklHttpMethod {
|
|||
DELETE,
|
||||
}
|
||||
|
||||
internal enum class SimklRetryPolicy {
|
||||
TRANSIENT_FAILURES,
|
||||
NEVER,
|
||||
}
|
||||
|
||||
internal data class SimklApiRequest(
|
||||
val method: SimklHttpMethod,
|
||||
val path: String,
|
||||
val query: Map<String, String> = emptyMap(),
|
||||
val body: String = "",
|
||||
val requiresAuthentication: Boolean = true,
|
||||
val retryPolicy: SimklRetryPolicy = SimklRetryPolicy.TRANSIENT_FAILURES,
|
||||
val scrobbleStopConflictIsSuccess: Boolean = false,
|
||||
)
|
||||
|
||||
|
|
@ -78,7 +84,11 @@ internal class SimklApiClient(
|
|||
}
|
||||
|
||||
var lastTransportFailure: Throwable? = null
|
||||
for (attempt in 0..MAX_RETRIES) {
|
||||
val maxRetries = when (request.retryPolicy) {
|
||||
SimklRetryPolicy.TRANSIENT_FAILURES -> MAX_RETRIES
|
||||
SimklRetryPolicy.NEVER -> 0
|
||||
}
|
||||
for (attempt in 0..maxRetries) {
|
||||
awaitRateLimit(request.method)
|
||||
val response = try {
|
||||
engine.execute(
|
||||
|
|
@ -94,7 +104,7 @@ internal class SimklApiClient(
|
|||
throw error
|
||||
} catch (error: Throwable) {
|
||||
lastTransportFailure = error
|
||||
if (attempt == MAX_RETRIES) {
|
||||
if (attempt == maxRetries) {
|
||||
throw SimklApiException(
|
||||
status = null,
|
||||
errorCode = "transport_failure",
|
||||
|
|
@ -112,12 +122,12 @@ internal class SimklApiClient(
|
|||
return@withLock response.toApiResponse(isSoftSuccess = true)
|
||||
}
|
||||
SimklResponseAction.REAUTHENTICATE -> {
|
||||
onUnauthorized()
|
||||
if (request.requiresAuthentication) onUnauthorized()
|
||||
throw response.toApiException(json)
|
||||
}
|
||||
SimklResponseAction.FAIL -> throw response.toApiException(json)
|
||||
SimklResponseAction.RETRY -> {
|
||||
if (attempt == MAX_RETRIES) throw response.toApiException(json)
|
||||
if (attempt == maxRetries) throw response.toApiException(json)
|
||||
sleep(
|
||||
retryDelayMs(
|
||||
attempt = attempt,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.nuvio.app.features.simkl
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.nuvio.app.features.addons.httpRequestRaw
|
||||
import com.nuvio.app.features.tracking.TrackingAuthProvider
|
||||
import com.nuvio.app.features.tracking.TrackingCapability
|
||||
import com.nuvio.app.features.tracking.TrackingProviderDescriptor
|
||||
|
|
@ -220,11 +219,14 @@ object SimklAuthRepository : TrackingAuthProvider {
|
|||
redirectUri = SimklConfig.REDIRECT_URI,
|
||||
)
|
||||
val response = try {
|
||||
httpRequestRaw(
|
||||
method = "POST",
|
||||
url = "$SIMKL_API_BASE_URL/oauth/token",
|
||||
headers = simklRequestHeaders(contentTypeJson = true),
|
||||
body = json.encodeToString(request),
|
||||
SimklApi.client.execute(
|
||||
SimklApiRequest(
|
||||
method = SimklHttpMethod.POST,
|
||||
path = "/oauth/token",
|
||||
body = json.encodeToString(request),
|
||||
requiresAuthentication = false,
|
||||
retryPolicy = SimklRetryPolicy.NEVER,
|
||||
),
|
||||
)
|
||||
} catch (error: CancellationException) {
|
||||
throw error
|
||||
|
|
@ -235,13 +237,6 @@ object SimklAuthRepository : TrackingAuthProvider {
|
|||
publish(isLoading = false, error = SimklAuthError.TOKEN_EXCHANGE_FAILED)
|
||||
return@withLock
|
||||
}
|
||||
|
||||
if (response.status !in 200..299) {
|
||||
clearPendingAuthorization()
|
||||
persistMetadata()
|
||||
publish(isLoading = false, error = SimklAuthError.TOKEN_EXCHANGE_FAILED)
|
||||
return@withLock
|
||||
}
|
||||
val token = runCatching { json.decodeFromString<SimklTokenResponse>(response.body) }
|
||||
.getOrNull()
|
||||
?.takeIf { it.accessToken.isNotBlank() }
|
||||
|
|
|
|||
|
|
@ -68,6 +68,54 @@ class SimklApiClientTest {
|
|||
assertTrue(request.headers.getValue("User-Agent").contains('/'))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `single use unauthenticated posts keep metadata and never retry`() = runBlocking {
|
||||
val engine = RecordingEngine(response(503), response(200))
|
||||
val harness = TestHarness(engine)
|
||||
|
||||
assertFailsWith<SimklApiException> {
|
||||
harness.client.execute(
|
||||
SimklApiRequest(
|
||||
method = SimklHttpMethod.POST,
|
||||
path = "/oauth/token",
|
||||
body = "{}",
|
||||
requiresAuthentication = false,
|
||||
retryPolicy = SimklRetryPolicy.NEVER,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val request = engine.requests.single()
|
||||
assertTrue("client_id=" in request.url)
|
||||
assertTrue("app-name=" in request.url)
|
||||
assertTrue("app-version=" in request.url)
|
||||
assertTrue(request.headers.getValue("User-Agent").contains('/'))
|
||||
assertFalse("Authorization" in request.headers)
|
||||
assertTrue(harness.sleeps.isEmpty())
|
||||
assertFalse(harness.wasUnauthorized)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unauthenticated 401 does not invalidate an existing session`() = runBlocking {
|
||||
val engine = RecordingEngine(response(401))
|
||||
val harness = TestHarness(engine)
|
||||
|
||||
assertFailsWith<SimklApiException> {
|
||||
harness.client.execute(
|
||||
SimklApiRequest(
|
||||
method = SimklHttpMethod.POST,
|
||||
path = "/oauth/token",
|
||||
body = "{}",
|
||||
requiresAuthentication = false,
|
||||
retryPolicy = SimklRetryPolicy.NEVER,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
assertFalse(harness.wasUnauthorized)
|
||||
assertEquals(1, engine.requests.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `authenticated requests are serialized at documented method rates`() = runBlocking {
|
||||
val engine = RecordingEngine(response(200), response(200), response(200), response(200))
|
||||
|
|
|
|||
Loading…
Reference in a new issue