diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index e7bd339..6181efb 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -59,10 +59,16 @@ kotlin { implementation(libs.androidx.activity.compose) // Android용 Ktor 엔진 implementation(libs.ktor.client.okhttp) + // Datastore + implementation(libs.datastore) + implementation(libs.datastore.preferences) } iosMain.dependencies { // iOS, MacOS용 Ktor 엔진 implementation(libs.ktor.client.darwin) + // Datastore + implementation(libs.datastore) + implementation(libs.datastore.preferences) } nativeMain.dependencies { // iOS, MacOS용 Ktor 엔진 @@ -84,6 +90,8 @@ kotlin { // Ktor 핵심 클라이언트 implementation(libs.ktor.client.core) implementation(libs.ktor.client.logging) + implementation(libs.ktor.client.auth) + implementation(libs.ktor.client.auth) // JSON 직렬화를 위해 implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.serialization.kotlinx.json) @@ -106,6 +114,9 @@ kotlin { implementation(libs.kotlinx.coroutinesSwing) // 데스크톱(JVM)용 Ktor 엔진 implementation(libs.ktor.client.okhttp) + // Datastore + implementation(libs.datastore) + implementation(libs.datastore.preferences) } wasmJsMain.dependencies { implementation(libs.ktor.client.cio) diff --git a/composeApp/src/androidMain/kotlin/org/whosin/client/datastore/createDataStore.android.kt b/composeApp/src/androidMain/kotlin/org/whosin/client/datastore/createDataStore.android.kt new file mode 100644 index 0000000..dd0cc5f --- /dev/null +++ b/composeApp/src/androidMain/kotlin/org/whosin/client/datastore/createDataStore.android.kt @@ -0,0 +1,13 @@ +package org.whosin.client.datastore + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import org.whosin.client.core.datastore.DATA_STORE_FILE_NAME +import org.whosin.client.core.datastore.createDataStore + +fun createDataStore(context: Context): DataStore { + return createDataStore { + context.filesDir.resolve(DATA_STORE_FILE_NAME).absolutePath + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/org/whosin/client/di/DIModules.android.kt b/composeApp/src/androidMain/kotlin/org/whosin/client/di/DIModules.android.kt index 531b6e4..963c1e8 100644 --- a/composeApp/src/androidMain/kotlin/org/whosin/client/di/DIModules.android.kt +++ b/composeApp/src/androidMain/kotlin/org/whosin/client/di/DIModules.android.kt @@ -1,11 +1,19 @@ package org.whosin.client.di +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences import io.ktor.client.engine.HttpClientEngine import io.ktor.client.engine.okhttp.OkHttp +import org.koin.android.ext.koin.androidContext import org.koin.core.module.Module import org.koin.dsl.module +import org.whosin.client.core.datastore.TokenManager +import org.whosin.client.core.datastore.TokenManagerImpl +import org.whosin.client.datastore.createDataStore actual val platformModule: Module get() = module { single { OkHttp.create() } + single> { createDataStore(androidContext())} + single { TokenManagerImpl(get()) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/App.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/App.kt index 6d4249c..6607201 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/App.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/App.kt @@ -7,6 +7,8 @@ import androidx.compose.ui.Modifier import androidx.navigation.compose.rememberNavController import org.jetbrains.compose.ui.tooling.preview.Preview import org.whosin.client.core.navigation.WhosInNavGraph +import org.whosin.client.presentation.dummy.DummyScreen +import org.whosin.client.presentation.dummy.TokenTestScreen import ui.theme.WhosInTheme @@ -25,5 +27,6 @@ fun App() { // Test용으로 남겨둔 코드, 추후 삭제 예정 // 확인하려면 위의 코드는 주석처리하고 실행 // DummyScreen() +// TokenTestScreen() } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/core/datastore/TokenManager.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/core/datastore/TokenManager.kt new file mode 100644 index 0000000..45575c8 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/core/datastore/TokenManager.kt @@ -0,0 +1,8 @@ +package org.whosin.client.core.datastore + +interface TokenManager { + suspend fun getAccessToken(): String? + suspend fun getRefreshToken(): String? + suspend fun saveTokens(accessToken: String, refreshToken: String) + suspend fun clearToken() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/core/datastore/TokenManagerImpl.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/core/datastore/TokenManagerImpl.kt new file mode 100644 index 0000000..eb1690b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/core/datastore/TokenManagerImpl.kt @@ -0,0 +1,28 @@ +package org.whosin.client.core.datastore + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import kotlinx.coroutines.flow.first + +class TokenManagerImpl( + private val dataStore: DataStore +): TokenManager { + private val accessKey = stringPreferencesKey("access_token") + private val refreshKey = stringPreferencesKey("refresh_token") + + override suspend fun getAccessToken(): String? = dataStore.data.first()[accessKey] + override suspend fun getRefreshToken(): String? = dataStore.data.first()[refreshKey] + + override suspend fun saveTokens(accessToken: String, refreshToken: String) { + dataStore.edit { + it[accessKey] = accessToken + it[refreshKey] = refreshToken + } + } + + override suspend fun clearToken() { + dataStore.edit { it.clear() } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/core/datastore/createDataStore.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/core/datastore/createDataStore.kt new file mode 100644 index 0000000..651550e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/core/datastore/createDataStore.kt @@ -0,0 +1,14 @@ +package org.whosin.client.core.datastore + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import androidx.datastore.preferences.core.Preferences +import okio.Path.Companion.toPath + +fun createDataStore(producePath: () -> String): DataStore { + return PreferenceDataStoreFactory.createWithPath( + produceFile = { producePath().toPath() } + ) +} + +internal const val DATA_STORE_FILE_NAME = "prefs.preferences_pb" \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/core/network/HttpClientFactory.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/core/network/HttpClientFactory.kt index 3bc4ad4..d1627b5 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/core/network/HttpClientFactory.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/core/network/HttpClientFactory.kt @@ -1,24 +1,35 @@ package org.whosin.client.core.network import io.ktor.client.HttpClient -import io.ktor.client.HttpClientConfig +import io.ktor.client.call.body import io.ktor.client.engine.HttpClientEngine import io.ktor.client.plugins.HttpTimeout -import io.ktor.client.plugins.HttpTimeoutConfig +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.defaultRequest import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.Logger import io.ktor.client.plugins.logging.Logging +import io.ktor.client.request.post +import io.ktor.client.request.setBody import io.ktor.http.ContentType import io.ktor.http.contentType +import io.ktor.http.encodedPath import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json import org.whosin.client.BuildKonfig +import org.whosin.client.core.datastore.TokenManager +import org.whosin.client.data.dto.request.ReissueTokenRequestDto +import org.whosin.client.data.dto.response.TokenDto object HttpClientFactory { val BASE_URL = BuildKonfig.BASE_URL - fun create(engine: HttpClientEngine): HttpClient { + fun create( + engine: HttpClientEngine, + tokenManager: TokenManager + ): HttpClient { return HttpClient(engine) { install(ContentNegotiation) { json( @@ -33,13 +44,64 @@ object HttpClientFactory { socketTimeoutMillis = 20_000L requestTimeoutMillis = 20_000L } + install(Auth){ + bearer { + loadTokens { + val accessToken = tokenManager.getAccessToken() ?: "no_token" + val refreshToken = tokenManager.getRefreshToken() ?: "no_token" + BearerTokens(accessToken = accessToken, refreshToken = refreshToken) + } + sendWithoutRequest { request -> + val host = "https://"+request.url.host+"/" + val path = request.url.encodedPath + val pathWithNoAuth = listOf( + "jokes", + "users/signup", + "users/find-password", + "auth/login", + "auth/email", + "auth/email/validation" + ) + // 결과가 true면 Authorization 헤더 추가, false면 제거 + if(host != BASE_URL){ + println("External API - No Auth") + false + }else{ + // pathWithNoAuth에 있는 경로에는 Authorization 헤더 제외 + val isNoAuthPath = pathWithNoAuth.any { noAuthPath -> + path.startsWith(noAuthPath) || path.contains(noAuthPath) + } + println("isNoAuthPath: $isNoAuthPath") + !isNoAuthPath + } + } + refreshTokens { + val rt = tokenManager.getRefreshToken() ?: "no_token" + val response = client.post("member/reissue"){ + setBody { + ReissueTokenRequestDto( + refreshToken = rt + ) + } + markAsRefreshTokenRequest() + }.body() + tokenManager.saveTokens( + accessToken = response.accessToken, + refreshToken = response.refreshToken + ) + val accessToken = response.accessToken + val refreshToken = response.refreshToken + BearerTokens(accessToken,refreshToken) + } + } + } install(Logging){ logger = object : Logger { override fun log(message: String) { println(message) } } - level = LogLevel.BODY + level = LogLevel.ALL } defaultRequest { contentType(ContentType.Application.Json) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/request/ReissueTokenRequestDto.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/request/ReissueTokenRequestDto.kt new file mode 100644 index 0000000..166fd9b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/data/dto/request/ReissueTokenRequestDto.kt @@ -0,0 +1,10 @@ +package org.whosin.client.data.dto.request + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ReissueTokenRequestDto( + @SerialName("refreshToken") + val refreshToken: String +) diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/di/DIModules.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/di/DIModules.kt index ed87075..8d749dc 100644 --- a/composeApp/src/commonMain/kotlin/org/whosin/client/di/DIModules.kt +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/di/DIModules.kt @@ -11,6 +11,7 @@ import org.whosin.client.data.repository.DummyRepository import org.whosin.client.data.repository.ClubRepository import org.whosin.client.data.repository.MemberRepository import org.whosin.client.presentation.dummy.DummyViewModel +import org.whosin.client.presentation.dummy.TokenTestViewModel import org.whosin.client.presentation.auth.login.viewmodel.LoginViewModel import org.whosin.client.presentation.home.HomeViewModel import org.whosin.client.presentation.mypage.MyPageViewModel @@ -26,7 +27,7 @@ fun appModule() = listOf( expect val platformModule: Module val httpClientModule = module { - single{ HttpClientFactory.create(get()) } + single{ HttpClientFactory.create(get(), get()) } } val dataSourceModule = module { @@ -47,4 +48,5 @@ val viewModelModule = module { viewModelOf(::HomeViewModel) viewModelOf(::MyPageViewModel) viewModelOf(::DummyViewModel) // TODO: 이후에 삭제 예정 + viewModelOf(::TokenTestViewModel) // TODO: 이후에 삭제 예정 } diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/dummy/TokenTestScreen.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/dummy/TokenTestScreen.kt new file mode 100644 index 0000000..261f25d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/dummy/TokenTestScreen.kt @@ -0,0 +1,185 @@ +package org.whosin.client.presentation.dummy + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import org.koin.compose.viewmodel.koinViewModel + +@Composable +fun TokenTestScreen( + modifier: Modifier = Modifier +) { + val viewModel: TokenTestViewModel = koinViewModel() + val accessToken by viewModel.accessToken.collectAsStateWithLifecycle() + val refreshToken by viewModel.refreshToken.collectAsStateWithLifecycle() + + var accessTokenInput by remember { mutableStateOf("") } + var refreshTokenInput by remember { mutableStateOf("") } + + val hasTokens = !accessToken.isNullOrEmpty() && !refreshToken.isNullOrEmpty() + + Column( + modifier = modifier + .fillMaxSize() + .padding(16.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + // 제목 + Text( + text = "Token Manager Test", + modifier = Modifier.align(Alignment.CenterHorizontally) + ) + + // 현재 토큰 상태 표시 + Card( + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + Text( + text = "현재 토큰 상태", + ) + Spacer(modifier = Modifier.size(12.dp)) + + Text( + text = "상태: ${if (hasTokens) "토큰 저장됨" else "토큰 없음"}", + ) + + if (hasTokens) { + Spacer(modifier = Modifier.size(8.dp)) + Text( + text = "Access Token:", + ) + Text( + text = accessToken ?: "null", + modifier = Modifier.padding(start = 8.dp) + ) + + Spacer(modifier = Modifier.size(4.dp)) + Text( + text = "Refresh Token:", + ) + Text( + text = refreshToken ?: "null", + modifier = Modifier.padding(start = 8.dp) + ) + } + } + } + + // 빠른 테스트 버튼들 + Card( + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + Text( + text = "빠른 테스트", + ) + Spacer(modifier = Modifier.size(12.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Button( + onClick = { viewModel.saveTestTokens() }, + modifier = Modifier.weight(1f) + ) { + Text("테스트 토큰 저장") + } + + Button( + onClick = { viewModel.clearTokens() }, + modifier = Modifier.weight(1f) + ) { + Text("토큰 삭제") + } + } + + Spacer(modifier = Modifier.size(8.dp)) + + Button( + onClick = { viewModel.loadTokens() }, + modifier = Modifier.fillMaxWidth() + ) { + Text("토큰 정보 새로고침") + } + } + } + + // 커스텀 토큰 입력 + Card( + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + Text( + text = "커스텀 토큰 저장", + ) + Spacer(modifier = Modifier.size(12.dp)) + + OutlinedTextField( + value = accessTokenInput, + onValueChange = { accessTokenInput = it }, + label = { Text("Access Token") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true + ) + + Spacer(modifier = Modifier.size(8.dp)) + + OutlinedTextField( + value = refreshTokenInput, + onValueChange = { refreshTokenInput = it }, + label = { Text("Refresh Token") }, + modifier = Modifier.fillMaxWidth(), + singleLine = true + ) + + Spacer(modifier = Modifier.size(12.dp)) + + Button( + onClick = { + if (accessTokenInput.isNotBlank() && refreshTokenInput.isNotBlank()) { + viewModel.saveCustomTokens(accessTokenInput, refreshTokenInput) + accessTokenInput = "" + refreshTokenInput = "" + } + }, + modifier = Modifier.fillMaxWidth(), + enabled = accessTokenInput.isNotBlank() && refreshTokenInput.isNotBlank() + ) { + Text("커스텀 토큰 저장") + } + } + } + } +} diff --git a/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/dummy/TokenTestViewModel.kt b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/dummy/TokenTestViewModel.kt new file mode 100644 index 0000000..479a757 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/whosin/client/presentation/dummy/TokenTestViewModel.kt @@ -0,0 +1,55 @@ +package org.whosin.client.presentation.dummy + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import org.whosin.client.core.datastore.TokenManager + +class TokenTestViewModel( + private val tokenManager: TokenManager +) : ViewModel() { + + private val _accessToken = MutableStateFlow(null) + val accessToken: StateFlow = _accessToken.asStateFlow() + + private val _refreshToken = MutableStateFlow(null) + val refreshToken: StateFlow = _refreshToken.asStateFlow() + + init { + loadTokens() + } + + fun loadTokens() { + viewModelScope.launch { + _accessToken.value = tokenManager.getAccessToken() + _refreshToken.value = tokenManager.getRefreshToken() + } + } + + fun saveTestTokens() { + viewModelScope.launch { + tokenManager.saveTokens( + accessToken = "test_access_token", + refreshToken = "test_refresh_token" + ) + loadTokens() + } + } + + fun saveCustomTokens(accessToken: String, refreshToken: String) { + viewModelScope.launch { + tokenManager.saveTokens(accessToken, refreshToken) + loadTokens() + } + } + + fun clearTokens() { + viewModelScope.launch { + tokenManager.clearToken() + loadTokens() + } + } +} diff --git a/composeApp/src/iosMain/kotlin/org/whosin/client/datastore/createDataStore.ios.kt b/composeApp/src/iosMain/kotlin/org/whosin/client/datastore/createDataStore.ios.kt new file mode 100644 index 0000000..b047878 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/org/whosin/client/datastore/createDataStore.ios.kt @@ -0,0 +1,24 @@ +package org.whosin.client.datastore + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import kotlinx.cinterop.ExperimentalForeignApi +import org.whosin.client.core.datastore.DATA_STORE_FILE_NAME +import org.whosin.client.core.datastore.createDataStore +import platform.Foundation.NSDocumentDirectory +import platform.Foundation.NSFileManager +import platform.Foundation.NSUserDomainMask + +@OptIn(ExperimentalForeignApi::class) +fun createDataStore(): DataStore { + return createDataStore { + val directory = NSFileManager.defaultManager.URLForDirectory( + directory = NSDocumentDirectory, + inDomain = NSUserDomainMask, + appropriateForURL = null, + create = false, + error = null + ) + requireNotNull(directory).path + "/$DATA_STORE_FILE_NAME" + } +} diff --git a/composeApp/src/iosMain/kotlin/org/whosin/client/di/DIModules.ios.kt b/composeApp/src/iosMain/kotlin/org/whosin/client/di/DIModules.ios.kt index 107852e..a69085e 100644 --- a/composeApp/src/iosMain/kotlin/org/whosin/client/di/DIModules.ios.kt +++ b/composeApp/src/iosMain/kotlin/org/whosin/client/di/DIModules.ios.kt @@ -1,11 +1,18 @@ package org.whosin.client.di +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences import io.ktor.client.engine.HttpClientEngine import io.ktor.client.engine.darwin.Darwin import org.koin.core.module.Module import org.koin.dsl.module +import org.whosin.client.core.datastore.TokenManager +import org.whosin.client.core.datastore.TokenManagerImpl +import org.whosin.client.datastore.createDataStore actual val platformModule: Module get() = module { single { Darwin.create() } + single> { createDataStore() } + single { TokenManagerImpl(get()) } } diff --git a/composeApp/src/jvmMain/kotlin/org/whosin/client/datastore/createDataStore.jvm.kt b/composeApp/src/jvmMain/kotlin/org/whosin/client/datastore/createDataStore.jvm.kt new file mode 100644 index 0000000..c1d0fb5 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/org/whosin/client/datastore/createDataStore.jvm.kt @@ -0,0 +1,12 @@ +package org.whosin.client.datastore + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import org.whosin.client.core.datastore.DATA_STORE_FILE_NAME +import org.whosin.client.core.datastore.createDataStore + +fun createDataStore(): DataStore { + return createDataStore { + DATA_STORE_FILE_NAME + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/org/whosin/client/di/DIModules.jvm.kt b/composeApp/src/jvmMain/kotlin/org/whosin/client/di/DIModules.jvm.kt index 160ee45..b7f04c4 100644 --- a/composeApp/src/jvmMain/kotlin/org/whosin/client/di/DIModules.jvm.kt +++ b/composeApp/src/jvmMain/kotlin/org/whosin/client/di/DIModules.jvm.kt @@ -1,11 +1,18 @@ package org.whosin.client.di +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences import io.ktor.client.engine.HttpClientEngine import io.ktor.client.engine.okhttp.OkHttp import org.koin.core.module.Module import org.koin.dsl.module +import org.whosin.client.core.datastore.TokenManager +import org.whosin.client.core.datastore.TokenManagerImpl +import org.whosin.client.datastore.createDataStore actual val platformModule: Module get() = module { single { OkHttp.create() } + single> { createDataStore() } + single { TokenManagerImpl(get()) } } diff --git a/composeApp/src/wasmJsMain/kotlin/org/whosin/client/datastore/JsTokenManagerImpl.kt b/composeApp/src/wasmJsMain/kotlin/org/whosin/client/datastore/JsTokenManagerImpl.kt new file mode 100644 index 0000000..bc6c673 --- /dev/null +++ b/composeApp/src/wasmJsMain/kotlin/org/whosin/client/datastore/JsTokenManagerImpl.kt @@ -0,0 +1,21 @@ +package org.whosin.client.datastore + +import org.whosin.client.core.datastore.TokenManager + +class JsTokenManagerImpl : TokenManager{ + override suspend fun getAccessToken(): String? { + TODO("Not yet implemented") + } + + override suspend fun getRefreshToken(): String? { + TODO("Not yet implemented") + } + + override suspend fun saveTokens(accessToken: String, refreshToken: String) { + TODO("Not yet implemented") + } + + override suspend fun clearToken() { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/composeApp/src/wasmJsMain/kotlin/org/whosin/client/di/DIModules.wasmJs.kt b/composeApp/src/wasmJsMain/kotlin/org/whosin/client/di/DIModules.wasmJs.kt index bc50696..1661dda 100644 --- a/composeApp/src/wasmJsMain/kotlin/org/whosin/client/di/DIModules.wasmJs.kt +++ b/composeApp/src/wasmJsMain/kotlin/org/whosin/client/di/DIModules.wasmJs.kt @@ -4,8 +4,11 @@ import io.ktor.client.engine.HttpClientEngine import io.ktor.client.engine.cio.CIO import org.koin.core.module.Module import org.koin.dsl.module +import org.whosin.client.core.datastore.TokenManager +import org.whosin.client.datastore.JsTokenManagerImpl actual val platformModule: Module get() = module { single { CIO.create() } + single { JsTokenManagerImpl() } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aa2d625..a16eb08 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,6 +20,7 @@ ktor = "3.2.3" kotlinx-serialization = "1.9.0" koin = "4.1.0" buildkonfig = "0.17.1" +datastore = "1.1.7" [libraries] coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilSvg" } @@ -55,6 +56,9 @@ koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" } koin-compose-viewmodel-navigation = { module = "io.insert-koin:koin-compose-viewmodel-navigation", version.ref = "koin" } +# DataStore +datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" } +datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" }