From 75ea73b178c0dfc169c907ad6027ba5101e2a419 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Wed, 22 Oct 2025 20:55:54 -0400 Subject: [PATCH] Refactor Nostr client imports --- docs/nostr-analytics.md | 4 +-- docs/nostr-event-schemas.md | 4 +-- docs/watch-history-logging.md | 12 ++++---- js/app.js | 5 +++- js/channelProfile.js | 6 ++-- js/historyView.js | 12 +++++++- js/nostr/index.js | 53 +++++++++++++++++++++++++++++++++++ js/relayManager.js | 8 ++---- js/services/authService.js | 2 +- js/services/nostrService.js | 6 ++-- js/subscriptions.js | 15 ++++++---- js/userBlocks.js | 8 ++---- js/viewCounter.js | 16 ++++++++--- js/watchHistoryService.js | 3 +- 14 files changed, 113 insertions(+), 41 deletions(-) create mode 100644 js/nostr/index.js diff --git a/docs/nostr-analytics.md b/docs/nostr-analytics.md index 550ce49d..d819af28 100644 --- a/docs/nostr-analytics.md +++ b/docs/nostr-analytics.md @@ -12,11 +12,11 @@ Most operators can ship with the defaults—24-hour deduplication, a 90-day back ### Anonymous session keys -Playback telemetry runs even when a viewer has not connected a Nostr account. The client first calls [`ensureSessionActor`](../js/nostr.js#L2011-L2105) to mint or restore an ephemeral keypair stored in `localStorage`. That session actor signs view events until the user authenticates, ensuring relays still see consistent pubkeys without blocking anonymous playback. +Playback telemetry runs even when a viewer has not connected a Nostr account. The client first calls [`ensureSessionActor`](../js/nostr/client.js#L2445-L2504) to mint or restore an ephemeral keypair stored in `localStorage`. That session actor signs view events until the user authenticates, ensuring relays still see consistent pubkeys without blocking anonymous playback. ### Publish latency expectations -When a player crosses the 12-second watch threshold, bitvid invokes [`publishViewEvent`](../js/nostr.js#L2159-L2323) through the view-only [`recordVideoView`](../js/nostr.js#L4516-L4555) contract. The helper considers the publish successful as soon as any configured relay acknowledges the event, which in practice keeps latency under a second on public relays. The UI optimistically increments totals by feeding the signed event into [`ingestLocalViewEvent`](../js/viewCounter.js#L608-L640), so cards and modals reflect the new view immediately even while other relays finish syncing. +When a player crosses the 12-second watch threshold, bitvid invokes [`publishViewEvent`](../js/nostr/viewEvents.js#L662-L838) through the view-only [`recordVideoView`](../js/nostr/viewEvents.js#L854-L879) contract. The helper considers the publish successful as soon as any configured relay acknowledges the event, which in practice keeps latency under a second on public relays. The UI optimistically increments totals by feeding the signed event into [`ingestLocalViewEvent`](../js/viewCounter.js#L619-L633), so cards and modals reflect the new view immediately even while other relays finish syncing. The watch history pipeline has been retired; `recordVideoView` now focuses solely on analytics publishing. diff --git a/docs/nostr-event-schemas.md b/docs/nostr-event-schemas.md index 4931d099..d0b98b4c 100644 --- a/docs/nostr-event-schemas.md +++ b/docs/nostr-event-schemas.md @@ -48,7 +48,7 @@ Authentication providers should register their capabilities with the `nostrClient` after login so every publish helper can reuse them: ```js -import { setActiveSigner } from "./nostr.js"; +import { setActiveSigner } from "./nostr/index.js"; setActiveSigner({ type: "extension", // optional label, used to request NIP-07 permissions @@ -143,7 +143,7 @@ plus an optional leading `['head','1']` marker so relays can prioritize the firs 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】 +pointer-only writes and local-only metadata caches.【F:config/instance-config.js†L60-L78】【F:js/nostrEventSchemas.js†L157-L189】【F:js/nostr/watchHistory.js†L1343-L1400】【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 diff --git a/docs/watch-history-logging.md b/docs/watch-history-logging.md index 3f7579d1..c0272e3e 100644 --- a/docs/watch-history-logging.md +++ b/docs/watch-history-logging.md @@ -16,16 +16,16 @@ Snapshots publish two event types to the shared `WATCH_HISTORY_KIND` stream: `['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】 + deduplicate aggressively.【F:js/nostrEventSchemas.js†L175-L189】【F:js/nostr/watchHistory.js†L1343-L1400】 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】 + address.【F:js/nostrEventSchemas.js†L157-L170】【F:js/nostr/watchHistory.js†L1434-L1462】 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】 +were played—only which events or addresses the client can dereference later.【F:js/nostr/watchHistory.js†L1362-L1375】 ## Batching configuration @@ -34,7 +34,7 @@ optionally cap each response by defining `WATCH_HISTORY_BATCH_PAGE_SIZE` in `config/instance-config.js`. Leaving the page size unset returns the full `WATCH_HISTORY_MAX_ITEMS` window while still letting the UI paginate locally, but setting a positive integer keeps API payloads in lockstep with batched -renderers like `historyView`.【F:config/instance-config.js†L101-L123】【F:js/nostr.js†L2987-L2998】【F:js/historyView.js†L1-L38】 +renderers like `historyView`.【F:config/instance-config.js†L101-L123】【F:js/nostr/watchHistory.js†L2218-L2256】【F:js/historyView.js†L1-L38】 ## Snapshot cadence @@ -52,7 +52,7 @@ 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】 +attempts unless a publish succeeds sooner.【F:js/watchHistoryService.js†L948-L985】【F:js/watchHistoryService.js†L723-L777】【F:js/nostr/watchHistory.js†L929-L999】 ## Legacy payload handling @@ -60,7 +60,7 @@ 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】 +relays remain compatible.【F:config/instance-config.js†L60-L78】【F:js/nostr/watchHistory.js†L2120-L2170】【F:js/nostr/watchHistory.js†L660-L689】 ## Local metadata toggles diff --git a/js/app.js b/js/app.js index 3828cebc..91c2e421 100644 --- a/js/app.js +++ b/js/app.js @@ -1,6 +1,7 @@ // js/app.js -import { nostrClient, recordVideoView as recordVideoViewApi } from "./nostr.js"; +import { nostrClient } from "./nostr.js"; +import { recordVideoView } from "./nostr/index.js"; import { torrentClient } from "./webtorrent.js"; import { isDevMode, @@ -137,6 +138,8 @@ import { urlHealthConstants, } from "./state/cache.js"; +const recordVideoViewApi = (...args) => recordVideoView(nostrClient, ...args); + const UNSUPPORTED_BTITH_MESSAGE = "This magnet link is missing a compatible BitTorrent v1 info hash."; diff --git a/js/channelProfile.js b/js/channelProfile.js index 6e63526c..41e155c3 100644 --- a/js/channelProfile.js +++ b/js/channelProfile.js @@ -1,9 +1,7 @@ // js/channelProfile.js -import { - nostrClient, - convertEventToVideo as sharedConvertEventToVideo, -} from "./nostr.js"; +import { nostrClient } from "./nostr.js"; +import { convertEventToVideo as sharedConvertEventToVideo } from "./nostr/index.js"; import { DEFAULT_RELAY_URLS } from "./nostr/toolkit.js"; import { subscriptions } from "./subscriptions.js"; import { attachHealthBadges } from "./gridHealth.js"; diff --git a/js/historyView.js b/js/historyView.js index 8828bd67..fb19b7e6 100644 --- a/js/historyView.js +++ b/js/historyView.js @@ -1,7 +1,10 @@ // js/historyView.js import watchHistoryService from "./watchHistoryService.js"; -import { nostrClient, updateWatchHistoryList } from "./nostr.js"; +import { nostrClient } from "./nostr.js"; +import { + updateWatchHistoryList as updateWatchHistoryListWithManager, +} from "./nostr/index.js"; import { pointerKey, normalizePointerInput } from "./nostr/watchHistory.js"; import { WATCH_HISTORY_BATCH_RESOLVE, @@ -33,6 +36,13 @@ const FALLBACK_AVATAR = "assets/svg/default-profile.svg"; const isDevEnv = typeof process !== "undefined" && process?.env?.NODE_ENV !== "production"; +const updateWatchHistoryList = (items, options) => + updateWatchHistoryListWithManager( + nostrClient.watchHistory, + items, + options, + ); + function escapeSelector(value) { if (typeof value !== "string") { return ""; diff --git a/js/nostr/index.js b/js/nostr/index.js new file mode 100644 index 00000000..5361b3b8 --- /dev/null +++ b/js/nostr/index.js @@ -0,0 +1,53 @@ +export { + NostrClient, + getActiveSigner, + setActiveSigner, + clearActiveSigner, + resolveActiveSigner, + shouldRequestExtensionPermissions, +} from "./client.js"; + +export { + DEFAULT_NIP07_ENCRYPTION_METHODS, + DEFAULT_NIP07_PERMISSION_METHODS, +} from "./nip07Permissions.js"; + +export { + buildNip71MetadataTags, + buildNip71VideoEvent, + collectNip71PointerRequests, + convertEventToVideo, + extractNip71MetadataFromTags, + getDTagValueFromTags, + mergeNip71MetadataIntoVideo, + populateNip71MetadataForVideos, + processNip71Events, + buildVideoPointerValue, + stringFromInput, +} from "./nip71.js"; + +export { + normalizePointerInput, + pointerKey, + chunkWatchHistoryPayloadItems, + normalizeActorKey, + parseWatchHistoryContentWithFallback, + isNip04EncryptedWatchHistoryEvent, + watchHistoryHelpers, + updateWatchHistoryList, + removeWatchHistoryItem, +} from "./watchHistory.js"; + +export { + deriveViewEventBucketIndex, + deriveViewEventPointerScope, + hasRecentViewPublish, + rememberViewPublish, + createVideoViewEventFilters, + getViewEventGuardWindowMs, + listVideoViewEvents, + subscribeVideoViewEvents, + countVideoViewEvents, + publishViewEvent, + recordVideoView, +} from "./viewEvents.js"; diff --git a/js/relayManager.js b/js/relayManager.js index 85db9a86..ccc82ea4 100644 --- a/js/relayManager.js +++ b/js/relayManager.js @@ -1,10 +1,8 @@ import { isDevMode } from "./config.js"; import { DEFAULT_RELAY_URLS } from "./nostr/toolkit.js"; -import { - getActiveSigner, - nostrClient, - requestDefaultExtensionPermissions, -} from "./nostr.js"; +import { nostrClient } from "./nostr.js"; +import { getActiveSigner } from "./nostr/index.js"; +import { requestDefaultExtensionPermissions } from "./nostrClientRegistry.js"; import { buildRelayListEvent } from "./nostrEventSchemas.js"; import { devLogger, userLogger } from "./utils/logger.js"; import { diff --git a/js/services/authService.js b/js/services/authService.js index 8a35a6f8..0132f204 100644 --- a/js/services/authService.js +++ b/js/services/authService.js @@ -7,7 +7,7 @@ import { getCurrentUserNpub, } from "../state/appState.js"; import { userLogger } from "../utils/logger.js"; -import { requestDefaultExtensionPermissions } from "../nostr.js"; +import { requestDefaultExtensionPermissions } from "../nostrClientRegistry.js"; import { getSavedProfiles, getActiveProfilePubkey, diff --git a/js/services/nostrService.js b/js/services/nostrService.js index e9f76122..01dbb1ee 100644 --- a/js/services/nostrService.js +++ b/js/services/nostrService.js @@ -1,7 +1,5 @@ -import { - nostrClient, - convertEventToVideo, -} from "../nostr.js"; +import { nostrClient } from "../nostr.js"; +import { convertEventToVideo } from "../nostr/index.js"; import { accessControl } from "../accessControl.js"; import { ALLOW_NSFW_CONTENT } from "../config.js"; import { userLogger } from "../utils/logger.js"; diff --git a/js/subscriptions.js b/js/subscriptions.js index e8a3e4a5..92f80d3a 100644 --- a/js/subscriptions.js +++ b/js/subscriptions.js @@ -1,12 +1,12 @@ // js/subscriptions.js +import { nostrClient } from "./nostr.js"; import { getActiveSigner, - nostrClient, convertEventToVideo as sharedConvertEventToVideo, - requestDefaultExtensionPermissions, - listVideoViewEvents as listVideoViewEventsApi, - subscribeVideoViewEvents as subscribeVideoViewEventsApi, -} from "./nostr.js"; + listVideoViewEvents, + subscribeVideoViewEvents, +} from "./nostr/index.js"; +import { requestDefaultExtensionPermissions } from "./nostrClientRegistry.js"; import { DEFAULT_RELAY_URLS } from "./nostr/toolkit.js"; import { buildSubscriptionListEvent, @@ -112,6 +112,11 @@ function determineDecryptionOrder(event, availableSchemes) { const getApp = () => getApplication(); +const listVideoViewEventsApi = (pointer, options) => + listVideoViewEvents(nostrClient, pointer, options); +const subscribeVideoViewEventsApi = (pointer, options) => + subscribeVideoViewEvents(nostrClient, pointer, options); + /** * Manages the user's subscription list (kind=30002) *privately*, * using NIP-04 encryption for the content field. diff --git a/js/userBlocks.js b/js/userBlocks.js index 1bfebf4e..c0b70f54 100644 --- a/js/userBlocks.js +++ b/js/userBlocks.js @@ -1,9 +1,7 @@ // js/userBlocks.js -import { - getActiveSigner, - nostrClient, - requestDefaultExtensionPermissions, -} from "./nostr.js"; +import { nostrClient } from "./nostr.js"; +import { getActiveSigner } from "./nostr/index.js"; +import { requestDefaultExtensionPermissions } from "./nostrClientRegistry.js"; import { buildBlockListEvent, BLOCK_LIST_IDENTIFIER } from "./nostrEventSchemas.js"; import { userLogger } from "./utils/logger.js"; import { diff --git a/js/viewCounter.js b/js/viewCounter.js index 5971eebe..41b83d93 100644 --- a/js/viewCounter.js +++ b/js/viewCounter.js @@ -17,16 +17,24 @@ import { VIEW_COUNT_DEDUPE_WINDOW_SECONDS, } from "./config.js"; import { userLogger } from "./utils/logger.js"; +import { nostrClient } from "./nostr.js"; import { - countVideoViewEvents as countVideoViewEventsApi, - listVideoViewEvents as listVideoViewEventsApi, - subscribeVideoViewEvents as subscribeVideoViewEventsApi, -} from "./nostr.js"; + countVideoViewEvents, + listVideoViewEvents, + subscribeVideoViewEvents, +} from "./nostr/index.js"; const VIEW_COUNTER_STORAGE_KEY = "bitvid:view-counter:v1"; const STORAGE_DEBOUNCE_MS = 500; const SECONDS_PER_DAY = 86_400; +const listVideoViewEventsApi = (pointer, options) => + listVideoViewEvents(nostrClient, pointer, options); +const subscribeVideoViewEventsApi = (pointer, options) => + subscribeVideoViewEvents(nostrClient, pointer, options); +const countVideoViewEventsApi = (pointer, options) => + countVideoViewEvents(nostrClient, pointer, options); + /** @type {Map} */ const pointerStates = new Map(); /** @type {Map} */ diff --git a/js/watchHistoryService.js b/js/watchHistoryService.js index 57eb7b86..54ced6a5 100644 --- a/js/watchHistoryService.js +++ b/js/watchHistoryService.js @@ -1,6 +1,7 @@ // js/watchHistoryService.js -import { nostrClient, requestDefaultExtensionPermissions } from "./nostr.js"; +import { nostrClient } from "./nostr.js"; +import { requestDefaultExtensionPermissions } from "./nostrClientRegistry.js"; import { normalizePointerInput, pointerKey,