@@ -8,6 +8,7 @@ import androidx.datastore.preferences.preferencesDataStore
88import dagger.hilt.android.qualifiers.ApplicationContext
99import kotlinx.coroutines.CoroutineDispatcher
1010import kotlinx.coroutines.flow.Flow
11+ import kotlinx.coroutines.flow.distinctUntilChanged
1112import kotlinx.coroutines.flow.first
1213import kotlinx.coroutines.flow.map
1314import org.lightningdevkit.ldknode.Network
@@ -32,7 +33,9 @@ class Keychain @Inject constructor(
3233 private val keyStore by lazy { AndroidKeyStore (alias) }
3334
3435 private val Context .keychain by preferencesDataStore(alias, scope = this )
35- val snapshot get() = runBlocking(this .coroutineContext) { context.keychain.data.first() }
36+ private val keychain = context.keychain
37+
38+ val snapshot get() = runBlocking(this .coroutineContext) { keychain.data.first() }
3639
3740 fun loadString (key : String ): String? = load(key)?.decodeToString()
3841
@@ -41,7 +44,7 @@ class Keychain @Inject constructor(
4144 return snapshot[key.indexed]?.fromBase64()?.let {
4245 keyStore.decrypt(it)
4346 }
44- } catch (e : Exception ) {
47+ } catch (_ : Exception ) {
4548 throw KeychainError .FailedToLoad (key)
4649 }
4750 }
@@ -53,17 +56,28 @@ class Keychain @Inject constructor(
5356
5457 try {
5558 val encryptedValue = keyStore.encrypt(value)
56- context. keychain.edit { it[key.indexed] = encryptedValue.toBase64() }
57- } catch (e : Exception ) {
59+ keychain.edit { it[key.indexed] = encryptedValue.toBase64() }
60+ } catch (_ : Exception ) {
5861 throw KeychainError .FailedToSave (key)
5962 }
6063 Logger .info(" Saved to keychain: $key " )
6164 }
6265
66+ /* * Inserts or replaces a string value associated with a given key in the keychain. */
67+ suspend fun upsertString (key : String , value : String ) {
68+ try {
69+ val encryptedValue = keyStore.encrypt(value.toByteArray())
70+ keychain.edit { it[key.indexed] = encryptedValue.toBase64() }
71+ } catch (_: Exception ) {
72+ throw KeychainError .FailedToSave (key)
73+ }
74+ Logger .info(" Saved/updated in keychain: $key " )
75+ }
76+
6377 suspend fun delete (key : String ) {
6478 try {
65- context. keychain.edit { it.remove(key.indexed) }
66- } catch (e : Exception ) {
79+ keychain.edit { it.remove(key.indexed) }
80+ } catch (_ : Exception ) {
6781 throw KeychainError .FailedToDelete (key)
6882 }
6983 Logger .debug(" Deleted from keychain: $key " )
@@ -73,13 +87,11 @@ class Keychain @Inject constructor(
7387 return snapshot.contains(key.indexed)
7488 }
7589
76- fun observeExists (key : Key ): Flow <Boolean > = context.keychain.data.map { it.contains(key.name.indexed) }
77-
7890 suspend fun wipe () {
7991 if (Env .network != Network .REGTEST ) throw KeychainError .KeychainWipeNotAllowed ()
8092
8193 val keys = snapshot.asMap().keys
82- context. keychain.edit { it.clear() }
94+ keychain.edit { it.clear() }
8395
8496 Logger .info(" Deleted all keychain entries: ${keys.joinToString()} " )
8597 }
@@ -90,10 +102,24 @@ class Keychain @Inject constructor(
90102 return " ${this } _$walletIndex " .let (::stringPreferencesKey)
91103 }
92104
105+ fun pinAttemptsRemaining (): Flow <Int ?> {
106+ return keychain.data
107+ .map { it[Key .PIN_ATTEMPTS_REMAINING .name.indexed] }
108+ .distinctUntilChanged()
109+ .map { encrypted ->
110+ encrypted?.fromBase64()?.let { bytes ->
111+ keyStore.decrypt(bytes).decodeToString()
112+ }
113+ }
114+ .map { string -> string?.toIntOrNull() }
115+ }
116+
93117 enum class Key {
94118 PUSH_NOTIFICATION_TOKEN ,
95119 PUSH_NOTIFICATION_PRIVATE_KEY ,
96120 BIP39_MNEMONIC ,
97- BIP39_PASSPHRASE ;
121+ BIP39_PASSPHRASE ,
122+ PIN ,
123+ PIN_ATTEMPTS_REMAINING ,
98124 }
99125}
0 commit comments