When all relay connections are temporarily lost, EVENT messages published by extensions (nostrmarket, events) are now queued in a bounded deque (max 100) instead of being silently dropped. On reconnection, queued events are flushed to all connected relays. Dead relay queues are also drained before restart to preserve in-flight events. Closes aiolabs/nostrclient#1 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3.2 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Nostrclient is an LNbits extension that acts as an always-on Nostr relay multiplexer. Multiple Nostr clients connect to a single WebSocket endpoint, which fans out requests to multiple configured Nostr relays and aggregates/deduplicates responses. It rewrites subscription IDs per-client to prevent conflicts.
Build & Development Commands
All commands use uv as the Python package manager:
make format # Format: prettier + black + ruff --fix
make check # All checks: mypy, pyright, black --check, ruff check, prettier --check
make test # Run pytest (DEBUG=true PYTHONUNBUFFERED=1 uv run pytest)
make mypy # Type check (excludes nostr/ directory)
make pre-commit # Run pre-commit hooks on all files
Individual checks: make checkblack, make checkruff, make checkprettier.
CI runs lint then pytest with LNBITS_BACKEND_WALLET_CLASS=FakeWallet.
Architecture
Request flow: Nostr Clients → WebSocket → NostrRouter → RelayManager → Nostr Relays
Key components:
-
NostrRouter (
router.py) — One per client WebSocket connection. Rewrites subscription IDs (original → hashed → original) to isolate clients. Two async tasks:_client_to_nostr(forward requests) and_nostr_to_client(deliver aggregated responses). -
NostrClient (
nostr/client/client.py) — Singleton orchestrator. Owns the RelayManager. Polls MessagePool and dispatches events via callbacks to routers. -
RelayManager (
nostr/relay_manager.py) — Manages connections to multiple relays. Caches subscriptions so new relays receive existing subscriptions. Runs health checks viacheck_and_restart_relays(). -
Relay (
nostr/relay.py) — Individual relay WebSocket connection with retry/backoff, ping latency tracking, and error counting. -
MessagePool (
nostr/message_pool.py) — Thread-safe event aggregation with deduplication by event ID across all relays.
Hybrid threading model: Relay connections use threads (via RelayManager.open_connections()); client communication uses asyncio. The bridge is in tasks.py where subscribe_events() runs in a thread executor.
Lifecycle (__init__.py): nostrclient_start() spawns three background tasks (init relays, subscribe events, check relays). nostrclient_stop() cancels tasks, stops routers, closes the client.
API Endpoints (views_api.py)
- REST endpoints under
/api/v1/for relay CRUD and config (admin-authenticated) - WebSocket endpoints:
/api/v1/{ws_id}(private, encrypted ID) and/api/v1/relay(public, if enabled)
Database
Three migrations in migrations.py: relays table, config table (JSON extra field), config owner scoping. CRUD in crud.py uses LNbits database abstraction.
Code Quality Notes
- mypy excludes
nostr/*— this is a custom Nostr protocol implementation, not a third-party package - Ruff rules: F, E, W, I, A, C, N, UP, RUF, B
- Frontend: Vue.js + Quasar via LNbits base templates (
templates/nostrclient/index.html) - Pub key helpers in
helpers.pynormalize between hex and bech32 (npub1) formats