mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 01:07:46 +00:00
feat(napplet): live subscription tail, multi-filters, resource.cancel
Implements the remaining tractable conformance follow-ups: - Live subscription tail: relay.subscribe now opens a real client.subscribe whose SubscriptionListener streams relay.event (stored + live), relay.eose, and relay.closed pushes keyed by subId, instead of a one-shot snapshot. relay.close unsubscribes (tracked in liveSubs, torn down in onDestroy). The broker only authorizes the subscription (RELAY consent) and returns Subscribed; the host owns the live stream. Shim dispatches relay.closed too. - Multi-filters: relay.query/subscribe honor every filter in filters[], not just the first (decodeFilterList; gateway query(List<Filter>); queryEvents unions across filters; max limit applied). - resource.cancel: accepted at the host edge as a no-op Done. Conformance tests extended (multi-filter decode, relay.closed push); commons:jvmTest and the amethyst napplet suite pass. Still open: identity getList/getZaps/getBadges + onChanged (shapes underspecified), the keys.action push (needs a host trigger UI), the resource nostr: scheme, inc + the niche domains, and on-device verification. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
This commit is contained in:
+5
-4
@@ -168,13 +168,14 @@ class NappletBroker(
|
||||
|
||||
is NappletRequest.QueryEvents -> {
|
||||
val gateway = relay ?: return NappletResponse.Unsupported("relay.query")
|
||||
NappletResponse.Events(gateway.query(request.filter))
|
||||
NappletResponse.Events(gateway.query(request.filters))
|
||||
}
|
||||
|
||||
// Live tailing is a follow-up; for now subscribe returns the initial matches.
|
||||
// The broker only authorizes the subscription (consent + declaration); the host opens the
|
||||
// live relay subscription and streams relay.event/relay.eose/relay.closed by subId.
|
||||
is NappletRequest.Subscribe -> {
|
||||
val gateway = relay ?: return NappletResponse.Unsupported("relay.subscribe")
|
||||
NappletResponse.Events(gateway.query(request.filter))
|
||||
relay ?: return NappletResponse.Unsupported("relay.subscribe")
|
||||
NappletResponse.Subscribed
|
||||
}
|
||||
|
||||
is NappletRequest.StorageGet -> {
|
||||
|
||||
+2
-2
@@ -50,8 +50,8 @@ interface NappletRelayGateway {
|
||||
/** Publishes [event] and returns the relay URLs that accepted it (empty = nowhere reached). */
|
||||
suspend fun publish(event: Event): List<String>
|
||||
|
||||
/** Returns events matching [filter] (e.g. from the local cache and/or a bounded relay fetch). */
|
||||
suspend fun query(filter: Filter): List<Event>
|
||||
/** Returns events matching any of [filters] (e.g. from the local cache and/or a bounded relay fetch). */
|
||||
suspend fun query(filters: List<Filter>): List<Event>
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-5
@@ -140,19 +140,19 @@ sealed interface NappletRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/** Read events matching [filter] (from the cache and/or a bounded relay fetch). */
|
||||
/** Read events matching [filters] (from the cache and/or a bounded relay fetch). */
|
||||
data class QueryEvents(
|
||||
val filter: Filter,
|
||||
val filters: List<Filter>,
|
||||
) : NappletRequest {
|
||||
override val capability get() = NappletCapability.RELAY
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to events matching [filter]. The shell currently answers with the initial matches
|
||||
* (like a query); a live tail over the existing reply channel is a follow-up.
|
||||
* Subscribe to events matching [filters]. The shell opens a live relay subscription and pushes
|
||||
* `relay.event`/`relay.eose`/`relay.closed` keyed by the applet's `subId`.
|
||||
*/
|
||||
data class Subscribe(
|
||||
val filter: Filter,
|
||||
val filters: List<Filter>,
|
||||
) : NappletRequest {
|
||||
override val capability get() = NappletCapability.RELAY
|
||||
}
|
||||
|
||||
+3
@@ -60,6 +60,9 @@ sealed interface NappletResponse {
|
||||
val actionId: String,
|
||||
) : NappletResponse
|
||||
|
||||
/** `relay.subscribe` was authorized; the host now streams `relay.event`/`relay.eose` pushes. */
|
||||
data object Subscribed : NappletResponse
|
||||
|
||||
/** Result of a storage read; [value] is null when the key is absent. */
|
||||
data class StorageValue(
|
||||
val value: String?,
|
||||
|
||||
+2
-2
@@ -77,7 +77,7 @@ class NappletBrokerTest {
|
||||
return listOf("wss://relay.example")
|
||||
}
|
||||
|
||||
override suspend fun query(filter: Filter): List<Event> = queryResult
|
||||
override suspend fun query(filters: List<Filter>): List<Event> = queryResult
|
||||
}
|
||||
|
||||
private class RecordingWallet(
|
||||
@@ -276,7 +276,7 @@ class NappletBrokerTest {
|
||||
|
||||
val response =
|
||||
broker(ScriptedPrompt(GrantState.ALLOW_ONCE), relay = relay)
|
||||
.handle(applet, NappletRequest.QueryEvents(Filter(kinds = listOf(1))), allDeclared)
|
||||
.handle(applet, NappletRequest.QueryEvents(listOf(Filter(kinds = listOf(1)))), allDeclared)
|
||||
|
||||
assertEquals(NappletResponse.Events(listOf(cached)), response)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user