Vivek Modi
12/28/2024, 12:11 AMVivek Modi
12/28/2024, 12:12 AMclass KtorHttpClientFactory(private val tokenDataSource: TokenDataSource) {
companion object {
private const val HOST_URL = "<http://api.vivek.com/dev/v1|api.vivek.com/dev/v1>"
}
fun build(): HttpClient {
return HttpClient(OkHttp) {
expectSuccess = true
install(Logging) {
logger = Logger.ANDROID
level = LogLevel.ALL
}
defaultRequest {
host = HOST_URL
url { protocol = URLProtocol.HTTPS }
header(HttpHeaders.ContentType, ContentType.Application.Json)
header("client-id", "349587438548375838957893475")
}
install(ContentNegotiation) {
json(
Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
}
)
}
install(ResponseObserver) {
onResponse { response ->
Log.d("HTTP status:", "${response.status.value}")
}
}
install(HttpRequestRetry) {
maxRetries = 0
}
configureResponseValidation()
install(Auth) {
bearer {
// 1) Load tokens from local storage before each request
loadTokens {
val accessToken = tokenDataSource.getAccessToken().first()
val refreshToken = tokenDataSource.getRefreshToken().first()
Log.e("KtorHttpClientFactory", "addTokenInterceptor: $accessToken")
// Load tokens from a local storage and return them as the 'BearerTokens' instance
BearerTokens(accessToken.orEmpty(), refreshToken.orEmpty())
}
// 2) If 401 is encountered, Ktor calls refreshTokens()
refreshTokens { // this: RefreshTokensParams
val oldToken = tokenDataSource.getAccessToken().first()
val response = <http://client.post|client.post> {
url("/refresh")
setBody(oldToken)
}
if (response.status == HttpStatusCode.Unauthorized) {
null
} else {
val authResponse = response.body<AuthResponse>()
val refreshToken = authResponse.refreshToken
val accessToken = authResponse.accessToken
tokenDataSource.storeTokens(refreshToken, accessToken)
BearerTokens(refreshToken, accessToken)
}
}
// 3) Send Request without Bearer
sendWithoutRequest { request ->
request.url.host.contains("/login")
}
}
}
}
}
private fun HttpClientConfig<OkHttpConfig>.configureResponseValidation() {
HttpResponseValidator {
handleResponseExceptionWithRequest { exception, _ ->
when (exception) {
is ResponseException -> {
val exceptionResponse = exception.response
val statusCode = exceptionResponse.status
val (value, description) = HttpStatusCode.fromValue(statusCode.value)
val travelException =
Json.decodeFromString<TravelException>(exceptionResponse.bodyAsText())
throw TravelException(
value,
description,
travelException.serverErrorCode,
travelException.serverMessage
)
}
else -> throw TravelException(null, null, serverMessage = exception.message)
}
}
}
}
}
Chris Lee
12/28/2024, 12:35 AMMOHSEN ALIAKBARI
12/28/2024, 12:37 AMmarkAsRefreshTokenRequest()
might solve your problem. Here's an example:
val refreshTokenInfo: RefreshTokenResponse = <http://client.post|client.post>(
"<https://abc.com/api/auth/refresh>"
)
{
markAsRefreshTokenRequest()
contentType(ContentType.Application.Json)
setBody(RefreshTokenRequest(refreshToken!!))
}
KotlinLeaner
12/28/2024, 7:47 AMKotlinLeaner
12/28/2024, 7:48 AMMOHSEN ALIAKBARI
12/29/2024, 8:12 AMmarkAsRefreshTokenRequest()
function helps to distinguish the token refresh operation from other API calls.KotlinLeaner
12/29/2024, 8:57 AMKotlinLeaner
12/29/2024, 9:08 AMSeri
12/30/2024, 3:57 PMSeri
12/30/2024, 3:58 PMKotlinLeaner
01/05/2025, 9:41 PM