mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-10 06:47:20 +01:00
Adds simplified methods to create Requests from NostrClient
This commit is contained in:
53
README.md
53
README.md
@@ -268,7 +268,7 @@ val readOnly = KeyPair(pubKey = "npub1...".bechToBytes())
|
||||
```
|
||||
|
||||
Create signers that can be Internal, when you have the private key or a read-only public key,
|
||||
or External, when it is controlled by Amber in NIP-55.
|
||||
or External, when it is controlled by Amber in NIP-55.
|
||||
|
||||
Use either the `NostrSignerInternal` or `NostrSignerExternal` class:
|
||||
|
||||
@@ -277,7 +277,7 @@ val signer = NostrSignerInternal(keyPair)
|
||||
val amberSigner = NostrSignerExternal(
|
||||
pubKey = keyPair.pubKey.toHexKey(),
|
||||
packageName = signerPackageName, // Amber package name
|
||||
contentResolver = appContext.contentResolver,
|
||||
contentResolver = appContext.contentResolver,
|
||||
)
|
||||
```
|
||||
|
||||
@@ -305,39 +305,54 @@ val authCoordinator = RelayAuthenticator(client, appScope) { challenge, relay ->
|
||||
}
|
||||
```
|
||||
|
||||
To manage subscriptions, the simplest approach is to build mutable subscriptions in
|
||||
the Application class. To use the best of the outbox model, this class allows you to
|
||||
build filters for as many relays as needed. The `NostrClient` will connect to the
|
||||
complete set of relays for all subscriptions.
|
||||
To make a request subscription simply do:
|
||||
|
||||
```kt
|
||||
val metadataSub = NostrClientSubscription(
|
||||
client = client,
|
||||
filter = {
|
||||
val metadataSub = client.req(
|
||||
relays = listOf("wss://nos.lol", "wss://nostr.mom"),
|
||||
filters = Filter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
authors = listOf(signer.pubkey)
|
||||
)
|
||||
) { event ->
|
||||
/* consume event */
|
||||
}
|
||||
```
|
||||
|
||||
The client will add the relay to the pool, connect to it and start receiving events. The
|
||||
`metadataSub` will be active until you call `metadataSub.close()`. If the client disconnects
|
||||
and reconnects, the sub will be active again.
|
||||
|
||||
To manage subscriptions that change over time, the simplest approach is to build mutable
|
||||
subscriptions in the Application class.
|
||||
|
||||
```kt
|
||||
val metadataSub = client.req(
|
||||
filters = {
|
||||
// Let's say you have a list of users that need to be rendered
|
||||
val users = pubkeysSeeingInTheScreen()
|
||||
// And a cache repository with their outbox relays
|
||||
val outboxRelays = outboxRelays(users)
|
||||
|
||||
val filters = listOf(
|
||||
Filter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
authors = listOf(signer.pubkey)
|
||||
authors = users
|
||||
)
|
||||
)
|
||||
|
||||
val signerOutboxRelays = listOfNotNull(
|
||||
RelayUrlNormalizer.normalizeOrNull("wss://relay1.com"),
|
||||
RelayUrlNormalizer.normalizeOrNull("wss://relay2.com")
|
||||
)
|
||||
|
||||
signerOutboxRelays.associateWith { filters }
|
||||
outboxRelays.associateWith { filters }
|
||||
}
|
||||
) { event ->
|
||||
/* consume event */
|
||||
}
|
||||
```
|
||||
|
||||
In that way, you can simply call `metadataSub.updateFilter()` when you need to update
|
||||
subscriptions to all relays. Or call `metadataSub.closeSubscription()` to stop the sub
|
||||
In that way, you can simply call `metadataSub.updateFilter()` when you need to update
|
||||
subscriptions to all relays. Or call `metadataSub.close()` to stop the sub
|
||||
without deleting it.
|
||||
|
||||
When your app goes to the background, you can use NostrClient's `connect` and `disconnect`
|
||||
When your app goes to the background, you can use NostrClient's `connect` and `disconnect`
|
||||
methods to stop all communication to relays. Add the `connect` to your `onResume` and `disconnect`
|
||||
to `onPause` methods.
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.relay.client.reqs
|
||||
|
||||
interface IOpenNostrRequest {
|
||||
fun updateFilter()
|
||||
|
||||
fun close()
|
||||
}
|
||||
@@ -18,19 +18,20 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.relay.client
|
||||
package com.vitorpamplona.quartz.nip01Core.relay.client.reqs
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.utils.RandomInstance
|
||||
|
||||
class NostrClientSubscription(
|
||||
class NostrClientDynamicFilterRequest(
|
||||
val client: INostrClient,
|
||||
val filter: () -> Map<NormalizedRelayUrl, List<Filter>> = { emptyMap() },
|
||||
val filter: () -> Map<NormalizedRelayUrl, List<Filter>>,
|
||||
val onEvent: (event: Event) -> Unit = {},
|
||||
) : IRequestListener {
|
||||
) : IRequestListener,
|
||||
IOpenNostrRequest {
|
||||
private val subId = RandomInstance.randomChars(10)
|
||||
|
||||
override fun onEvent(
|
||||
@@ -46,15 +47,16 @@ class NostrClientSubscription(
|
||||
* Creates or Updates the filter with relays. This method should be called
|
||||
* everytime the filter changes.
|
||||
*/
|
||||
fun updateFilter() = client.openReqSubscription(subId, filter(), this)
|
||||
override fun updateFilter() = client.openReqSubscription(subId, filter(), this)
|
||||
|
||||
fun closeSubscription() = client.close(subId)
|
||||
|
||||
fun destroy() {
|
||||
closeSubscription()
|
||||
}
|
||||
override fun close() = client.close(subId)
|
||||
|
||||
init {
|
||||
updateFilter()
|
||||
}
|
||||
}
|
||||
|
||||
fun INostrClient.req(
|
||||
filters: () -> Map<NormalizedRelayUrl, List<Filter>>,
|
||||
onEvent: (event: Event) -> Unit = {},
|
||||
): IOpenNostrRequest = NostrClientDynamicFilterRequest(this, filters, onEvent)
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.relay.client.reqs
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.utils.RandomInstance
|
||||
|
||||
class NostrClientStaticFilterRequest(
|
||||
val client: INostrClient,
|
||||
val filter: Map<NormalizedRelayUrl, List<Filter>>,
|
||||
val onEvent: (event: Event) -> Unit = {},
|
||||
) : IRequestListener,
|
||||
IOpenNostrRequest {
|
||||
private val subId = RandomInstance.randomChars(10)
|
||||
|
||||
override fun onEvent(
|
||||
event: Event,
|
||||
isLive: Boolean,
|
||||
relay: NormalizedRelayUrl,
|
||||
forFilters: List<Filter>?,
|
||||
) {
|
||||
onEvent(event)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or Updates the filter with relays. This method should be called
|
||||
* everytime the filter changes.
|
||||
*/
|
||||
override fun updateFilter() = client.openReqSubscription(subId, filter, this)
|
||||
|
||||
override fun close() = client.close(subId)
|
||||
|
||||
init {
|
||||
updateFilter()
|
||||
}
|
||||
}
|
||||
|
||||
fun INostrClient.req(
|
||||
relay: NormalizedRelayUrl,
|
||||
filters: List<Filter>,
|
||||
onEvent: (event: Event) -> Unit = {},
|
||||
): IOpenNostrRequest = NostrClientStaticFilterRequest(this, mapOf(relay to filters), onEvent)
|
||||
|
||||
fun INostrClient.req(
|
||||
relay: NormalizedRelayUrl,
|
||||
filter: Filter,
|
||||
onEvent: (event: Event) -> Unit = {},
|
||||
): IOpenNostrRequest = NostrClientStaticFilterRequest(this, mapOf(relay to listOf(filter)), onEvent)
|
||||
|
||||
fun INostrClient.req(
|
||||
relays: List<NormalizedRelayUrl>,
|
||||
filters: List<Filter>,
|
||||
onEvent: (event: Event) -> Unit = {},
|
||||
): IOpenNostrRequest = NostrClientStaticFilterRequest(this, relays.associateWith { filters }, onEvent)
|
||||
|
||||
fun INostrClient.req(
|
||||
relays: List<NormalizedRelayUrl>,
|
||||
filter: Filter,
|
||||
onEvent: (event: Event) -> Unit = {},
|
||||
): IOpenNostrRequest = NostrClientStaticFilterRequest(this, relays.associateWith { listOf(filter) }, onEvent)
|
||||
|
||||
// -----------------------------------
|
||||
// Helper methods with relay as string
|
||||
// -----------------------------------
|
||||
fun INostrClient.req(
|
||||
relay: String,
|
||||
filters: List<Filter>,
|
||||
onEvent: (event: Event) -> Unit = {},
|
||||
): IOpenNostrRequest = NostrClientStaticFilterRequest(this, mapOf(RelayUrlNormalizer.normalize(relay) to filters), onEvent)
|
||||
|
||||
fun INostrClient.req(
|
||||
relay: String,
|
||||
filter: Filter,
|
||||
onEvent: (event: Event) -> Unit = {},
|
||||
): IOpenNostrRequest = NostrClientStaticFilterRequest(this, mapOf(RelayUrlNormalizer.normalize(relay) to listOf(filter)), onEvent)
|
||||
@@ -23,9 +23,8 @@ package com.vitorpamplona.quartz.nip01Core.relay
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClientSubscription
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.req
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -48,19 +47,13 @@ class NostrClientSubscriptionTest : BaseNostrClientTest() {
|
||||
val events = mutableSetOf<Event>()
|
||||
|
||||
val sub =
|
||||
NostrClientSubscription(
|
||||
client = client,
|
||||
filter = {
|
||||
mapOf(
|
||||
RelayUrlNormalizer.normalize("wss://relay.damus.io") to
|
||||
listOf(
|
||||
Filter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
limit = 100,
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
client.req(
|
||||
relay = "wss://relay.damus.io",
|
||||
filter =
|
||||
Filter(
|
||||
kinds = listOf(MetadataEvent.KIND),
|
||||
limit = 100,
|
||||
),
|
||||
) { event ->
|
||||
assertEquals(MetadataEvent.KIND, event.kind)
|
||||
resultChannel.trySend(event)
|
||||
@@ -75,7 +68,7 @@ class NostrClientSubscriptionTest : BaseNostrClientTest() {
|
||||
|
||||
resultChannel.close()
|
||||
|
||||
sub.closeSubscription()
|
||||
sub.close()
|
||||
|
||||
client.disconnect()
|
||||
appScope.cancel()
|
||||
|
||||
Reference in New Issue
Block a user