mirror of
https://github.com/bitwarden/android.git
synced 2026-06-07 14:57:41 -05:00
BIT-752: Add Environment/EnvironmentRepository/EnvironmentDiskSource (#151)
This commit is contained in:
committed by
Álison Fernandes
parent
f4dbe68527
commit
e4ab70a106
@@ -1,16 +1,15 @@
|
||||
package com.x8bit.bitwarden.data.auth.datasource.disk
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import androidx.core.content.edit
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
private const val BASE_KEY = "bwPreferencesStorage"
|
||||
private const val REMEMBERED_EMAIL_ADDRESS_KEY = "$BASE_KEY:rememberedEmail"
|
||||
private const val STATE_KEY = "$BASE_KEY:state"
|
||||
|
||||
@@ -18,9 +17,10 @@ private const val STATE_KEY = "$BASE_KEY:state"
|
||||
* Primary implementation of [AuthDiskSource].
|
||||
*/
|
||||
class AuthDiskSourceImpl(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
sharedPreferences: SharedPreferences,
|
||||
private val json: Json,
|
||||
) : AuthDiskSource {
|
||||
) : BaseDiskSource(sharedPreferences = sharedPreferences),
|
||||
AuthDiskSource {
|
||||
override var rememberedEmailAddress: String?
|
||||
get() = getString(key = REMEMBERED_EMAIL_ADDRESS_KEY)
|
||||
set(value) {
|
||||
@@ -48,25 +48,12 @@ class AuthDiskSourceImpl(
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
|
||||
private val onSharedPreferenceChangeListener =
|
||||
OnSharedPreferenceChangeListener { _, key ->
|
||||
when (key) {
|
||||
STATE_KEY -> mutableUserStateFlow.tryEmit(userState)
|
||||
}
|
||||
override fun onSharedPreferenceChanged(
|
||||
sharedPreferences: SharedPreferences?,
|
||||
key: String?,
|
||||
) {
|
||||
when (key) {
|
||||
STATE_KEY -> mutableUserStateFlow.tryEmit(userState)
|
||||
}
|
||||
|
||||
init {
|
||||
sharedPreferences
|
||||
.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener)
|
||||
}
|
||||
|
||||
private fun getString(
|
||||
key: String,
|
||||
default: String? = null,
|
||||
): String? = sharedPreferences.getString(key, default)
|
||||
|
||||
private fun putString(
|
||||
key: String,
|
||||
value: String?,
|
||||
): Unit = sharedPreferences.edit { putString(key, value) }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import androidx.core.content.edit
|
||||
|
||||
/**
|
||||
* Base class for simplifying interactions with [SharedPreferences].
|
||||
*/
|
||||
abstract class BaseDiskSource(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
) : OnSharedPreferenceChangeListener {
|
||||
|
||||
init {
|
||||
@Suppress("LeakingThis")
|
||||
sharedPreferences
|
||||
.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
protected fun getString(
|
||||
key: String,
|
||||
default: String? = null,
|
||||
): String? = sharedPreferences.getString(key, default)
|
||||
|
||||
protected fun putString(
|
||||
key: String,
|
||||
value: String?,
|
||||
): Unit = sharedPreferences.edit { putString(key, value) }
|
||||
|
||||
companion object {
|
||||
const val BASE_KEY: String = "bwPreferencesStorage"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Primary access point for general environment-related disk information.
|
||||
*/
|
||||
interface EnvironmentDiskSource {
|
||||
/**
|
||||
* The currently persisted [EnvironmentUrlDataJson] (or `null` if not set).
|
||||
*/
|
||||
var preAuthEnvironmentUrlData: EnvironmentUrlDataJson?
|
||||
|
||||
/**
|
||||
* Emits updates that track [preAuthEnvironmentUrlData]. This will replay the last known value,
|
||||
* if any.
|
||||
*/
|
||||
val preAuthEnvironmentUrlDataFlow: Flow<EnvironmentUrlDataJson?>
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
private const val PRE_AUTH_URLS_KEY = "$BASE_KEY:preAuthEnvironmentUrls"
|
||||
|
||||
/**
|
||||
* Primary implementation of [EnvironmentDiskSource].
|
||||
*/
|
||||
class EnvironmentDiskSourceImpl(
|
||||
sharedPreferences: SharedPreferences,
|
||||
private val json: Json,
|
||||
) : BaseDiskSource(sharedPreferences = sharedPreferences),
|
||||
EnvironmentDiskSource {
|
||||
override var preAuthEnvironmentUrlData: EnvironmentUrlDataJson?
|
||||
get() = getString(key = PRE_AUTH_URLS_KEY)?.let { json.decodeFromString(it) }
|
||||
set(value) {
|
||||
putString(
|
||||
key = PRE_AUTH_URLS_KEY,
|
||||
value = value?.let { json.encodeToString(value) },
|
||||
)
|
||||
}
|
||||
|
||||
override val preAuthEnvironmentUrlDataFlow: Flow<EnvironmentUrlDataJson?>
|
||||
get() = mutableEnvironmentUrlDataFlow
|
||||
.onSubscription { emit(preAuthEnvironmentUrlData) }
|
||||
|
||||
private val mutableEnvironmentUrlDataFlow = MutableSharedFlow<EnvironmentUrlDataJson?>(
|
||||
replay = 1,
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
|
||||
override fun onSharedPreferenceChanged(
|
||||
sharedPreferences: SharedPreferences?,
|
||||
key: String?,
|
||||
) {
|
||||
when (key) {
|
||||
PRE_AUTH_URLS_KEY -> mutableEnvironmentUrlDataFlow.tryEmit(preAuthEnvironmentUrlData)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.disk.di
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSourceImpl
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.json.Json
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Provides persistence-related dependencies in the platform package.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DiskModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideEnvironmentDiskSource(
|
||||
sharedPreferences: SharedPreferences,
|
||||
json: Json,
|
||||
): EnvironmentDiskSource =
|
||||
EnvironmentDiskSourceImpl(
|
||||
sharedPreferences = sharedPreferences,
|
||||
json = json,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.x8bit.bitwarden.data.platform.repository
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* Provides an API for observing and modifying environment state.
|
||||
*/
|
||||
interface EnvironmentRepository {
|
||||
/**
|
||||
* The currently set environment.
|
||||
*/
|
||||
var environment: Environment
|
||||
|
||||
/**
|
||||
* Emits updates that track [environment].
|
||||
*/
|
||||
val environmentStateFlow: StateFlow<Environment>
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.x8bit.bitwarden.data.platform.repository
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.toEnvironmentUrls
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
/**
|
||||
* Primary implementation of [EnvironmentRepository].
|
||||
*/
|
||||
class EnvironmentRepositoryImpl(
|
||||
private val environmentDiskSource: EnvironmentDiskSource,
|
||||
private val dispatcher: CoroutineDispatcher,
|
||||
) : EnvironmentRepository {
|
||||
|
||||
private val scope = CoroutineScope(dispatcher)
|
||||
|
||||
override var environment: Environment
|
||||
get() = environmentDiskSource
|
||||
.preAuthEnvironmentUrlData
|
||||
.toEnvironmentUrlsOrDefault()
|
||||
set(value) {
|
||||
environmentDiskSource.preAuthEnvironmentUrlData = value.environmentUrlData
|
||||
}
|
||||
|
||||
override val environmentStateFlow: StateFlow<Environment>
|
||||
get() = environmentDiskSource
|
||||
.preAuthEnvironmentUrlDataFlow
|
||||
.map { it.toEnvironmentUrlsOrDefault() }
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = Environment.Us,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a nullable [EnvironmentUrlDataJson] to an [Environment], where `null` values default to
|
||||
* the US environment.
|
||||
*/
|
||||
private fun EnvironmentUrlDataJson?.toEnvironmentUrlsOrDefault(): Environment =
|
||||
this?.toEnvironmentUrls() ?: Environment.Us
|
||||
@@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.onEach
|
||||
class NetworkConfigRepositoryImpl(
|
||||
private val authRepository: AuthRepository,
|
||||
private val authTokenInterceptor: AuthTokenInterceptor,
|
||||
private val environmentRepository: EnvironmentRepository,
|
||||
dispatcher: CoroutineDispatcher,
|
||||
) : NetworkConfigRepository {
|
||||
|
||||
@@ -30,5 +31,12 @@ class NetworkConfigRepositoryImpl(
|
||||
}
|
||||
}
|
||||
.launchIn(scope)
|
||||
|
||||
environmentRepository
|
||||
.environmentStateFlow
|
||||
.onEach { environment ->
|
||||
// TODO: Update base URL interceptors (BIT-725)
|
||||
}
|
||||
.launchIn(scope)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.x8bit.bitwarden.data.platform.repository.di
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepositoryImpl
|
||||
import com.x8bit.bitwarden.data.platform.repository.NetworkConfigRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.NetworkConfigRepositoryImpl
|
||||
import dagger.Module
|
||||
@@ -18,15 +21,27 @@ import javax.inject.Singleton
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object RepositoryModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideEnvironmentRepository(
|
||||
environmentDiskSource: EnvironmentDiskSource,
|
||||
): EnvironmentRepository =
|
||||
EnvironmentRepositoryImpl(
|
||||
environmentDiskSource = environmentDiskSource,
|
||||
dispatcher = Dispatchers.IO,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNetworkConfigRepository(
|
||||
authRepository: AuthRepository,
|
||||
authTokenInterceptor: AuthTokenInterceptor,
|
||||
environmentRepository: EnvironmentRepository,
|
||||
): NetworkConfigRepository =
|
||||
NetworkConfigRepositoryImpl(
|
||||
authRepository = authRepository,
|
||||
authTokenInterceptor = authTokenInterceptor,
|
||||
environmentRepository = environmentRepository,
|
||||
dispatcher = Dispatchers.IO,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.x8bit.bitwarden.data.platform.repository.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.parcelize.RawValue
|
||||
|
||||
/**
|
||||
* A higher-level wrapper around [EnvironmentUrlDataJson] that provides type-safety, enumerability,
|
||||
* and human-readable labels.
|
||||
*/
|
||||
sealed class Environment : Parcelable {
|
||||
/**
|
||||
* The [Type] of the environment.
|
||||
*/
|
||||
abstract val type: Type
|
||||
|
||||
/**
|
||||
* The raw [environmentUrlData] that contains specific base URLs for each relevant domain.
|
||||
*/
|
||||
abstract val environmentUrlData: EnvironmentUrlDataJson
|
||||
|
||||
/**
|
||||
* Helper for a returning a human-readable label from a [Type].
|
||||
*/
|
||||
val label: Text get() = type.label
|
||||
|
||||
/**
|
||||
* The default US environment.
|
||||
*/
|
||||
@Parcelize
|
||||
data object Us : Environment() {
|
||||
override val type: Type get() = Type.US
|
||||
override val environmentUrlData: EnvironmentUrlDataJson
|
||||
get() = EnvironmentUrlDataJson.DEFAULT_US
|
||||
}
|
||||
|
||||
/**
|
||||
* The default EU environment.
|
||||
*/
|
||||
@Parcelize
|
||||
data object Eu : Environment() {
|
||||
override val type: Type get() = Type.EU
|
||||
override val environmentUrlData: EnvironmentUrlDataJson
|
||||
get() = EnvironmentUrlDataJson.DEFAULT_EU
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom self-hosted environment with a fully configurable [environmentUrlData].
|
||||
*/
|
||||
@Parcelize
|
||||
data class SelfHosted(
|
||||
override val environmentUrlData: @RawValue EnvironmentUrlDataJson,
|
||||
) : Environment() {
|
||||
override val type: Type get() = Type.SELF_HOSTED
|
||||
}
|
||||
|
||||
/**
|
||||
* A summary of the various types that can be enumerated over and which contains a
|
||||
* human-readable [label].
|
||||
*/
|
||||
enum class Type(val label: Text) {
|
||||
US(label = "bitwarden.com".asText()),
|
||||
EU(label = "bitwarden.eu".asText()),
|
||||
SELF_HOSTED(label = R.string.self_hosted.asText()),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.x8bit.bitwarden.data.platform.repository.util
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
|
||||
/**
|
||||
* Converts a raw [EnvironmentUrlDataJson] to an externally-consumable [Environment].
|
||||
*/
|
||||
fun EnvironmentUrlDataJson.toEnvironmentUrls(): Environment =
|
||||
when (this) {
|
||||
Environment.Us.environmentUrlData -> Environment.Us
|
||||
Environment.Eu.environmentUrlData -> Environment.Eu
|
||||
else -> Environment.SelfHosted(environmentUrlData = this)
|
||||
}
|
||||
Reference in New Issue
Block a user