diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt index caabcf416..b56a10e46 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt @@ -21,9 +21,8 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor } val originalRequest = chain.request() - // Refresh access token if expired - if (oauth != null && oauth!!.isExpired()) { - setAuth(refreshToken(chain)) + if (oauth?.isExpired() == true) { + refreshToken(chain) } if (oauth == null) { @@ -36,27 +35,7 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor .header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})") .build() - val response = chain.proceed(authRequest) - val tokenIsExpired = response.headers["www-authenticate"] - ?.contains("The access token expired") ?: false - - // Retry the request once with a new token in case it was not already refreshed - // by the is expired check before. - if (response.code == 401 && tokenIsExpired) { - response.close() - - val newToken = refreshToken(chain) - setAuth(newToken) - - val newRequest = originalRequest.newBuilder() - .addHeader("Authorization", "Bearer ${newToken.access_token}") - .header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})") - .build() - - return chain.proceed(newRequest) - } - - return response + return chain.proceed(authRequest) } /** @@ -68,22 +47,37 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor myanimelist.saveOAuth(oauth) } - private fun refreshToken(chain: Interceptor.Chain): OAuth { + private fun refreshToken(chain: Interceptor.Chain): OAuth = synchronized(this) { + if (tokenExpired) throw MALTokenExpired() + oauth?.takeUnless { it.isExpired() }?.let { return@synchronized it } + + val response = try { + chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!)) + } catch (_: Throwable) { + throw MALTokenRefreshFailed() + } + + if (response.code == 401) { + myanimelist.setAuthExpired() + throw MALTokenExpired() + } + return runCatching { - val oauthResponse = chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!)) - if (oauthResponse.code == 401) { - myanimelist.setAuthExpired() - } - if (oauthResponse.isSuccessful) { - with(json) { oauthResponse.parseAs() } + if (response.isSuccessful) { + with(json) { response.parseAs() } } else { - oauthResponse.close() + response.close() null } } .getOrNull() - ?: throw MALTokenExpired() + ?.also { + this.oauth = it + myanimelist.saveOAuth(it) + } + ?: throw MALTokenRefreshFailed() } } +class MALTokenRefreshFailed : IOException("MAL: Failed to refresh account token") class MALTokenExpired : IOException("MAL: Login has expired") diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListModels.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListModels.kt index dea4231b3..1ae02142f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListModels.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListModels.kt @@ -5,14 +5,16 @@ import kotlinx.serialization.Serializable @Serializable data class OAuth( + val token_type: String, val refresh_token: String, val access_token: String, - val token_type: String, - val created_at: Long = System.currentTimeMillis(), val expires_in: Long, -) - -fun OAuth.isExpired() = System.currentTimeMillis() > created_at + (expires_in * 1000) + val created_at: Long = System.currentTimeMillis(), +) { + // Assumes expired a minute earlier + private val adjustedExpiresIn: Long = (expires_in - 60) * 1000 + fun isExpired() = created_at + adjustedExpiresIn < System.currentTimeMillis() +} fun Track.toMyAnimeListStatus() = when (status) { MyAnimeList.READING -> "reading"