Files
bitvid/docs/nostr-client-overview.md
T
google-labs-jules[bot] 40690c7008 chore(ai): doc js/nostr/client.js (agent)
- Added JSDoc comments to `NostrClient` class and methods.
- Documented state management (`activeMap`, `tombstones`), caching strategy, and subscription buffering.
- Created `docs/nostr-client-overview.md` with a high-level architecture overview.
- Verified with `tests/nostr-login-permissions.test.mjs`.
2026-01-20 15:23:38 +00:00

81 lines
4.6 KiB
Markdown

# NostrClient Overview
The `NostrClient` (`js/nostr/client.js`) is the central controller for all Nostr network interactions, state management, and cryptographic operations in the application. It acts as a facade over `nostr-tools` and manages the complexity of connecting to relays, syncing data, and handling various NIPs (Nostr Implementation Possibilities).
## Core Responsibilities
1. **Connection Management**: Maintains persistent connections to multiple relays using `SimplePool`.
2. **State Management**: Tracks video events, deletions (tombstones), and deduplicates content.
3. **Caching**: Implements a "stale-while-revalidate" strategy using IndexedDB (`EventsCacheStore`) to load content immediately while fetching updates in the background.
4. **Publishing**: Orchestrates the signing and broadcasting of events, including complex multi-event flows (e.g., Video + Mirror + NIP-71).
5. **Signer Management**: Abstracts the differences between NIP-07 (Extension), NIP-46 (Remote/Bunker), and Local (nsec) signers.
## State Architecture
The client maintains several in-memory maps to manage state:
* **`allEvents`** (`Map<string, Video>`): A raw collection of all fetched video events, keyed by Event ID. This includes history (older versions) and is used for hydration.
* **`activeMap`** (`Map<string, Video>`): The "materialized view" used by the UI. It maps a unique key (Video Root ID or `pubkey:dTag`) to the *latest* valid version of a video. It automatically handles deduplication and versioning.
* **`rawEvents`** (`Map<string, Event>`): caches the exact raw JSON events received from relays. This is crucial for signature verification and republishing (e.g., NIP-94 mirrors) without altering the original signature.
* **`tombstones`** (`Map<string, number>`): Tracks the timestamp of deletions. This prevents older versions of a video from "resurrecting" if a relay sends them after a user has deleted the video.
## Caching Strategy
To ensure a snappy UX, the client uses `EventsCacheStore` to persist `allEvents` and `tombstones` to IndexedDB.
1. **Load**: On `init()`, the client loads the snapshot from IndexedDB into memory.
2. **Fingerprinting**: To minimize write overhead, objects are fingerprinted. Only changed items are written back to disk.
3. **Persistence**: Changes are persisted periodically (debounced) or on critical actions (like a flush after a batch subscription).
## Subscription Model: Buffering & Debouncing
Relays often send events in bursts (e.g., thousands of events on initial load). Processing each event individually would freeze the UI. `NostrClient` implements a **Buffering & Debouncing** strategy in `subscribeVideos`:
1. **Buffer**: Incoming events are pushed into an `eventBuffer` array.
2. **Debounce**: A flush is scheduled (e.g., 75ms delay).
3. **Flush**: When the timer fires, the entire buffer is processed in a batch:
* Events are converted to `Video` objects.
* `activeMap` is updated (latest-wins logic).
* `onVideo` callback is fired once for the batch or per-item, but React updates are batched effectively.
* State is persisted to the local cache.
## Publishing Lifecycle
Publishing a video is a multi-step process to ensure compatibility and discoverability:
1. **Video Note (Kind 30078)**: The primary payload containing metadata (title, magnet, etc.).
2. **NIP-94 Mirror (Kind 1063)**: (Optional) If a HTTP URL is provided, a "File Header" event is published to support clients that only rely on NIP-94.
3. **NIP-71 Metadata (Kind 22)**: (Optional) If the user adds categories or tags, a separate metadata event is linked to the video.
## Code Example
```javascript
import { NostrClient } from "./js/nostr/client.js";
const client = new NostrClient();
// 1. Initialize (loads cache, connects to relays)
await client.init();
// 2. Subscribe to videos
const sub = client.subscribeVideos((video) => {
console.log("New video received:", video.title);
});
// 3. Login (NIP-07)
await client.login();
// 4. Publish a video
await client.publishVideo({
title: "My Video",
magnet: "magnet:?xt=urn:btih:...",
// ...
}, client.pubkey);
```
## Why it works this way
* **Active Map**: The `activeMap` ensures that if a user edits a video (publishing a new version), the UI automatically shows the new one and hides the old one, even if relays send them out of order.
* **Tombstones**: Distributed systems are eventually consistent. A relay might send a "deleted" video event *after* we've processed the deletion locally. Tombstones ensure we ignore it.
* **Raw Events**: We never modify the original event object for verification purposes. The `Video` object in `allEvents` is a normalized, app-specific representation.