Apply ktor-client-auth library to auto refresh token
This commit is contained in:
+25
-29
@@ -10,6 +10,9 @@ import io.ktor.client.engine.okhttp.OkHttp
|
|||||||
import io.ktor.client.plugins.DefaultRequest
|
import io.ktor.client.plugins.DefaultRequest
|
||||||
import io.ktor.client.plugins.HttpSend
|
import io.ktor.client.plugins.HttpSend
|
||||||
import io.ktor.client.plugins.HttpTimeout
|
import io.ktor.client.plugins.HttpTimeout
|
||||||
|
import io.ktor.client.plugins.auth.Auth
|
||||||
|
import io.ktor.client.plugins.auth.providers.BearerTokens
|
||||||
|
import io.ktor.client.plugins.auth.providers.bearer
|
||||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||||
import io.ktor.client.plugins.logging.LogLevel
|
import io.ktor.client.plugins.logging.LogLevel
|
||||||
import io.ktor.client.plugins.logging.Logger
|
import io.ktor.client.plugins.logging.Logger
|
||||||
@@ -24,16 +27,34 @@ actual fun createPlatformHttpClient(
|
|||||||
tokenManager: TokenManager,
|
tokenManager: TokenManager,
|
||||||
authApi: AuthApi?
|
authApi: AuthApi?
|
||||||
): HttpClient {
|
): HttpClient {
|
||||||
val client = HttpClient(OkHttp) {
|
return HttpClient(OkHttp) {
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json(Json {
|
json(Json {
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
isLenient = true
|
isLenient = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
install(DefaultRequest) {
|
install(Auth) {
|
||||||
tokenManager.getAccessToken()?.let {
|
bearer {
|
||||||
header("Authorization", "Bearer $it")
|
cacheTokens = true
|
||||||
|
loadTokens {
|
||||||
|
val access = tokenManager.getAccessToken()
|
||||||
|
val refresh = tokenManager.getRefreshToken()
|
||||||
|
if (access != null && refresh != null) {
|
||||||
|
BearerTokens(access, refresh)
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
refreshTokens {
|
||||||
|
Napier.d(">>>>> [NetworkEngine.android.kt] Access token expired, auto request refresh token...")
|
||||||
|
val newAccess = authApi?.refreshToken()
|
||||||
|
if (newAccess != null) {
|
||||||
|
BearerTokens(newAccess, tokenManager.getRefreshToken()!!)
|
||||||
|
} else {
|
||||||
|
tokenManager.clearTokens()
|
||||||
|
tokenManager.triggerSessionExpired()
|
||||||
|
throw ForceLogoutException()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
install(HttpTimeout) { requestTimeoutMillis = 30_000 }
|
install(HttpTimeout) { requestTimeoutMillis = 30_000 }
|
||||||
@@ -46,29 +67,4 @@ actual fun createPlatformHttpClient(
|
|||||||
level = LogLevel.ALL // set LogLevel.NONE for release
|
level = LogLevel.ALL // set LogLevel.NONE for release
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authApi != null) {
|
|
||||||
client.plugin(HttpSend).intercept { request ->
|
|
||||||
// Skip interceptor for refresh token request to avoid recursion
|
|
||||||
if (request.url.pathSegments.contains(ReferKeys.REFRESH_TOKEN)) {
|
|
||||||
return@intercept execute(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
val originalCall = execute(request)
|
|
||||||
if (originalCall.response.status == HttpStatusCode.Unauthorized) {
|
|
||||||
Napier.d(">>>>> [NetworkEngine.ios.kt] Access token expired, auto request refresh token...")
|
|
||||||
val newToken = authApi.refreshToken()
|
|
||||||
if (newToken != null) {
|
|
||||||
request.headers["Authorization"] = "Bearer $newToken"
|
|
||||||
execute(request)
|
|
||||||
} else {
|
|
||||||
tokenManager.clearTokens()
|
|
||||||
throw ForceLogoutException()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
originalCall
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return client
|
|
||||||
}
|
}
|
||||||
+18
@@ -3,10 +3,13 @@ package com.digitoolsolutions.app.torquevaultkmp
|
|||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.AppStorage
|
import com.digitoolsolutions.app.torquevaultkmp.data.storage.AppStorage
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.ReferKeys
|
import com.digitoolsolutions.app.torquevaultkmp.data.storage.ReferKeys
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.TokenManager
|
import com.digitoolsolutions.app.torquevaultkmp.data.storage.TokenManager
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.data.storage.load
|
import com.digitoolsolutions.app.torquevaultkmp.data.storage.load
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class AppViewModel(
|
class AppViewModel(
|
||||||
private val tokenManager: TokenManager,
|
private val tokenManager: TokenManager,
|
||||||
@@ -16,6 +19,21 @@ class AppViewModel(
|
|||||||
private val _isDarkTheme = mutableStateOf<Boolean?>(storage.load(ReferKeys.THEME))
|
private val _isDarkTheme = mutableStateOf<Boolean?>(storage.load(ReferKeys.THEME))
|
||||||
val isDarkTheme: State<Boolean?> = _isDarkTheme
|
val isDarkTheme: State<Boolean?> = _isDarkTheme
|
||||||
|
|
||||||
|
private val _sessionExpired = mutableStateOf(false)
|
||||||
|
val sessionExpired: State<Boolean> = _sessionExpired
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
tokenManager.sessionExpired.collectLatest {
|
||||||
|
_sessionExpired.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dismissSessionExpired() {
|
||||||
|
_sessionExpired.value = false
|
||||||
|
}
|
||||||
|
|
||||||
fun isLoggedIn(): Boolean {
|
fun isLoggedIn(): Boolean {
|
||||||
return tokenManager.getRefreshToken() != null
|
return tokenManager.getRefreshToken() != null
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -1,6 +1,16 @@
|
|||||||
package com.digitoolsolutions.app.torquevaultkmp.data.storage
|
package com.digitoolsolutions.app.torquevaultkmp.data.storage
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
|
||||||
class TokenManager(private val storage: AppStorage) {
|
class TokenManager(private val storage: AppStorage) {
|
||||||
|
private val _sessionExpired = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
|
||||||
|
val sessionExpired = _sessionExpired.asSharedFlow()
|
||||||
|
|
||||||
|
fun triggerSessionExpired() {
|
||||||
|
_sessionExpired.tryEmit(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
private var accessToken: String? = null
|
private var accessToken: String? = null
|
||||||
private var refreshToken: String? = null
|
private var refreshToken: String? = null
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -13,6 +13,7 @@ import com.digitoolsolutions.app.torquevaultkmp.screens.home.HomeViewModel
|
|||||||
import com.digitoolsolutions.app.torquevaultkmp.screens.settings.SettingViewModel
|
import com.digitoolsolutions.app.torquevaultkmp.screens.settings.SettingViewModel
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
import org.koin.core.module.dsl.factoryOf
|
import org.koin.core.module.dsl.factoryOf
|
||||||
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.core.qualifier.named
|
import org.koin.core.qualifier.named
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
@@ -41,6 +42,6 @@ val networkModule = module {
|
|||||||
val viewModelModule = module {
|
val viewModelModule = module {
|
||||||
factoryOf(::HomeViewModel)
|
factoryOf(::HomeViewModel)
|
||||||
factory { AuthViewModel(get(), get()) }
|
factory { AuthViewModel(get(), get()) }
|
||||||
factoryOf(::AppViewModel)
|
singleOf(::AppViewModel)
|
||||||
factoryOf(::SettingViewModel)
|
factoryOf(::SettingViewModel)
|
||||||
}
|
}
|
||||||
|
|||||||
+28
@@ -2,6 +2,8 @@ package com.digitoolsolutions.app.torquevaultkmp.screens
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.NavigationBar
|
import androidx.compose.material3.NavigationBar
|
||||||
import androidx.compose.material3.NavigationBarItem
|
import androidx.compose.material3.NavigationBarItem
|
||||||
@@ -14,6 +16,7 @@ import androidx.navigation.compose.NavHost
|
|||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.AppViewModel
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.screens.auth.AuthDestination
|
import com.digitoolsolutions.app.torquevaultkmp.screens.auth.AuthDestination
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.screens.auth.LoginScreen
|
import com.digitoolsolutions.app.torquevaultkmp.screens.auth.LoginScreen
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.screens.bonded.BondedDestination
|
import com.digitoolsolutions.app.torquevaultkmp.screens.bonded.BondedDestination
|
||||||
@@ -25,12 +28,17 @@ import com.digitoolsolutions.app.torquevaultkmp.screens.logs.LogScreen
|
|||||||
import com.digitoolsolutions.app.torquevaultkmp.screens.settings.SettingDestination
|
import com.digitoolsolutions.app.torquevaultkmp.screens.settings.SettingDestination
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.screens.settings.SettingScreen
|
import com.digitoolsolutions.app.torquevaultkmp.screens.settings.SettingScreen
|
||||||
import org.jetbrains.compose.resources.painterResource
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
import torquevaultkmp.composeapp.generated.resources.Res
|
||||||
|
import torquevaultkmp.composeapp.generated.resources.btn_login
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainScreen(
|
fun MainScreen(
|
||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
onThemeToggle: (Boolean) -> Unit,
|
onThemeToggle: (Boolean) -> Unit,
|
||||||
isLoggedIn: Boolean,
|
isLoggedIn: Boolean,
|
||||||
|
viewModel: AppViewModel = koinViewModel()
|
||||||
) {
|
) {
|
||||||
val destinations = listOf(
|
val destinations = listOf(
|
||||||
HomeDestination,
|
HomeDestination,
|
||||||
@@ -42,6 +50,26 @@ fun MainScreen(
|
|||||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
val currentDestination = navBackStackEntry?.destination
|
val currentDestination = navBackStackEntry?.destination
|
||||||
|
|
||||||
|
val sessionExpired by viewModel.sessionExpired
|
||||||
|
|
||||||
|
if (sessionExpired) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { },
|
||||||
|
title = { Text("Session Expired") },
|
||||||
|
text = { Text("Your session has expired. Please login again.") },
|
||||||
|
confirmButton = {
|
||||||
|
Button(onClick = {
|
||||||
|
viewModel.dismissSessionExpired()
|
||||||
|
navController.navigate(AuthDestination.route) {
|
||||||
|
popUpTo(0) { inclusive = true }
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text(stringResource(Res.string.btn_login))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
|
|||||||
-12
@@ -78,18 +78,6 @@ fun HomeScreen(
|
|||||||
viewModel.loadWorkOrders()
|
viewModel.loadWorkOrders()
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(viewModel) {
|
|
||||||
viewModel.error.collectLatest { error ->
|
|
||||||
if (error is ForceLogoutException) {
|
|
||||||
navController.navigate(AuthDestination.route) {
|
|
||||||
popUpTo(0) { inclusive = true }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
snackbarHostState.showSnackbar(error.message ?: "Unknown error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.imePadding(),
|
modifier = Modifier.imePadding(),
|
||||||
topBar = {
|
topBar = {
|
||||||
|
|||||||
+2
-5
@@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import com.digitoolsolutions.app.torquevaultkmp.domain.model.WorkOrder
|
import com.digitoolsolutions.app.torquevaultkmp.domain.model.WorkOrder
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.domain.usecase.FetchWorkOrdersUseCase
|
import com.digitoolsolutions.app.torquevaultkmp.domain.usecase.FetchWorkOrdersUseCase
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.utils.ForceLogoutException
|
import com.digitoolsolutions.app.torquevaultkmp.utils.ForceLogoutException
|
||||||
|
import io.github.aakira.napier.Napier
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
@@ -16,17 +17,13 @@ class HomeViewModel(
|
|||||||
): ViewModel() {
|
): ViewModel() {
|
||||||
private val _workOrders = MutableStateFlow<List<WorkOrder>>(emptyList())
|
private val _workOrders = MutableStateFlow<List<WorkOrder>>(emptyList())
|
||||||
val workOrders = _workOrders.asStateFlow()
|
val workOrders = _workOrders.asStateFlow()
|
||||||
|
|
||||||
private val _error = MutableSharedFlow<Throwable>()
|
|
||||||
val error = _error.asSharedFlow()
|
|
||||||
|
|
||||||
fun loadWorkOrders() {
|
fun loadWorkOrders() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
val result = fetchWorkOrdersUseCase(page = 1)
|
val result = fetchWorkOrdersUseCase(page = 1)
|
||||||
_workOrders.value = result
|
_workOrders.value = result
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_error.emit(e)
|
Napier.d("$e", tag="HomeViewModel")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -19,6 +19,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.digitoolsolutions.app.torquevaultkmp.components.CircularIcon
|
import com.digitoolsolutions.app.torquevaultkmp.components.CircularIcon
|
||||||
|
import com.digitoolsolutions.app.torquevaultkmp.domain.model.WorkOrder
|
||||||
|
|
||||||
//import com.digitoolsolutions.android.components.ui.CircularIcon
|
//import com.digitoolsolutions.android.components.ui.CircularIcon
|
||||||
//import com.digitoolsolutions.android.uart.spec.ConnectionState
|
//import com.digitoolsolutions.android.uart.spec.ConnectionState
|
||||||
@@ -27,7 +28,7 @@ import com.digitoolsolutions.app.torquevaultkmp.components.CircularIcon
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
//fun HomeItem(device: UartDevice, modifier: Modifier = Modifier) {
|
//fun HomeItem(device: UartDevice, modifier: Modifier = Modifier) {
|
||||||
fun HomeItem(device: Any, modifier: Modifier = Modifier) {
|
fun HomeItem(device: WorkOrder, modifier: Modifier = Modifier) {
|
||||||
OutlinedCard(
|
OutlinedCard(
|
||||||
modifier = modifier.fillMaxWidth().padding(vertical = 4.dp),
|
modifier = modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||||
@@ -50,6 +51,7 @@ fun HomeItem(device: Any, modifier: Modifier = Modifier) {
|
|||||||
// style = MaterialTheme.typography.bodyMedium,
|
// style = MaterialTheme.typography.bodyMedium,
|
||||||
// color = MaterialTheme.colorScheme.onSurfaceVariant
|
// color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
// )
|
// )
|
||||||
|
Text(text=device.id)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
// val statusColor = when (device.status) {
|
// val statusColor = when (device.status) {
|
||||||
|
|||||||
+25
-30
@@ -10,6 +10,9 @@ import io.ktor.client.engine.darwin.Darwin
|
|||||||
import io.ktor.client.plugins.DefaultRequest
|
import io.ktor.client.plugins.DefaultRequest
|
||||||
import io.ktor.client.plugins.HttpSend
|
import io.ktor.client.plugins.HttpSend
|
||||||
import io.ktor.client.plugins.HttpTimeout
|
import io.ktor.client.plugins.HttpTimeout
|
||||||
|
import io.ktor.client.plugins.auth.Auth
|
||||||
|
import io.ktor.client.plugins.auth.providers.BearerTokens
|
||||||
|
import io.ktor.client.plugins.auth.providers.bearer
|
||||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||||
import io.ktor.client.plugins.logging.LogLevel
|
import io.ktor.client.plugins.logging.LogLevel
|
||||||
import io.ktor.client.plugins.logging.Logger
|
import io.ktor.client.plugins.logging.Logger
|
||||||
@@ -18,22 +21,40 @@ import io.ktor.client.plugins.plugin
|
|||||||
import io.ktor.client.request.header
|
import io.ktor.client.request.header
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.serialization.kotlinx.json.json
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
actual fun createPlatformHttpClient(
|
actual fun createPlatformHttpClient(
|
||||||
tokenManager: TokenManager,
|
tokenManager: TokenManager,
|
||||||
authApi: AuthApi?
|
authApi: AuthApi?
|
||||||
): HttpClient {
|
): HttpClient {
|
||||||
val client = HttpClient(Darwin) {
|
return HttpClient(Darwin) {
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json(Json {
|
json(Json {
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
isLenient = true
|
isLenient = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
install(DefaultRequest) {
|
install(Auth) {
|
||||||
tokenManager.getAccessToken()?.let {
|
bearer {
|
||||||
header("Authorization", "Bearer $it")
|
loadTokens {
|
||||||
|
val access = tokenManager.getAccessToken()
|
||||||
|
val refresh = tokenManager.getRefreshToken()
|
||||||
|
if (access != null && refresh != null) {
|
||||||
|
BearerTokens(access, refresh)
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
refreshTokens {
|
||||||
|
Napier.d(">>>>> [NetworkEngine.ios.kt] Access token expired, auto request refresh token...")
|
||||||
|
val newAccess = authApi?.refreshToken()
|
||||||
|
if (newAccess != null) {
|
||||||
|
BearerTokens(newAccess, tokenManager.getRefreshToken()!!)
|
||||||
|
} else {
|
||||||
|
tokenManager.clearTokens()
|
||||||
|
tokenManager.triggerSessionExpired()
|
||||||
|
throw ForceLogoutException()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
install(HttpTimeout) { requestTimeoutMillis = 30_000 }
|
install(HttpTimeout) { requestTimeoutMillis = 30_000 }
|
||||||
@@ -46,30 +67,4 @@ actual fun createPlatformHttpClient(
|
|||||||
level = LogLevel.ALL // set LogLevel.NONE for release
|
level = LogLevel.ALL // set LogLevel.NONE for release
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authApi != null) {
|
|
||||||
client.plugin(HttpSend).intercept { request ->
|
|
||||||
// Skip interceptor for refresh token request to avoid recursion
|
|
||||||
if (request.url.pathSegments.contains(ReferKeys.REFRESH_TOKEN)) {
|
|
||||||
return@intercept execute(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
val originalCall = execute(request)
|
|
||||||
if (originalCall.response.status == HttpStatusCode.Unauthorized) {
|
|
||||||
Napier.d(">>>>> [NetworkEngine.ios.kt] Access token expired, auto request refresh token...")
|
|
||||||
val newToken = authApi.refreshToken()
|
|
||||||
if (newToken != null) {
|
|
||||||
request.headers["Authorization"] = "Bearer $newToken"
|
|
||||||
execute(request)
|
|
||||||
} else {
|
|
||||||
tokenManager.clearTokens()
|
|
||||||
throw ForceLogoutException()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
originalCall
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return client
|
|
||||||
}
|
}
|
||||||
@@ -53,6 +53,7 @@ ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negoti
|
|||||||
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||||
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
|
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
|
||||||
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
|
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
|
||||||
|
ktor-client-auth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor" }
|
||||||
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
|
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
|
||||||
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
|
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
|
||||||
logger-napier = { module = "io.github.aakira:napier", version.ref = "logger" }
|
logger-napier = { module = "io.github.aakira:napier", version.ref = "logger" }
|
||||||
@@ -69,7 +70,7 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi
|
|||||||
ktor = [
|
ktor = [
|
||||||
"ktor-client-core",
|
"ktor-client-core",
|
||||||
"ktor-client-content-negotiation",
|
"ktor-client-content-negotiation",
|
||||||
# "ktor-client-auth",
|
"ktor-client-auth",
|
||||||
"ktor-client-logging",
|
"ktor-client-logging",
|
||||||
"ktor-serialization-kotlinx-json"
|
"ktor-serialization-kotlinx-json"
|
||||||
]
|
]
|
||||||
Reference in New Issue
Block a user