Network and storage layer, handle login, auto refresh token

This commit is contained in:
2026-05-15 00:12:01 +07:00
parent 5c4c9804ee
commit 04bb4bf1cd
43 changed files with 857 additions and 79 deletions
@@ -0,0 +1,31 @@
package com.digitoolsolutions.app.torquevaultkmp
import com.digitoolsolutions.app.torquevaultkmp.data.storage.AppStorage
import platform.Foundation.NSUserDefaults
import kotlin.reflect.KClass
class AppStoragePlatform : AppStorage {
private val defaults = NSUserDefaults.standardUserDefaults()
override fun <T : Any> save(key: String, value: T) {
when (value) {
is String -> defaults.setObject(value, forKey = key)
is Int -> defaults.setInteger(value.toLong(), forKey = key)
is Boolean -> defaults.setBool(value, forKey = key)
else -> throw IllegalArgumentException("Unsupported type")
}
}
@Suppress("UNCHECKED_CAST")
override fun <T : Any> load(key: String, type: KClass<T>): T? {
return when (type) {
String::class -> defaults.stringForKey(key) as T?
Int::class -> defaults.integerForKey(key).toInt() as T?
Boolean::class -> defaults.boolForKey(key) as T?
else -> throw IllegalArgumentException("Unsupported type")
}
}
override fun remove(key: String) {
defaults.removeObjectForKey(key)
}
}
@@ -1,5 +1,10 @@
package com.digitoolsolutions.app.torquevaultkmp
import androidx.compose.ui.window.ComposeUIViewController
import com.digitoolsolutions.app.torquevaultkmp.di.initKoin
fun MainViewController() = ComposeUIViewController { App() }
fun MainViewController() = ComposeUIViewController(
configure = {
initKoin()
}
) { App() }
@@ -0,0 +1,63 @@
package com.digitoolsolutions.app.torquevaultkmp
import com.digitoolsolutions.app.torquevaultkmp.data.network.api.AuthApi
import com.digitoolsolutions.app.torquevaultkmp.data.storage.ReferKeys
import com.digitoolsolutions.app.torquevaultkmp.data.storage.TokenManager
import com.digitoolsolutions.app.torquevaultkmp.utils.ForceLogoutException
import io.ktor.client.HttpClient
import io.ktor.client.engine.darwin.Darwin
import io.ktor.client.plugins.DefaultRequest
import io.ktor.client.plugins.HttpSend
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.plugin
import io.ktor.client.request.header
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
actual fun createPlatformHttpClient(
tokenManager: TokenManager,
authApi: AuthApi?
): HttpClient {
val client = HttpClient(Darwin) {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
isLenient = true
})
}
install(DefaultRequest) {
tokenManager.getAccessToken()?.let {
header("Authorization", "Bearer $it")
}
}
install(HttpTimeout) { requestTimeoutMillis = 30_000 }
}
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) {
println(">>>>> [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
}
@@ -0,0 +1,9 @@
package com.digitoolsolutions.app.torquevaultkmp.di
import com.digitoolsolutions.app.torquevaultkmp.AppStoragePlatform
import com.digitoolsolutions.app.torquevaultkmp.data.storage.AppStorage
import org.koin.dsl.module
actual val platformModule = module {
single<AppStorage> { AppStoragePlatform() }
}