diff --git a/docs/nostr-event-schemas.md b/docs/nostr-event-schemas.md index d7c8dc67..a52e034b 100644 --- a/docs/nostr-event-schemas.md +++ b/docs/nostr-event-schemas.md @@ -49,8 +49,8 @@ read/write split. | NIP-94 mirror (`NOTE_TYPES.VIDEO_MIRROR`) | `1063` | Tags forwarded from `publishVideo` (URL, mime type, thumbnail, alt text, magnet) | Plain text alt description | | Relay list (`NOTE_TYPES.RELAY_LIST`) | `10002` | Repeating `['r', ]` tags, optionally with a marker of `'read'` or `'write'` to scope the relay; marker omitted for read/write relays | Empty content | | View counter (`NOTE_TYPES.VIEW_EVENT`) | `WATCH_HISTORY_KIND` (default `30079`, clients also read legacy `30078`) | Canonical tag set: `['t','view']`, a pointer tag (`['e', ]` or `['a',
]`), and a stable dedupe tag `['d', ]`, with optional `['session','true']` when a session actor signs; schema overrides may append extra tags. `['video', ...]` is supported for legacy overrides only. | Optional plaintext message | -| Watch history index (`NOTE_TYPES.WATCH_HISTORY_INDEX`, legacy) | `WATCH_HISTORY_KIND` (default `30079`, clients also read legacy `30078`) | `['d', WATCH_HISTORY_LIST_IDENTIFIER]`, `['snapshot', ]`, `['chunks', ]`, repeated `['a',
]` pointers to each chunk event plus schema append tags | JSON payload `{ snapshot, totalChunks }` | -| Watch history chunk (`NOTE_TYPES.WATCH_HISTORY_CHUNK`, legacy) | `WATCH_HISTORY_KIND` (default `30079`, clients also read legacy `30078`) | `['d', ]`, `['encrypted','nip04']`, `['snapshot', ]`, `['chunk', , ]`, pointer tags for each entry, plus schema append tags | NIP-04 encrypted JSON chunk (`{ version, snapshot, chunkIndex, totalChunks, items[] }`) | +| Watch history index (`NOTE_TYPES.WATCH_HISTORY_INDEX`) | `WATCH_HISTORY_KIND` (default `30079`, clients also read legacy `30078`) | `['d', WATCH_HISTORY_LIST_IDENTIFIER]`, `['snapshot', ]`, `['chunks', ]`, repeated `['a', ]` pointers plus schema append tags | JSON payload `{ snapshot, totalChunks }` (may be empty when using tags only) | +| Watch history chunk (`NOTE_TYPES.WATCH_HISTORY_CHUNK`) | `WATCH_HISTORY_KIND` (default `30079`, clients also read legacy `30078`) | `['d', ]`, `['encrypted','nip04']`, `['snapshot', ]`, `['chunk', , ]`, optional leading `['head','1']` on the first chunk, pointer tags for each item, plus schema append tags | NIP-04 encrypted JSON chunk (`{ version, snapshot, chunkIndex, totalChunks, items[] }`) | | Subscription list (`NOTE_TYPES.SUBSCRIPTION_LIST`) | `30002` | `['d', 'subscriptions']` | NIP-04 encrypted JSON `{ subPubkeys: string[] }` | | User block list (`NOTE_TYPES.USER_BLOCK_LIST`) | `30002` | `['d', 'user-blocks']` | NIP-04 encrypted JSON `{ blockedPubkeys: string[] }` | | Admin moderation list (`NOTE_TYPES.ADMIN_MODERATION_LIST`) | `30000` | `['d', 'bitvid:admin:editors']`, repeated `['p', ]` entries | Empty content | @@ -63,4 +63,21 @@ builders inherit the same debugging knobs. ### Watch history identifiers -The watch history publishing pipeline has been retired. These identifiers remain documented so existing relays can keep serving legacy payloads, but new clients no longer emit or refresh watch-history events. +The encrypted watch history pipeline is gated by the `FEATURE_WATCH_HISTORY_V2` +runtime flag. When the flag is disabled, clients continue emitting view events +but skip publishing snapshots; the UI still resolves legacy `watch-history:v2:index` +lists so operators can stage the rollout per deployment.【F:config/instance-config.js†L69-L94】【F:js/watchHistoryService.js†L82-L140】【F:js/watchHistoryService.js†L948-L985】 + +Active identifiers include the default `WATCH_HISTORY_LIST_IDENTIFIER` +(`"watch-history"`) and the legacy aliases enumerated in +`WATCH_HISTORY_LEGACY_LIST_IDENTIFIERS`. Chunk events derive their `d` tag from +`:`, advertise `['snapshot', ]`, and carry `['chunk', , ]` +plus an optional leading `['head','1']` marker so relays can prioritize the first +ciphertext. All chunk content is encrypted with NIP-04 and stores only pointer +entries; richer metadata remains on-device via the +[`WatchHistoryService`](../js/watchHistoryService.js) APIs, which default to +pointer-only writes and local-only metadata caches.【F:config/instance-config.js†L60-L78】【F:js/nostrEventSchemas.js†L157-L189】【F:js/nostr.js†L2329-L2369】【F:js/watchHistoryService.js†L331-L376】 + +Refer to the [`WatchHistoryService`](../js/watchHistoryService.js) for queue +management hooks, manual snapshot helpers, and metadata toggle controls that +complement these schema definitions.【F:js/watchHistoryService.js†L695-L776】【F:js/watchHistoryService.js†L1040-L1093】 diff --git a/docs/watch-history-logging.md b/docs/watch-history-logging.md index 78f354a8..c31998b8 100644 --- a/docs/watch-history-logging.md +++ b/docs/watch-history-logging.md @@ -1,3 +1,64 @@ -# Watch History Logging Cheatsheet +# Watch history logging (v2 pipeline) -The legacy watch history publisher has been removed. Clients no longer emit watch-history snapshot events, and related console logs have been retired alongside the feature. +BitVid's watch history sync is orchestrated by the +[`WatchHistoryService`](../js/watchHistoryService.js). The service collects +per-video pointer data during playback, batches it in session storage, and emits +encrypted snapshots to relays only when a publish is triggered. Relay payloads +stick to pointer identifiers so title, thumbnail, and profile context remain on +the client unless an operator opts into local metadata caching. + +## Snapshot composition + +Snapshots publish two event types to the shared `WATCH_HISTORY_KIND` stream: + +1. **Chunk events** — Each chunk carries a deterministic identifier in its + `d` tag, advertises its `snapshot` and `chunk` position, and is tagged with + `['encrypted','nip04']`. The first chunk is additionally marked as + `['head','1']` and may append `"a"` tags that reference previous chunk + addresses, allowing readers to reconstruct the full set even when relays + deduplicate aggressively.【F:js/nostrEventSchemas.js†L175-L189】【F:js/nostr.js†L2329-L2369】 +2. **Pointer event** — After chunks are accepted, BitVid emits a compact index + event containing the canonical list identifier (`['d', WATCH_HISTORY_LIST_IDENTIFIER]`), + the `snapshot` label, the total chunk count, and an `a` tag for each chunk + address.【F:js/nostrEventSchemas.js†L157-L170】【F:js/nostr.js†L2425-L2441】 + +Chunk content is a NIP-04 encrypted JSON envelope of `{ version: 2, snapshot, +chunkIndex, totalChunks, items[] }`. Items are pointer descriptors (`e` or `a` +references plus optional relay hints) so that relays never learn which titles +were played—only which events or addresses the client can dereference later.【F:js/nostr.js†L2337-L2364】 + +## Snapshot cadence + +`WatchHistoryService.publishView` queues pointer entries while the feature flag +is enabled, merging repeats with a one-minute throttle and persisting the queue +in `sessionStorage`. Snapshots run when `watchHistoryService.snapshot()` is +invoked (e.g., on `beforeunload`, `visibilitychange`, or manual sync actions in +the history UI) and clear the queue on success.【F:js/watchHistoryService.js†L872-L917】【F:js/app.js†L6041-L6081】【F:js/app.js†L7467-L7484】 +If no items are pending, the snapshot resolves with an empty result so UI calls +remain idempotent.【F:js/watchHistoryService.js†L932-L944】 + +## Republish and backoff + +When a snapshot publish fails but the response is retryable, the service stores +the returned `snapshotId`, records the originating reason, and delegates to the +Nostr client to retry with exponential backoff. Retries start after two seconds, +double per attempt with 25% jitter, cap at five minutes, and stop after eight +attempts unless a publish succeeds sooner.【F:js/watchHistoryService.js†L948-L985】【F:js/watchHistoryService.js†L723-L777】【F:js/nostr.js†L1988-L2087】 + +## Legacy payload handling + +Readers hydrate history by combining the pointer event, any encrypted chunks, +and fallback metadata embedded in legacy watch-history lists. The resolver still +accepts the `watch-history:v2:index` identifier, merges pointer tags from the +index event, and falls back to plaintext content when decryption fails so older +relays remain compatible.【F:config/instance-config.js†L60-L78】【F:js/nostr.js†L2704-L2779】【F:js/nostr.js†L5216-L5264】 + +## Local metadata toggles + +By default the service only persists pointer data to relays; richer metadata is +stored locally (and opt-in). Users can toggle the "store watch history metadata" +preference, which flips a localStorage flag, clears cached entries when +disabled, and never pushes the sanitized video/profile cache over the network.【F:js/watchHistoryService.js†L82-L140】【F:js/watchHistoryService.js†L232-L313】【F:js/watchHistoryService.js†L331-L376】 + +See the [`WatchHistoryService` API](../js/watchHistoryService.js) for +event hooks, queue inspection helpers, and manual snapshot controls.