
commonMain.com.supertokens.sdk.SuperTokensClient.kt Maven / Gradle / Ivy
package com.supertokens.sdk
import com.russhwolf.settings.Settings
import com.supertokens.sdk.recipes.BuildRecipe
import com.supertokens.sdk.recipes.Recipe
import com.supertokens.sdk.recipes.RecipeBuilder
import com.supertokens.sdk.recipes.RecipeConfig
import com.supertokens.sdk.repositories.AuthRepository
import com.supertokens.sdk.repositories.AuthState
import com.supertokens.sdk.repositories.user.UserRepository
import com.supertokens.sdk.repositories.user.UserRepositorySettings
import com.supertokens.sdk.repositories.AuthRepositoryImpl
import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.defaultRequest
import io.ktor.client.request.header
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.contentType
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
internal expect fun getDefaultSettings(): Settings
@SuperTokensDslMarker
class SuperTokensClientConfig(
val apiBaseUrl: String,
) {
var tenantId: String? = null
// Modify the http client config used by the SDK
var clientConfig: HttpClientConfig<*>.() -> Unit = {}
var userRepository: UserRepository? = null
var authRepository: AuthRepository? = null
var clientName: String = "MyMobileApp"
val scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
var recipes: List = emptyList()
private set
operator fun BuildRecipe.unaryPlus() {
recipes = recipes + this
}
fun > recipe(builder: RecipeBuilder, configure: C.() -> Unit = {}) {
+builder.install(configure)
}
}
class SuperTokensClient(
private val config: SuperTokensClientConfig,
) {
internal val scope: CoroutineScope
get() = config.scope
val tenantId: String?
get() = config.tenantId
val recipes: List> = config.recipes.map { it.invoke(this) }
@OptIn(ExperimentalSerializationApi::class)
val apiClient by lazy {
HttpClient {
install(ContentNegotiation) {
json(Json {
isLenient = true
explicitNulls = false
encodeDefaults = true
ignoreUnknownKeys = true
})
}
recipes.forEach {
with(it) {
configureClient()
}
}
config.clientConfig(this)
defaultRequest {
url(config.apiBaseUrl)
contentType(ContentType.Application.Json)
header(HttpHeaders.Origin, config.clientName)
}
}
}
val userRepository by lazy { config.userRepository ?: UserRepositorySettings(getDefaultSettings()) }
val authRepository by lazy { config.authRepository ?: AuthRepositoryImpl() }
private val _isInitialized = MutableStateFlow(false)
val isInitialized = _isInitialized.asStateFlow()
inline fun > getRecipe(): T = recipes.filterIsInstance().firstOrNull()
?: throw RuntimeException("Recipe ${T::class.simpleName} not configured")
inline fun > hasRecipe(): Boolean = recipes.filterIsInstance().isNotEmpty()
/** true, if the the user is at least logged in (but may not be authenticated from the backend yet)
* It essentially means, there is a refresh token present, but no access token yet, e.g. during startup
* when a new access token hasn't been fetched yet.
*/
fun isLoggedIn(): Boolean = authRepository.authState.value !is AuthState.Unauthenticated
// true, if the user was authenticated from the backend (an access token is present)
fun isAuthenticated():Boolean = authRepository.authState.value is AuthState.Authenticated
init {
scope.launch {
recipes.forEach { recipe ->
recipe.postInit()
}
_isInitialized.value = true
}
}
}
fun superTokensClient(apiBaseUrl: String, init: SuperTokensClientConfig.() -> Unit = {}): SuperTokensClient {
val config = SuperTokensClientConfig(
apiBaseUrl = apiBaseUrl,
).apply(init)
return SuperTokensClient(config)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy