Compare commits
No commits in common. "main" and "v1.2.0-aio.2" have entirely different histories.
main
...
v1.2.0-aio
4 changed files with 4 additions and 44 deletions
|
|
@ -50,7 +50,7 @@ flowchart LR
|
||||||
3. **Fan-Out** - Subscription requests are sent to all configured relays
|
3. **Fan-Out** - Subscription requests are sent to all configured relays
|
||||||
4. **Aggregation** - Events from all relays are collected and deduplicated
|
4. **Aggregation** - Events from all relays are collected and deduplicated
|
||||||
5. **Response** - Events are sent back to the client with the original subscription ID
|
5. **Response** - Events are sent back to the client with the original subscription ID
|
||||||
6. **Publish Acknowledgement** - Every `EVENT` a client publishes gets exactly one `["OK", <id>, <accepted>, <message>]` reply (NIP-01): `true` as soon as any relay accepts it, `false` once every relay has rejected it, the wait times out (10 s), or no relay is connected. A `REQ` while no relay is configured at all is answered with `CLOSED`
|
6. **Publish Acknowledgement** - Every `EVENT` a client publishes gets exactly one `["OK", <id>, <accepted>, <message>]` reply (NIP-01): `true` as soon as any relay accepts it, `false` once every relay has rejected it, the wait times out (10 s), or no relay is connected
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "Nostr Client",
|
"name": "Nostr Client",
|
||||||
"short_description": "Nostr relay multiplexer",
|
"short_description": "Nostr relay multiplexer",
|
||||||
"version": "1.2.0-aio.3",
|
"version": "1.2.0-aio.2",
|
||||||
"tile": "/nostrclient/static/images/nostr-bitcoin.png",
|
"tile": "/nostrclient/static/images/nostr-bitcoin.png",
|
||||||
"contributors": ["calle", "motorina0", "dni"],
|
"contributors": ["calle", "motorina0", "dni"],
|
||||||
"min_lnbits_version": "1.4.0",
|
"min_lnbits_version": "1.4.0",
|
||||||
|
|
|
||||||
18
router.py
18
router.py
|
|
@ -195,7 +195,7 @@ class NostrRouter:
|
||||||
assert len(json_data), "Bad JSON array"
|
assert len(json_data), "Bad JSON array"
|
||||||
|
|
||||||
if json_data[0] == "REQ":
|
if json_data[0] == "REQ":
|
||||||
await self._handle_client_req(json_data)
|
self._handle_client_req(json_data)
|
||||||
return
|
return
|
||||||
|
|
||||||
if json_data[0] == "CLOSE":
|
if json_data[0] == "CLOSE":
|
||||||
|
|
@ -223,29 +223,15 @@ class NostrRouter:
|
||||||
self.pending_publishes[event_id] = PendingPublish(event_id, len(connected))
|
self.pending_publishes[event_id] = PendingPublish(event_id, len(connected))
|
||||||
nostr_client.relay_manager.publish_message(json_str)
|
nostr_client.relay_manager.publish_message(json_str)
|
||||||
|
|
||||||
async def _handle_client_req(self, json_data):
|
def _handle_client_req(self, json_data):
|
||||||
subscription_id = json_data[1]
|
subscription_id = json_data[1]
|
||||||
logger.info(f"New subscription: '{subscription_id}'")
|
logger.info(f"New subscription: '{subscription_id}'")
|
||||||
if not nostr_client.relay_manager.relays:
|
|
||||||
# NIP-01: nothing will ever serve this subscription, so refuse it.
|
|
||||||
# Only when no relay is configured at all: a configured relay that
|
|
||||||
# is merely reconnecting gets the cached subscription replayed.
|
|
||||||
await self._send_closed(subscription_id, "error: no relay connections")
|
|
||||||
return
|
|
||||||
subscription_id_rewritten = urlsafe_short_hash()
|
subscription_id_rewritten = urlsafe_short_hash()
|
||||||
self.original_subscription_ids[subscription_id_rewritten] = subscription_id
|
self.original_subscription_ids[subscription_id_rewritten] = subscription_id
|
||||||
filters = json_data[2:]
|
filters = json_data[2:]
|
||||||
|
|
||||||
nostr_client.relay_manager.add_subscription(subscription_id_rewritten, filters)
|
nostr_client.relay_manager.add_subscription(subscription_id_rewritten, filters)
|
||||||
|
|
||||||
async def _send_closed(self, subscription_id: str, message: str):
|
|
||||||
try:
|
|
||||||
await self.websocket.send_text(
|
|
||||||
json.dumps(["CLOSED", subscription_id, message])
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Failed to send CLOSED for '{subscription_id}': {e}")
|
|
||||||
|
|
||||||
def _handle_client_close(self, subscription_id):
|
def _handle_client_close(self, subscription_id):
|
||||||
subscription_id_rewritten = next(
|
subscription_id_rewritten = next(
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -25,14 +25,10 @@ class FakeRelayManager:
|
||||||
def __init__(self, relays: dict[str, FakeRelay]):
|
def __init__(self, relays: dict[str, FakeRelay]):
|
||||||
self.relays = relays
|
self.relays = relays
|
||||||
self.published: list[str] = []
|
self.published: list[str] = []
|
||||||
self.subscriptions: list[str] = []
|
|
||||||
|
|
||||||
def publish_message(self, message: str):
|
def publish_message(self, message: str):
|
||||||
self.published.append(message)
|
self.published.append(message)
|
||||||
|
|
||||||
def add_subscription(self, subscription_id: str, filters):
|
|
||||||
self.subscriptions.append(subscription_id)
|
|
||||||
|
|
||||||
def close_subscriptions(self, subscriptions):
|
def close_subscriptions(self, subscriptions):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -143,25 +139,3 @@ async def test_stop_drops_pending_publishes(monkeypatch):
|
||||||
|
|
||||||
assert router.pending_publishes == {}
|
assert router.pending_publishes == {}
|
||||||
assert NostrRouter.received_command_results == {}
|
assert NostrRouter.received_command_results == {}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_req_without_configured_relays_is_closed(monkeypatch):
|
|
||||||
router, ws, manager = _router(monkeypatch, {})
|
|
||||||
|
|
||||||
await router._handle_client_to_nostr(json.dumps(["REQ", "sub1", {"kinds": [1]}]))
|
|
||||||
|
|
||||||
assert ws.sent == [["CLOSED", "sub1", "error: no relay connections"]]
|
|
||||||
assert router.subscriptions == []
|
|
||||||
assert manager.subscriptions == []
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_req_with_configured_but_disconnected_relay_is_kept(monkeypatch):
|
|
||||||
router, ws, manager = _router(monkeypatch, {"wss://a": FakeRelay(False)})
|
|
||||||
|
|
||||||
await router._handle_client_to_nostr(json.dumps(["REQ", "sub1", {"kinds": [1]}]))
|
|
||||||
|
|
||||||
assert ws.sent == []
|
|
||||||
assert len(router.subscriptions) == 1
|
|
||||||
assert len(manager.subscriptions) == 1
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue