From 93e8d76c5c07cd3a140a49e540353823fcfc318e Mon Sep 17 00:00:00 2001 From: Julia Samol Date: Thu, 6 Oct 2022 11:07:17 +0200 Subject: [PATCH 1/6] Fix/micheline --- .../data/operation/MichelineMichelsonV1Expression.kt | 6 +++--- buildSrc/src/main/java/GradleConfig.kt | 4 ++-- .../airgap/beaconsdk/core/internal/data/HexString.kt | 2 +- .../it/airgap/beaconsdk/core/internal/utils/Bytes.kt | 2 +- .../beaconsdk/core/internal/data/HexStringTest.kt | 6 +++--- .../beaconsdk/core/internal/utils/BytesTest.kt | 4 ++-- .../it/airgap/beaconsdkdemo/dapp/DAppFragment.kt | 1 - .../beaconsdkdemo/dapp/DAppFragmentViewModel.kt | 12 ++++++++---- .../beaconsdkdemo/wallet/WalletFragmentViewModel.kt | 9 +++++++-- 9 files changed, 27 insertions(+), 19 deletions(-) diff --git a/blockchain-tezos/src/main/java/it/airgap/beaconsdk/blockchain/tezos/data/operation/MichelineMichelsonV1Expression.kt b/blockchain-tezos/src/main/java/it/airgap/beaconsdk/blockchain/tezos/data/operation/MichelineMichelsonV1Expression.kt index 3115467a..e61295a8 100644 --- a/blockchain-tezos/src/main/java/it/airgap/beaconsdk/blockchain/tezos/data/operation/MichelineMichelsonV1Expression.kt +++ b/blockchain-tezos/src/main/java/it/airgap/beaconsdk/blockchain/tezos/data/operation/MichelineMichelsonV1Expression.kt @@ -104,15 +104,15 @@ public data class MichelinePrimitiveBytes(public val bytes: String) : MichelineM override fun deserialize(decoder: Decoder): MichelinePrimitiveBytes = decoder.decodeStructure(descriptor) { - val primitive = decodeStringElement(descriptor, 0) + val primitive = HexString(decodeStringElement(descriptor, 0)) - return MichelinePrimitiveBytes(primitive) + return MichelinePrimitiveBytes(primitive.asString(withPrefix = false)) } override fun serialize(encoder: Encoder, value: MichelinePrimitiveBytes) { encoder.encodeStructure(descriptor) { with(value) { - encodeStringElement(descriptor, 0, HexString(bytes).asString(withPrefix = true)) + encodeStringElement(descriptor, 0, HexString(bytes).asString(withPrefix = false)) } } } diff --git a/buildSrc/src/main/java/GradleConfig.kt b/buildSrc/src/main/java/GradleConfig.kt index b5232896..4d8976a7 100644 --- a/buildSrc/src/main/java/GradleConfig.kt +++ b/buildSrc/src/main/java/GradleConfig.kt @@ -3,8 +3,8 @@ object Android { const val minSdk = 21 const val targetSdk = 32 - const val versionCode = 25 - const val versionName = "3.2.1" + const val versionCode = 27 + const val versionName = "3.2.2" } object Version { diff --git a/core/src/main/java/it/airgap/beaconsdk/core/internal/data/HexString.kt b/core/src/main/java/it/airgap/beaconsdk/core/internal/data/HexString.kt index eefdd38a..73645093 100644 --- a/core/src/main/java/it/airgap/beaconsdk/core/internal/data/HexString.kt +++ b/core/src/main/java/it/airgap/beaconsdk/core/internal/data/HexString.kt @@ -25,7 +25,7 @@ public value class HexString(private val value: String) { public fun toByteArray(): ByteArray = asString().chunked(2).map { it.toInt(16).toByte() }.toByteArray() public fun toInt(): Int = asString().toUInt(16).toInt() - public fun toBigInteger(): BigInteger = BigInteger(asString(), 16) + public fun toBigInteger(): BigInteger = BigInteger(asString().ifEmpty { "00" }, 16) public fun slice(indices: IntRange): HexString = HexString(value.slice(indices)) public fun slice(startIndex: Int): HexString = HexString(value.substring(startIndex)) diff --git a/core/src/main/java/it/airgap/beaconsdk/core/internal/utils/Bytes.kt b/core/src/main/java/it/airgap/beaconsdk/core/internal/utils/Bytes.kt index 82ab3bf8..da3c6001 100644 --- a/core/src/main/java/it/airgap/beaconsdk/core/internal/utils/Bytes.kt +++ b/core/src/main/java/it/airgap/beaconsdk/core/internal/utils/Bytes.kt @@ -4,7 +4,7 @@ import androidx.annotation.RestrictTo import it.airgap.beaconsdk.core.internal.data.HexString import java.math.BigInteger -private val hexRegex: Regex = Regex("^(${HexString.PREFIX})?([0-9a-fA-F]{2})+$") +private val hexRegex: Regex = Regex("^(${HexString.PREFIX})?([0-9a-fA-F]{2})*$") @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public fun String.isHex(): Boolean = this.matches(hexRegex) diff --git a/core/src/test/java/it/airgap/beaconsdk/core/internal/data/HexStringTest.kt b/core/src/test/java/it/airgap/beaconsdk/core/internal/data/HexStringTest.kt index 098a8e2e..a0423b40 100644 --- a/core/src/test/java/it/airgap/beaconsdk/core/internal/data/HexStringTest.kt +++ b/core/src/test/java/it/airgap/beaconsdk/core/internal/data/HexStringTest.kt @@ -9,18 +9,18 @@ import kotlin.test.assertFailsWith internal class HexStringTest { private val validHexStrings: List = listOf( + "", "9434dc98", "0x7b1ea2cb", "e40476d7", "c47320abdd31", "0x5786dac9eaf4", + "0x", ) private val invalidHexStrings: List = listOf( - "", "9434dc98az", "0xe40476d77t", - "0x", "0x1", ) @@ -122,7 +122,7 @@ internal class HexStringTest { @Test fun `creates BigInteger from HexString`() { val hexStringsWithExpected: List> = - validHexStrings.map { it to BigInteger(withoutHexPrefix(it), 16) } + validHexStrings.map { it to BigInteger(withoutHexPrefix(it).ifEmpty { "00" }, 16) } hexStringsWithExpected .map { it.first.asHexString().toBigInteger() to it.second } diff --git a/core/src/test/java/it/airgap/beaconsdk/core/internal/utils/BytesTest.kt b/core/src/test/java/it/airgap/beaconsdk/core/internal/utils/BytesTest.kt index c5bfcb49..6655b2a8 100644 --- a/core/src/test/java/it/airgap/beaconsdk/core/internal/utils/BytesTest.kt +++ b/core/src/test/java/it/airgap/beaconsdk/core/internal/utils/BytesTest.kt @@ -6,18 +6,18 @@ import java.math.BigInteger internal class BytesTest { private val validHexStrings: List = listOf( + "", "9434dc98", "0x7b1ea2cb", "e40476d7", "c47320abdd31", "0x5786dac9eaf4", + "0x", ) private val invalidHexStrings: List = listOf( - "", "9434dc98az", "0xe40476d77t", - "0x", "0x1", ) diff --git a/demo/src/main/java/it/airgap/beaconsdkdemo/dapp/DAppFragment.kt b/demo/src/main/java/it/airgap/beaconsdkdemo/dapp/DAppFragment.kt index c30e3b1f..2d68b554 100644 --- a/demo/src/main/java/it/airgap/beaconsdkdemo/dapp/DAppFragment.kt +++ b/demo/src/main/java/it/airgap/beaconsdkdemo/dapp/DAppFragment.kt @@ -12,7 +12,6 @@ import it.airgap.beaconsdk.core.message.BeaconResponse import it.airgap.beaconsdkdemo.R import it.airgap.beaconsdkdemo.utils.toJson import kotlinx.android.synthetic.main.fragment_dapp.* -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json diff --git a/demo/src/main/java/it/airgap/beaconsdkdemo/dapp/DAppFragmentViewModel.kt b/demo/src/main/java/it/airgap/beaconsdkdemo/dapp/DAppFragmentViewModel.kt index ac9295a2..4ba7df81 100644 --- a/demo/src/main/java/it/airgap/beaconsdkdemo/dapp/DAppFragmentViewModel.kt +++ b/demo/src/main/java/it/airgap/beaconsdkdemo/dapp/DAppFragmentViewModel.kt @@ -44,10 +44,14 @@ class DAppFragmentViewModel : ViewModel() { viewModelScope.launch { val beaconClient = beaconClient ?: return@launch - val pairingRequest = beaconClient.pair() - val serializerPairingRequest = beaconClient.serializePairingData(pairingRequest) - - _state.emit { copy(pairingRequest = serializerPairingRequest) } + try { + val pairingRequest = beaconClient.pair() + val serializerPairingRequest = beaconClient.serializePairingData(pairingRequest) + + _state.emit { copy(pairingRequest = serializerPairingRequest) } + } catch (e: Throwable) { + onError(e) + } } } diff --git a/demo/src/main/java/it/airgap/beaconsdkdemo/wallet/WalletFragmentViewModel.kt b/demo/src/main/java/it/airgap/beaconsdkdemo/wallet/WalletFragmentViewModel.kt index 9adb6d09..cbbe0255 100644 --- a/demo/src/main/java/it/airgap/beaconsdkdemo/wallet/WalletFragmentViewModel.kt +++ b/demo/src/main/java/it/airgap/beaconsdkdemo/wallet/WalletFragmentViewModel.kt @@ -76,8 +76,13 @@ class WalletFragmentViewModel : ViewModel() { /* Others */ else -> ErrorBeaconResponse.from(request, BeaconError.Unknown) } - beaconClient.respond(response) - removeAwaitingRequest() + + try { + beaconClient.respond(response) + removeAwaitingRequest() + } catch (e: Throwable) { + onError(e) + } } } From 7081a43a612f45b12b8c9344a3fa31e56b1cf14f Mon Sep 17 00:00:00 2001 From: Julia Samol Date: Thu, 6 Oct 2022 11:08:01 +0200 Subject: [PATCH 2/6] Chore/readme-community-projects --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b70a7b59..9c68b345 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ To add `Beacon Android SDK` into your project: #### Groovy ```groovy dependencies { - def beacon_version = "3.2.0" + def beacon_version = "x.y.z" // REQUIRED, core implementation "com.github.airgap-it.beacon-android-sdk:core:$beacon_version" @@ -76,7 +76,7 @@ To add `Beacon Android SDK` into your project: #### Kotlin ```kotlin dependencies { - val beaconVersion = "3.2.0" + val beaconVersion = "x.y.z" // REQUIRED, core implementation("com.github.airgap-it.beacon-android-sdk:core:$beaconVersion") @@ -368,6 +368,12 @@ $ ./gradlew testMock{Release|Debug}UnitTest --- ## Related Projects -[Beacon SDK](https://github.com/airgap-it/beacon-sdk) - an SDK for web developers (dApp & wallet) +### AirGap Projects -[Beacon iOS SDK](https://github.com/airgap-it/beacon-ios-sdk) - an SDK for iOS developers (wallet) +[Beacon SDK](https://github.com/airgap-it/beacon-sdk) - an SDK for web developers + +[Beacon iOS SDK](https://github.com/airgap-it/beacon-ios-sdk) - an SDK for iOS developers + +### Community Projects + +[Beacon Flutter SDK](https://github.com/TalaoDAO/beacon) - an SDK for Flutter developers From ec5fb48ee5bcf5762cd558b3f71a1628a880ff78 Mon Sep 17 00:00:00 2001 From: Julia Samol Date: Mon, 7 Nov 2022 16:38:29 +0100 Subject: [PATCH 3/6] Fix/agp --- build.gradle | 4 ++-- buildSrc/src/main/java/GradleConfig.kt | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index c1c15c45..3192d8a2 100644 --- a/build.gradle +++ b/build.gradle @@ -5,10 +5,10 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.android.tools.build:gradle:7.3.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Version.kotlin}" classpath "org.jetbrains.kotlin:kotlin-serialization:${Version.kotlin}" - classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.10" + classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.21" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/buildSrc/src/main/java/GradleConfig.kt b/buildSrc/src/main/java/GradleConfig.kt index 4d8976a7..374e400f 100644 --- a/buildSrc/src/main/java/GradleConfig.kt +++ b/buildSrc/src/main/java/GradleConfig.kt @@ -1,14 +1,14 @@ object Android { - const val compileSdk = 32 + const val compileSdk = 33 const val minSdk = 21 - const val targetSdk = 32 + const val targetSdk = 33 - const val versionCode = 27 - const val versionName = "3.2.2" + const val versionCode = 28 + const val versionName = "3.2.3-beta01" } object Version { - const val kotlin = "1.5.30" + const val kotlin = "1.7.20" const val kotlinSerialization = "1.3.1" From fba7b1c77b770da6b025fcabd0e6d8ee99748963 Mon Sep 17 00:00:00 2001 From: Julia Samol Date: Mon, 7 Nov 2022 16:53:48 +0100 Subject: [PATCH 4/6] Chore/github-issue-templates --- .github/ISSUE_TEMPLATE/BUG.md | 47 +++++++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/FEATURE.md | 30 ++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/BUG.md create mode 100644 .github/ISSUE_TEMPLATE/FEATURE.md diff --git a/.github/ISSUE_TEMPLATE/BUG.md b/.github/ISSUE_TEMPLATE/BUG.md new file mode 100644 index 00000000..0d737bc0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG.md @@ -0,0 +1,47 @@ +--- +name: Bug Report +about: Create a report to help us improve the SDK. +labels: bug +--- + + + +### Bug Report + +- [ ] I'm using the latest version of the SDK. +- [ ] I've seen [the docs](https://docs.walletbeacon.io) and [the demo code](https://github.com/airgap-it/beacon-android-sdk/tree/master/demo). +- [ ] I'm using the SDK directly (not via a 3rd party library or application). + + - [ ] I've been able to confirm that the issue comes from this SDK, not the 3rd party software on top. + +#### Current Behavior + + + +#### Expected Behavior + + + +#### How to Reproduce? + + + +#### Environment + +- Device: **PLACEHOLDER** +- OS version: **PLACEHOLDER** + +#### Additional Context + + \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/FEATURE.md b/.github/ISSUE_TEMPLATE/FEATURE.md new file mode 100644 index 00000000..e5c2450c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE.md @@ -0,0 +1,30 @@ +--- +name: Feature Request +about: Suggest an idea for this project. +labels: enhancement +--- + + + +### Feature Request + +- [ ] I'm using the latest version of the SDK. + +#### Summary + + + +#### Expected Behaviour + + + +#### Use Case + + + +#### Additional Context + + \ No newline at end of file From 0c52497ed3974bf7c998832f51c97fa75be7c681 Mon Sep 17 00:00:00 2001 From: Julia Samol Date: Wed, 9 Nov 2022 10:36:31 +0100 Subject: [PATCH 5/6] Fix/peer status --- buildSrc/src/main/java/GradleConfig.kt | 4 +- .../migration/v3_2_0/SerializersFromV3_2_0.kt | 2 +- .../storage/decorator/DecoratedStorage.kt | 189 +++++++++++------- .../internal/transport/p2p/P2pTransport.kt | 35 +++- .../internal/utils/CoroutineScopeRegistry.kt | 54 +++++ .../internal/storage/MockSecureStorage.kt | 10 +- .../core/internal/storage/MockStorage.kt | 34 +++- .../matrix/internal/matrix/MatrixClient.kt | 32 +-- .../transport/p2p/matrix/P2pMatrixTest.kt | 2 - .../internal/matrix/MatrixClientTest.kt | 10 +- 10 files changed, 245 insertions(+), 127 deletions(-) create mode 100644 core/src/main/java/it/airgap/beaconsdk/core/internal/utils/CoroutineScopeRegistry.kt diff --git a/buildSrc/src/main/java/GradleConfig.kt b/buildSrc/src/main/java/GradleConfig.kt index 374e400f..d2f119d9 100644 --- a/buildSrc/src/main/java/GradleConfig.kt +++ b/buildSrc/src/main/java/GradleConfig.kt @@ -3,8 +3,8 @@ object Android { const val minSdk = 21 const val targetSdk = 33 - const val versionCode = 28 - const val versionName = "3.2.3-beta01" + const val versionCode = 30 + const val versionName = "3.2.3-beta03" } object Version { diff --git a/core/src/main/java/it/airgap/beaconsdk/core/internal/migration/v3_2_0/SerializersFromV3_2_0.kt b/core/src/main/java/it/airgap/beaconsdk/core/internal/migration/v3_2_0/SerializersFromV3_2_0.kt index b3f0932f..68ec657b 100644 --- a/core/src/main/java/it/airgap/beaconsdk/core/internal/migration/v3_2_0/SerializersFromV3_2_0.kt +++ b/core/src/main/java/it/airgap/beaconsdk/core/internal/migration/v3_2_0/SerializersFromV3_2_0.kt @@ -46,7 +46,7 @@ private data class PeerSurrogate( val isPaired: Boolean = false, ) { fun toTarget(): Peer = when (type) { - Type.P2P -> P2pPeer(id, name, publicKey, relayServer, version, icon, appUrl) + Type.P2P -> P2pPeer(id, name, publicKey, relayServer, version, icon, appUrl, isPaired) } @Serializable diff --git a/core/src/main/java/it/airgap/beaconsdk/core/internal/storage/decorator/DecoratedStorage.kt b/core/src/main/java/it/airgap/beaconsdk/core/internal/storage/decorator/DecoratedStorage.kt index 6197e4b6..08f67e2a 100644 --- a/core/src/main/java/it/airgap/beaconsdk/core/internal/storage/decorator/DecoratedStorage.kt +++ b/core/src/main/java/it/airgap/beaconsdk/core/internal/storage/decorator/DecoratedStorage.kt @@ -15,51 +15,49 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlin.reflect.KClass -private typealias StorageSelectCollection = suspend Storage.() -> List -private typealias StorageInsertCollection = suspend Storage.(List) -> Unit - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class DecoratedStorage( private val storage: Storage, private val beaconConfiguration: BeaconConfiguration, ) : ExtendedStorage, Storage by storage { - private val _appMetadata: MutableSharedFlow by lazy { resourceFlow() } - override val appMetadata: Flow get() = _appMetadata.onSubscription { emitAll(getAppMetadata(beaconConfiguration).asFlow()) } + override val peers: Flow + get() = with(ResourceCollection.PeerList) { + resourceFlow.onSubscription { emitAll(getPeers(beaconConfiguration).asFlow()) } + } - private val _permissions: MutableSharedFlow by lazy { resourceFlow() } - override val permissions: Flow get() = _permissions.onSubscription { emitAll(getPermissions(beaconConfiguration).asFlow()) } + override val appMetadata: Flow + get() = with(ResourceCollection.AppMetadataList) { + resourceFlow.onSubscription { emitAll(getAppMetadata(beaconConfiguration).asFlow()) } + } - private val _peers: MutableSharedFlow by lazy { resourceFlow() } - override val peers: Flow get() = _peers.onSubscription { emitAll(getPeers(beaconConfiguration).asFlow()) } + override val permissions: Flow + get() = with(ResourceCollection.PermissionList) { + resourceFlow.onSubscription { emitAll(getPermissions(beaconConfiguration).asFlow()) } + } override suspend fun addPeers( peers: List, overwrite: Boolean, selector: Peer.() -> List?, ) { - add( - { getPeers(beaconConfiguration) }, - Storage::setPeers, - _peers, - peers, - overwrite, - selector, - ) + add(ResourceCollection.PeerList, peers, overwrite, selector) } override suspend fun findPeer(predicate: (Peer) -> Boolean): Peer? = - selectFirst({ getPeers(beaconConfiguration) }, predicate) + selectFirst(ResourceCollection.PeerList, predicate) override suspend fun findPeer( instanceClass: KClass, predicate: (T) -> Boolean, - ): T? = selectFirstInstance({ getPeers(beaconConfiguration) }, instanceClass, predicate) + ): T? = selectFirstInstance(ResourceCollection.PeerList, instanceClass, predicate) override suspend fun removePeers(predicate: ((Peer) -> Boolean)?) { - if (predicate != null) remove({ getPeers(beaconConfiguration) }, Storage::setPeers, predicate) - else removeAll(Storage::setPeers) + if (predicate != null) remove(ResourceCollection.PeerList, predicate) + else removeAll(ResourceCollection.PeerList) } override suspend fun addAppMetadata( @@ -67,27 +65,20 @@ public class DecoratedStorage( overwrite: Boolean, selector: AppMetadata.() -> List?, ) { - add( - { getAppMetadata(beaconConfiguration) }, - Storage::setAppMetadata, - _appMetadata, - appsMetadata, - overwrite, - selector, - ) + add(ResourceCollection.AppMetadataList, appsMetadata, overwrite, selector) } override suspend fun findAppMetadata(predicate: (AppMetadata) -> Boolean): AppMetadata? = - selectFirst({ getAppMetadata(beaconConfiguration) }, predicate) + selectFirst(ResourceCollection.AppMetadataList, predicate) override suspend fun findAppMetadata( instanceClass: KClass, predicate: (T) -> Boolean, - ): T? = selectFirstInstance({ getAppMetadata(beaconConfiguration) }, instanceClass, predicate) + ): T? = selectFirstInstance(ResourceCollection.AppMetadataList, instanceClass, predicate) override suspend fun removeAppMetadata(predicate: ((AppMetadata) -> Boolean)?) { - if (predicate != null) remove({ getAppMetadata(beaconConfiguration) }, Storage::setAppMetadata, predicate) - else removeAll(Storage::setAppMetadata) + if (predicate != null) remove(ResourceCollection.AppMetadataList, predicate) + else removeAll(ResourceCollection.AppMetadataList) } override suspend fun addPermissions( @@ -95,81 +86,72 @@ public class DecoratedStorage( overwrite: Boolean, selector: Permission.() -> List?, ) { - add( - { getPermissions(beaconConfiguration) }, - Storage::setPermissions, - _permissions, - permissions, - overwrite, - selector, - ) + add(ResourceCollection.PermissionList, permissions, overwrite, selector) } override suspend fun findPermission(predicate: (Permission) -> Boolean): Permission? = - selectFirst({ getPermissions(beaconConfiguration) }, predicate) + selectFirst(ResourceCollection.PermissionList, predicate) override suspend fun findPermission( instanceClass: KClass, predicate: (T) -> Boolean, - ): T? = selectFirstInstance({ getPermissions(beaconConfiguration) }, instanceClass, predicate) + ): T? = selectFirstInstance(ResourceCollection.PermissionList, instanceClass, predicate) override suspend fun removePermissions(predicate: ((Permission) -> Boolean)?) { - if (predicate != null) remove({ getPermissions(beaconConfiguration) }, Storage::setPermissions, predicate) - else removeAll(Storage::setPermissions) + if (predicate != null) remove(ResourceCollection.PermissionList, predicate) + else removeAll(ResourceCollection.PermissionList) } override suspend fun addMigrations(migrations: Set) { - val storageMigrations = getMigrations() - .toMutableSet() - .also { it.addAll(migrations) } + ResourceCollection.MigrationSet.runAtomic { + val storageMigrations = getMigrations() + .toMutableSet() + .also { it.addAll(migrations) } - setMigrations(storageMigrations) + setMigrations(storageMigrations) + } } override fun scoped(beaconScope: BeaconScope): ExtendedStorage = DecoratedStorage(storage.scoped(beaconScope), beaconConfiguration) override fun extend(beaconConfiguration: BeaconConfiguration): ExtendedStorage = this - private suspend fun selectFirst( - select: StorageSelectCollection, + private suspend fun > selectFirst( + resourceCollection: ResourceCollection, predicate: (T) -> Boolean, - ): T? = select(this).find(predicate) + ): T? = resourceCollection.runAtomic { select(beaconConfiguration).find(predicate) } - private suspend fun selectFirstInstance( - select: StorageSelectCollection, + private suspend fun , Instance : T> selectFirstInstance( + resourceCollection: ResourceCollection, instanceClass: KClass, predicate: (Instance) -> Boolean, - ): Instance? = select(this).filterIsInstance(instanceClass.java).find(predicate) + ): Instance? = resourceCollection.runAtomic { select(beaconConfiguration).filterIsInstance(instanceClass.java).find(predicate) } private suspend fun add( - select: StorageSelectCollection, - insert: StorageInsertCollection, - subscribeFlow: MutableSharedFlow, + resourceCollection: ResourceCollection>, elements: List, overwrite: Boolean, selector: T.() -> List?, ) { - val stored = select(this).distinctByKeepLast(selector) + resourceCollection.runAtomic { + val stored = select(beaconConfiguration).distinctByKeepLast(selector) - val mappedIndices = createMappedIndices(stored, elements, selector) - val (toInsert, updatedIndices) = stored.updatedWith(elements, mappedIndices, overwrite) + val mappedIndices = createMappedIndices(stored, elements, selector) + val (toInsert, updatedIndices) = stored.updatedWith(elements, mappedIndices, overwrite) - insert(this, toInsert) + insert(toInsert, beaconConfiguration) - CoroutineScope(Dispatchers.Default).launch { - toInsert.filterIndexed { index, _ -> updatedIndices.contains(index) }.forEach { subscribeFlow.tryEmit(it) } + CoroutineScope(Dispatchers.Default).launch { + toInsert.filterIndexed { index, _ -> updatedIndices.contains(index) }.forEach { resourceFlow.emit(it) } + } } } - private suspend fun remove( - select: StorageSelectCollection, - insert: StorageInsertCollection, - predicate: (T) -> Boolean, - ) { - insert(this, select(this).filterNot(predicate)) + private suspend fun remove(resourceCollection: ResourceCollection>, predicate: (T) -> Boolean) { + resourceCollection.runAtomic { insert(select(beaconConfiguration).filterNot(predicate), beaconConfiguration) } } - private suspend fun removeAll(insert: StorageInsertCollection) { - insert(this, emptyList()) + private suspend fun removeAll(resourceCollection: ResourceCollection>) { + resourceCollection.runAtomic { insert(emptyList(), beaconConfiguration) } } private fun createMappedIndices(first: List, second: List, selector: T.() -> List?): Map { @@ -181,9 +163,6 @@ public class DecoratedStorage( return indices.values.filter { it.size == 2 }.associate { it[0] to it[1] } } - private fun resourceFlow(bufferCapacity: Int = 64): MutableSharedFlow = - MutableSharedFlow(extraBufferCapacity = bufferCapacity) - private inline fun List.distinctByKeepLast(selector: T.() -> List?): List { val (withSelector, withoutSelector) = map { selector(it)?.sumHashCodes() to it } .partition { it.first != null } @@ -221,4 +200,62 @@ public class DecoratedStorage( private fun List.sumHashCodes(): Int = fold(0) { acc, next -> acc + next.hashCode() } private fun Pair.mapFirst(transform: (A) -> R): Pair = Pair(transform(first), second) -} \ No newline at end of file + + private sealed class ResourceCollection> { + abstract val Storage.resourceFlow: MutableSharedFlow + + abstract suspend fun Storage.select(beaconConfiguration: BeaconConfiguration): C + abstract suspend fun Storage.insert(collection: C, beaconConfiguration: BeaconConfiguration) + + private val mutex: Mutex = Mutex() + suspend inline fun runAtomic(action: ResourceCollection.() -> R): R = + mutex.withLock { action(this) } + + protected fun resourceFlow(bufferCapacity: Int = 64): MutableSharedFlow = + MutableSharedFlow(extraBufferCapacity = bufferCapacity) + + object PeerList : ResourceCollection>() { + override val Storage.resourceFlow: MutableSharedFlow by lazy { resourceFlow() } + + override suspend fun Storage.select(beaconConfiguration: BeaconConfiguration): List = + getPeers(beaconConfiguration) + + override suspend fun Storage.insert(collection: List, beaconConfiguration: BeaconConfiguration) { + setPeers(collection) + } + } + + object AppMetadataList : ResourceCollection>() { + override val Storage.resourceFlow: MutableSharedFlow by lazy { resourceFlow() } + + override suspend fun Storage.select(beaconConfiguration: BeaconConfiguration): List = + getAppMetadata(beaconConfiguration) + + override suspend fun Storage.insert(collection: List, beaconConfiguration: BeaconConfiguration) { + setAppMetadata(collection) + } + } + + object PermissionList : ResourceCollection>() { + override val Storage.resourceFlow: MutableSharedFlow by lazy { resourceFlow() } + + override suspend fun Storage.select(beaconConfiguration: BeaconConfiguration): List = + getPermissions(beaconConfiguration) + + override suspend fun Storage.insert(collection: List, beaconConfiguration: BeaconConfiguration) { + setPermissions(collection) + } + } + + object MigrationSet : ResourceCollection>() { + override val Storage.resourceFlow: MutableSharedFlow by lazy { resourceFlow() } + + override suspend fun Storage.select(beaconConfiguration: BeaconConfiguration): Set = + getMigrations() + + override suspend fun Storage.insert(collection: Set, beaconConfiguration: BeaconConfiguration) { + setMigrations(collection) + } + } + } +} diff --git a/core/src/main/java/it/airgap/beaconsdk/core/internal/transport/p2p/P2pTransport.kt b/core/src/main/java/it/airgap/beaconsdk/core/internal/transport/p2p/P2pTransport.kt index 86428db4..3cd40a3b 100644 --- a/core/src/main/java/it/airgap/beaconsdk/core/internal/transport/p2p/P2pTransport.kt +++ b/core/src/main/java/it/airgap/beaconsdk/core/internal/transport/p2p/P2pTransport.kt @@ -11,13 +11,14 @@ import it.airgap.beaconsdk.core.internal.transport.p2p.store.DiscardPairingData import it.airgap.beaconsdk.core.internal.transport.p2p.store.OnPairingCompleted import it.airgap.beaconsdk.core.internal.transport.p2p.store.OnPairingRequested import it.airgap.beaconsdk.core.internal.transport.p2p.store.P2pTransportStore +import it.airgap.beaconsdk.core.internal.utils.CoroutineScopeRegistry import it.airgap.beaconsdk.core.internal.utils.flatMap import it.airgap.beaconsdk.core.internal.utils.runCatchingFlat import it.airgap.beaconsdk.core.internal.utils.success import it.airgap.beaconsdk.core.storage.findPeer import it.airgap.beaconsdk.core.transport.data.* import it.airgap.beaconsdk.core.transport.p2p.P2pClient -import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @OptIn(FlowPreview::class) @@ -31,13 +32,15 @@ internal class P2pTransport( override val incomingConnectionMessages: Flow> by lazy { storageManager.updatedPeers .filterIsInstance() - .onEach { onUpdatedP2pPeer(it) } + .onEach { it.onUpdated() } .filterNot { it.isRemoved || client.isSubscribed(it) } .mapNotNull { client.subscribeTo(it) } .flattenMerge() .map { IncomingConnectionMessage.fromResult(it) } } + private val peerScopes: CoroutineScopeRegistry = CoroutineScopeRegistry("peers") + override suspend fun pair(): Flow> = flow { val pairingRequest = client.createPairingRequest().also { emit(it) @@ -87,19 +90,33 @@ internal class P2pTransport( } } - private suspend fun onUpdatedP2pPeer(peer: P2pPeer) { - if (!peer.isPaired && !peer.isRemoved) pairP2pPeer(peer) - if (peer.isRemoved) client.unsubscribeFrom(peer) + private suspend fun P2pPeer.onUpdated() { + when { + isRemoved -> unpair() + !isPaired -> pair() + } } - private suspend fun pairP2pPeer(peer: P2pPeer) { - val result = client.sendPairingResponse(peer) + private suspend fun P2pPeer.pair() { + peerScopes.get(scopeId).launch { + val result = client.sendPairingResponse(this@pair) + + if (result.isSuccess) { + storageManager.updatePeers(listOf(selfPaired())) { listOfNotNull(id, name, publicKey, relayServer, icon, appUrl) } + } - if (result.isSuccess) { - storageManager.updatePeers(listOf(peer.selfPaired())) { listOfNotNull(id, name, publicKey, relayServer, icon, appUrl) } + peerScopes.cancel(scopeId) } } + private suspend fun P2pPeer.unpair() { + peerScopes.cancel(scopeId) + client.unsubscribeFrom(this) + } + + private val P2pPeer.scopeId: String + get() = id ?: publicKey + private fun failWithUnknownPeer(publicKey: String?): Nothing = throw IllegalStateException("P2P peer with public key $publicKey is not recognized.") diff --git a/core/src/main/java/it/airgap/beaconsdk/core/internal/utils/CoroutineScopeRegistry.kt b/core/src/main/java/it/airgap/beaconsdk/core/internal/utils/CoroutineScopeRegistry.kt new file mode 100644 index 00000000..29bbda07 --- /dev/null +++ b/core/src/main/java/it/airgap/beaconsdk/core/internal/utils/CoroutineScopeRegistry.kt @@ -0,0 +1,54 @@ +package it.airgap.beaconsdk.core.internal.utils + +import androidx.annotation.RestrictTo +import kotlinx.coroutines.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) +public class CoroutineScopeRegistry( + private val name: String, + private val context: CoroutineContext = Dispatchers.Default, +) { + private val mutex: Mutex = Mutex() + private val scopes: MutableMap = mutableMapOf() + + public suspend fun get(id: String): CoroutineScope = mutex.withLock { + scopes.getIfActiveOrPut(id) { DisposableCoroutineScope(id) } + } + + public suspend fun cancel(id: String) { + mutex.withLock { + scopes[id]?.cancel(message = "Scope ${scopeName(id)} canceled.") + scopes.remove(id) + } + } + + public suspend fun cancelAll() { + mutex.withLock { + scopes.forEach { it.value.cancel(message = "Scope ${scopeName(it.key)} canceled.") } + scopes.clear() + } + } + + public suspend fun isEmpty(): Boolean = mutex.withLock { scopes.isEmpty() } + + private fun scopeName(id: String): String = "$name@$id" + + @Suppress("FunctionName") + private fun DisposableCoroutineScope(id: String): CoroutineScope = + CoroutineScope(CoroutineName(scopeName(id)) + context).also { it.removeOnCompletion(id) } + + private fun CoroutineScope.removeOnCompletion(id: String) { + coroutineContext.job.invokeOnCompletion { + CoroutineScope(Dispatchers.Default).launch { + mutex.withLock { scopes.remove(id) } + } + } + } + + private fun MutableMap.getIfActiveOrPut(key: String, defaultValue: () -> CoroutineScope): CoroutineScope = + get(key)?.takeIf { it.isActive } ?: defaultValue().also { put(key, it) } +} \ No newline at end of file diff --git a/core/src/mock/java/it/airgap/beaconsdk/core/internal/storage/MockSecureStorage.kt b/core/src/mock/java/it/airgap/beaconsdk/core/internal/storage/MockSecureStorage.kt index 13befbdc..a08aaf0b 100644 --- a/core/src/mock/java/it/airgap/beaconsdk/core/internal/storage/MockSecureStorage.kt +++ b/core/src/mock/java/it/airgap/beaconsdk/core/internal/storage/MockSecureStorage.kt @@ -2,13 +2,19 @@ package it.airgap.beaconsdk.core.internal.storage import it.airgap.beaconsdk.core.scope.BeaconScope import it.airgap.beaconsdk.core.storage.SecureStorage +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock public class MockSecureStorage : SecureStorage { + private val mutex: Mutex = Mutex() + private var sdkSecretSeed: String? = null - override suspend fun getSdkSecretSeed(): String? = sdkSecretSeed + override suspend fun getSdkSecretSeed(): String? = mutex.withLock { sdkSecretSeed } override suspend fun setSdkSecretSeed(sdkSecretSeed: String) { - this.sdkSecretSeed = sdkSecretSeed + mutex.withLock { + this.sdkSecretSeed = sdkSecretSeed + } } override fun scoped(beaconScope: BeaconScope): SecureStorage = this diff --git a/core/src/mock/java/it/airgap/beaconsdk/core/internal/storage/MockStorage.kt b/core/src/mock/java/it/airgap/beaconsdk/core/internal/storage/MockStorage.kt index 6bf6126f..34ae5789 100644 --- a/core/src/mock/java/it/airgap/beaconsdk/core/internal/storage/MockStorage.kt +++ b/core/src/mock/java/it/airgap/beaconsdk/core/internal/storage/MockStorage.kt @@ -6,37 +6,51 @@ import it.airgap.beaconsdk.core.data.Peer import it.airgap.beaconsdk.core.data.Permission import it.airgap.beaconsdk.core.scope.BeaconScope import it.airgap.beaconsdk.core.storage.Storage +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock public class MockStorage : Storage { + private val mutex: Mutex = Mutex() + private var p2pPeers: List = emptyList() private var appsMetadata: List = emptyList() private var permissions: List = emptyList() private var sdkVersion: String? = null private var migrations: Set = emptySet() - override suspend fun getMaybePeers(): List> = p2pPeers.map { Maybe.Some(it) } + override suspend fun getMaybePeers(): List> = mutex.withLock { p2pPeers.map { Maybe.Some(it) } } override suspend fun setPeers(p2pPeers: List) { - this.p2pPeers = p2pPeers + mutex.withLock { + this.p2pPeers = p2pPeers + } } - override suspend fun getMaybeAppMetadata(): List> = appsMetadata.map { Maybe.Some(it) } + override suspend fun getMaybeAppMetadata(): List> = mutex.withLock { appsMetadata.map { Maybe.Some(it) } } override suspend fun setAppMetadata(appMetadata: List) { - this.appsMetadata = appMetadata + mutex.withLock { + this.appsMetadata = appMetadata + } } - override suspend fun getMaybePermissions(): List> = permissions.map { Maybe.Some(it) } + override suspend fun getMaybePermissions(): List> = mutex.withLock { permissions.map { Maybe.Some(it) } } override suspend fun setPermissions(permissions: List) { - this.permissions = permissions + mutex.withLock { + this.permissions = permissions + } } - override suspend fun getSdkVersion(): String? = sdkVersion + override suspend fun getSdkVersion(): String? = mutex.withLock { sdkVersion } override suspend fun setSdkVersion(sdkVersion: String) { - this.sdkVersion = sdkVersion + mutex.withLock { + this.sdkVersion = sdkVersion + } } - override suspend fun getMigrations(): Set = migrations + override suspend fun getMigrations(): Set = mutex.withLock { migrations } override suspend fun setMigrations(migrations: Set) { - this.migrations = migrations + mutex.withLock { + this.migrations = migrations + } } override fun scoped(beaconScope: BeaconScope): Storage = this diff --git a/transport-p2p-matrix/src/main/java/it/airgap/beaconsdk/transport/p2p/matrix/internal/matrix/MatrixClient.kt b/transport-p2p-matrix/src/main/java/it/airgap/beaconsdk/transport/p2p/matrix/internal/matrix/MatrixClient.kt index 7dee9e8e..739908c2 100644 --- a/transport-p2p-matrix/src/main/java/it/airgap/beaconsdk/transport/p2p/matrix/internal/matrix/MatrixClient.kt +++ b/transport-p2p-matrix/src/main/java/it/airgap/beaconsdk/transport/p2p/matrix/internal/matrix/MatrixClient.kt @@ -28,7 +28,7 @@ internal class MatrixClient( val events: Flow get() = store.events - private val syncScopes: MutableMap = mutableMapOf() + private val syncScopes: CoroutineScopeRegistry = CoroutineScopeRegistry("sync") suspend fun joinedRooms(): List = store.state().getOrNull()?.rooms?.values?.filterIsInstance() ?: emptyList() @@ -51,18 +51,12 @@ internal class MatrixClient( ?: failWith("Login failed", loginResponse.exceptionOrNull()) store.intent(Init(userId, deviceId, accessToken)) - syncScope(node) { syncPoll(it, node).collect() } + syncPoll(node) } suspend fun stop(node: String? = null) { with(syncScopes) { - if (node != null) { - get(node)?.cancel("Sync for node $node canceled.") - remove(node) - } else { - forEach { it.value.cancel("Sync for node ${it.key} canceled.") } - clear() - } + node?.let { cancel(it) } ?: cancelAll() if (isEmpty()) { store.intent(Reset) @@ -156,7 +150,7 @@ internal class MatrixClient( } } - fun syncPoll(scope: CoroutineScope, node: String, interval: Long = 0): Flow> { + fun syncPollFlow(scope: CoroutineScope, node: String, interval: Long = 0): Flow> { val syncMutex = Mutex() return poller.poll(Dispatchers.IO, interval) { @@ -171,6 +165,13 @@ internal class MatrixClient( } } + private suspend fun syncPoll(node: String) { + syncScopes.get(node).launch { + syncPollFlow(this, node).collect() + syncScopes.cancel(node) + } + } + private suspend inline fun withAccessToken( name: String, block: (accessToken: String) -> T, @@ -179,17 +180,6 @@ internal class MatrixClient( return block(accessToken) } - private suspend fun syncScope(node: String, block: suspend (CoroutineScope) -> Unit) { - syncScopes - .getOrPut(node) { CoroutineScope(CoroutineName(syncScopeName(node)) + Dispatchers.Default) } - .launch { - block(this) - syncScopes.remove(node) - } - } - - private fun syncScopeName(node: String): String = "sync@$node" - private suspend fun onSyncSuccess(sync: MatrixSync) { store.intent( OnSyncSuccess( diff --git a/transport-p2p-matrix/src/test/java/it/airgap/beaconsdk/transport/p2p/matrix/P2pMatrixTest.kt b/transport-p2p-matrix/src/test/java/it/airgap/beaconsdk/transport/p2p/matrix/P2pMatrixTest.kt index 421d3b96..4d046339 100644 --- a/transport-p2p-matrix/src/test/java/it/airgap/beaconsdk/transport/p2p/matrix/P2pMatrixTest.kt +++ b/transport-p2p-matrix/src/test/java/it/airgap/beaconsdk/transport/p2p/matrix/P2pMatrixTest.kt @@ -495,8 +495,6 @@ internal class P2pMatrixTest { p2pMatrix.unsubscribeFrom(peer) p2pMatrix.subscribeTo(peer) - println(unsubscribed.await()) - assertTrue(p2pMatrix.isSubscribed(peer), "Expected peer to be recognized as subscribed") assertTrue(unsubscribed.await()?.isEmpty() == true, diff --git a/transport-p2p-matrix/src/test/java/it/airgap/beaconsdk/transport/p2p/matrix/internal/matrix/MatrixClientTest.kt b/transport-p2p-matrix/src/test/java/it/airgap/beaconsdk/transport/p2p/matrix/internal/matrix/MatrixClientTest.kt index a7430ffe..c2410c5a 100644 --- a/transport-p2p-matrix/src/test/java/it/airgap/beaconsdk/transport/p2p/matrix/internal/matrix/MatrixClientTest.kt +++ b/transport-p2p-matrix/src/test/java/it/airgap/beaconsdk/transport/p2p/matrix/internal/matrix/MatrixClientTest.kt @@ -218,7 +218,7 @@ internal class MatrixClientTest { coVerify(exactly = 1) { store.intent(Init(userId, deviceId, accessToken)) } coVerify(exactly = 1) { store.intent(OnSyncSuccess(nextSyncToken, pollingTimeout = 30000, null, null)) } - verify(exactly = 1) { client.syncPoll(any(), node) } + verify(exactly = 1) { client.syncPollFlow(any(), node) } verify(exactly = 1) { poller.poll(any(), 0, any()) } confirmVerified(userService, poller) @@ -667,7 +667,7 @@ internal class MatrixClientTest { coEvery { store.state() } returns Result.success(MatrixStoreState(accessToken = accessToken)) runBlocking { - client.syncPoll(CoroutineScope(TestCoroutineDispatcher()), node).take(1).collect() + client.syncPollFlow(this, node).take(1).collect() coVerify { store.intent( @@ -684,6 +684,7 @@ internal class MatrixClientTest { @Test fun `updates retry counter on error and continues polling if max not exceeded`() { + val node = "node" val accessToken = "accessToken" coEvery { eventService.sync(any(), any(), any()) } returns Result.failure() @@ -691,7 +692,7 @@ internal class MatrixClientTest { runBlocking { val scope = CoroutineScope(TestCoroutineDispatcher()) - client.syncPoll(scope, "node").take(1).collect() + client.syncPollFlow(scope, node).take(1).collect() assertTrue(scope.isActive, "Expected scope to be active") coVerify { store.intent(OnSyncError) } @@ -700,6 +701,7 @@ internal class MatrixClientTest { @Test fun `cancels polling on max retries exceeded`() { + val node = "node" val accessToken = "accessToken" coEvery { eventService.sync(any(), any(), any()) } returns Result.failure() @@ -707,7 +709,7 @@ internal class MatrixClientTest { runBlocking { val scope = CoroutineScope(TestCoroutineDispatcher()) - client.syncPoll(scope, "node").take(1).collect() + client.syncPollFlow(scope, node).take(1).collect() assertFalse(scope.isActive, "Expected scope to be canceled") coVerify { store.intent(OnSyncError) } From ddf65515c32f79cf41631d8adc96f0a3b3337233 Mon Sep 17 00:00:00 2001 From: Julia Samol Date: Wed, 9 Nov 2022 10:48:12 +0100 Subject: [PATCH 6/6] Release/3.2.3 --- buildSrc/src/main/java/GradleConfig.kt | 4 ++-- demo/build.gradle | 3 ++- .../it/airgap/beaconsdkdemo/dapp/DAppFragmentViewModel.kt | 6 +++++- .../beaconsdkdemo/wallet/WalletFragmentViewModel.kt | 8 ++++++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/buildSrc/src/main/java/GradleConfig.kt b/buildSrc/src/main/java/GradleConfig.kt index d2f119d9..a4aec971 100644 --- a/buildSrc/src/main/java/GradleConfig.kt +++ b/buildSrc/src/main/java/GradleConfig.kt @@ -3,8 +3,8 @@ object Android { const val minSdk = 21 const val targetSdk = 33 - const val versionCode = 30 - const val versionName = "3.2.3-beta03" + const val versionCode = 31 + const val versionName = "3.2.3" } object Version { diff --git a/demo/build.gradle b/demo/build.gradle index 517ad717..bc20658c 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -68,12 +68,13 @@ dependencies { implementation project(path: ':blockchain-substrate') implementation project(path: ':transport-p2p-matrix') - // // Published + // Published // def beaconVersion = Android.versionName // def withoutJna = { exclude group: "net.java.dev.jna" } // // implementation "com.github.airgap-it.beacon-android-sdk:core:$beaconVersion", withoutJna // +// implementation "com.github.airgap-it.beacon-android-sdk:client-dapp:$beaconVersion", withoutJna // implementation "com.github.airgap-it.beacon-android-sdk:client-wallet:$beaconVersion", withoutJna // implementation "com.github.airgap-it.beacon-android-sdk:blockchain-tezos:$beaconVersion", withoutJna // implementation "com.github.airgap-it.beacon-android-sdk:blockchain-substrate:$beaconVersion", withoutJna diff --git a/demo/src/main/java/it/airgap/beaconsdkdemo/dapp/DAppFragmentViewModel.kt b/demo/src/main/java/it/airgap/beaconsdkdemo/dapp/DAppFragmentViewModel.kt index 4ba7df81..9f3e6848 100644 --- a/demo/src/main/java/it/airgap/beaconsdkdemo/dapp/DAppFragmentViewModel.kt +++ b/demo/src/main/java/it/airgap/beaconsdkdemo/dapp/DAppFragmentViewModel.kt @@ -57,7 +57,11 @@ class DAppFragmentViewModel : ViewModel() { fun requestPermission() { viewModelScope.launch { - beaconClient?.requestTezosPermission() + try { + beaconClient?.requestTezosPermission() + } catch (e: Exception) { + onError(e) + } } } diff --git a/demo/src/main/java/it/airgap/beaconsdkdemo/wallet/WalletFragmentViewModel.kt b/demo/src/main/java/it/airgap/beaconsdkdemo/wallet/WalletFragmentViewModel.kt index cbbe0255..7df3e3d9 100644 --- a/demo/src/main/java/it/airgap/beaconsdkdemo/wallet/WalletFragmentViewModel.kt +++ b/demo/src/main/java/it/airgap/beaconsdkdemo/wallet/WalletFragmentViewModel.kt @@ -88,8 +88,12 @@ class WalletFragmentViewModel : ViewModel() { fun pair(pairingRequest: String) { viewModelScope.launch { - beaconClient?.pair(pairingRequest) - checkForPeers() + try { + beaconClient?.pair(pairingRequest) + checkForPeers() + } catch (e: Throwable) { + onError(e) + } } }