mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
chore: remove watch history logic
This commit is contained in:
1 parent
32ab19e915
commit
48410b1abb
9 files changed
+109
-6728
No files matched your search
+1
-11
@@ -18,7 +18,7 @@ Playback telemetry runs even when a viewer has not connected a Nostr account. Th
|
||||
|
||||
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.
|
||||
|
||||
`recordVideoView` no longer mutates watch history—it strictly publishes analytics events. Clients that want personal playback timelines must independently call [`updateWatchHistoryList`](../js/nostr.js#L3235-L3344) once their threshold logic fires. See the [watch history pipeline](#watch-history-pipeline) for integration details.
|
||||
The watch history pipeline has been retired; `recordVideoView` now focuses solely on analytics publishing.
|
||||
|
||||
To avoid double-counting the same session, the playback stack respects the cooldown keys generated in [`js/app.js`](../js/app.js#L6825-L6893). The combination of pointer identity and viewer fingerprint prevents re-logging until the dedupe window expires or the user switches accounts.
|
||||
|
||||
@@ -28,13 +28,3 @@ Every view event includes a unique `#d` tag derived from the pointer scope, the
|
||||
|
||||
Background hydration keeps optimistic counts honest. The view counter subscribes to live events while simultaneously fetching historical lists and issuing a `COUNT` request. Whenever the authoritative total returned by `COUNT` exceeds the locally accumulated sum, the hydration routine overwrites the optimistic value so the UI stays aligned with relay truth. If `COUNT` falls back (because relays reject it or the browser is offline), the locally deduped history still anchors totals, and the next successful hydration pass reconciles any drift. This interplay means cards, modals, and dashboards settle on the same number without requiring a full page reload.
|
||||
|
||||
## Watch history pipeline
|
||||
|
||||
BitVid ships a parallel watch-history system so operators can disable analytics entirely while still giving viewers a private "continue watching" shelf. [`updateWatchHistoryList`](../js/nostr.js#L3235-L3344) gathers the latest pointer plus any cached entries, normalizes them, and persists an encrypted snapshot through [`publishWatchHistorySnapshot`](../js/nostr.js#L2170-L2584). Each publish produces:
|
||||
|
||||
- A monotonic index event that references the active snapshot ID and every chunk address.
|
||||
- One or more encrypted chunk events containing `{ type, value, relay?, watchedAt }` tuples for each pointer. `watchedAt` is truncated to seconds and defaults to `Date.now()` when the caller omits it.
|
||||
|
||||
Snapshots dedupe pointers, cap the list at the configured maximum, and are signed by the same session actor used for views. Clients should supply the same pointer structure they feed into [`recordVideoView`](../js/nostr.js#L4516-L4555)—typically `{ type: 'a', value: <address>, relay }`—while setting a `watchedAt` timestamp if they want to preserve historical ordering during backfills.
|
||||
|
||||
Because view counting and watch history are intentionally decoupled, invoking `updateWatchHistoryList` is the caller's responsibility. Most integrations fire it immediately after `recordVideoView` confirms success, but privacy-focused deployments can disable view logging and still call the watch-history API. Cross-check the [publish latency expectations](#publish-latency-expectations) section when wiring both flows so operators understand they can operate independently.
|
||||
@@ -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', <relay url>]` 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', <eventId>]` or `['a', <address>]`), and a stable dedupe tag `['d', <view identifier>]`, 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`) | `WATCH_HISTORY_KIND` (default `30079`, clients also read legacy `30078`) | `['d', WATCH_HISTORY_LIST_IDENTIFIER]`, `['snapshot', <id>]`, `['chunks', <total>]`, repeated `['a', <address>]` pointers to each chunk event plus schema append tags | JSON payload `{ snapshot, totalChunks }` |
|
||||
| Watch history chunk (`NOTE_TYPES.WATCH_HISTORY_CHUNK`) | `WATCH_HISTORY_KIND` (default `30079`, clients also read legacy `30078`) | `['d', <chunk identifier>]`, `['encrypted','nip04']`, `['snapshot', <id>]`, `['chunk', <index>, <total>]`, 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`, legacy) | `WATCH_HISTORY_KIND` (default `30079`, clients also read legacy `30078`) | `['d', WATCH_HISTORY_LIST_IDENTIFIER]`, `['snapshot', <id>]`, `['chunks', <total>]`, repeated `['a', <address>]` 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', <chunk identifier>]`, `['encrypted','nip04']`, `['snapshot', <id>]`, `['chunk', <index>, <total>]`, pointer tags for each entry, 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', <pubkey>]` entries | Empty content |
|
||||
@@ -63,16 +63,4 @@ builders inherit the same debugging knobs.
|
||||
|
||||
### Watch history identifiers
|
||||
|
||||
Watch-history snapshots now publish a dedicated index event with
|
||||
`#d ${WATCH_HISTORY_LIST_IDENTIFIER}` (defaults to `watch-history`). The
|
||||
index advertises the active snapshot id, total chunk count, and a pointer to
|
||||
each chunk via `a` tags so clients can fetch the encrypted payloads without
|
||||
guessing identifiers. Deployments that previously used
|
||||
`watch-history:v2:index` continue to resolve thanks to the legacy identifier
|
||||
list.
|
||||
|
||||
Every chunk event uses a unique `d` tag such as
|
||||
`watch-history:v2/<snapshot>/<chunkIndex>`. Legacy snapshots created before the
|
||||
index event shipped stored the first chunk at `#d watch-history`; new clients
|
||||
should continue reading that identifier for backward compatibility when
|
||||
hydrating older histories.
|
||||
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.
|
||||
@@ -1,15 +1,3 @@
|
||||
# Watch History Logging Cheatsheet
|
||||
|
||||
The watch history publisher emits developer-mode console messages from `publishWatchHistorySnapshot()`.
|
||||
|
||||
- `Watch history snapshot updated` (info) — all chunks and the index event were accepted by at least one relay. The summary includes:
|
||||
- `actor`: abbreviated pubkey that authored the snapshot.
|
||||
- `itemCount`: number of watch history entries that made it into the snapshot payload.
|
||||
- `snapshot`: identifier constructed from the current unix timestamp and a random suffix.
|
||||
- `latestPointer`: truncated pointer key for the newest watch entry that survived chunking.
|
||||
- `latestWatchedAt`: ISO timestamp derived from the most recent entry's `watchedAt` (falls back to `Date.now()` when missing).
|
||||
- `Watch history snapshot publish incomplete` (warning) — at least one chunk or the index event failed to reach any relay. When this fires the snapshot is still cached locally and the client will retry publication later.
|
||||
|
||||
Errors that precede the info log do **not** necessarily indicate a failure. For example, a message like `WebSocket connection to 'ws://127.0.0.1:4869/' failed` simply means one relay in the user's configured relay list is offline. The publisher only requires that at least one relay accepts each chunk before reporting success.
|
||||
|
||||
Refer to the log source in `js/nostr.js` for the exact structure and retry behavior.
|
||||
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.
|
||||
@@ -1,11 +1,7 @@
|
||||
// js/app.js
|
||||
|
||||
import { loadView } from "./viewManager.js";
|
||||
import {
|
||||
nostrClient,
|
||||
recordVideoView,
|
||||
updateWatchHistoryList,
|
||||
} from "./nostr.js";
|
||||
import { nostrClient, recordVideoView } from "./nostr.js";
|
||||
import { torrentClient } from "./webtorrent.js";
|
||||
import { isDevMode, ADMIN_SUPER_NPUB } from "./config.js";
|
||||
import { accessControl, normalizeNpub } from "./accessControl.js";
|
||||
@@ -4289,29 +4285,6 @@ class bitvidApp {
|
||||
}
|
||||
button.dataset.navBound = "true";
|
||||
button.addEventListener("click", () => {
|
||||
if (name === "history") {
|
||||
let actorCandidate;
|
||||
if (typeof this.pubkey === "string" && this.pubkey.trim()) {
|
||||
actorCandidate = this.pubkey.trim();
|
||||
} else if (
|
||||
typeof window?.app?.pubkey === "string" &&
|
||||
window.app.pubkey.trim()
|
||||
) {
|
||||
actorCandidate = window.app.pubkey.trim();
|
||||
}
|
||||
|
||||
void nostrClient
|
||||
.fetchWatchHistory(actorCandidate, { forceRefresh: true })
|
||||
.catch((error) => {
|
||||
if (isDevMode) {
|
||||
console.warn(
|
||||
"[profileModal] Failed to prefetch watch history on navigation:",
|
||||
error
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.selectProfilePane(name);
|
||||
});
|
||||
});
|
||||
@@ -6987,10 +6960,6 @@ class bitvidApp {
|
||||
clearTimeout(state.viewTimerId);
|
||||
}
|
||||
|
||||
if (state.historyTimerId) {
|
||||
clearTimeout(state.historyTimerId);
|
||||
}
|
||||
|
||||
if (Array.isArray(state.handlers) && state.videoEl) {
|
||||
for (const { eventName, handler } of state.handlers) {
|
||||
try {
|
||||
@@ -7085,7 +7054,6 @@ class bitvidApp {
|
||||
}
|
||||
|
||||
const VIEW_THRESHOLD_SECONDS = 12;
|
||||
const WATCH_HISTORY_THRESHOLD_SECONDS = 10;
|
||||
const state = {
|
||||
videoEl,
|
||||
pointer,
|
||||
@@ -7093,15 +7061,7 @@ class bitvidApp {
|
||||
viewerIdentityKey,
|
||||
handlers: [],
|
||||
viewTimerId: null,
|
||||
historyTimerId: null,
|
||||
viewFired: false,
|
||||
historyFired: false,
|
||||
};
|
||||
|
||||
const maybeCleanup = () => {
|
||||
if (state.viewFired && state.historyFired) {
|
||||
this.cancelPendingViewLogging();
|
||||
}
|
||||
};
|
||||
|
||||
const clearTimer = (timerKey) => {
|
||||
@@ -7118,7 +7078,7 @@ class bitvidApp {
|
||||
}
|
||||
state.viewFired = true;
|
||||
clearTimer("view");
|
||||
maybeCleanup();
|
||||
this.cancelPendingViewLogging();
|
||||
|
||||
const { pointer: thresholdPointer, pointerKey: thresholdPointerKey } =
|
||||
state;
|
||||
@@ -7171,41 +7131,6 @@ class bitvidApp {
|
||||
});
|
||||
};
|
||||
|
||||
const finalizeWatchHistory = () => {
|
||||
if (state.historyFired) {
|
||||
return;
|
||||
}
|
||||
state.historyFired = true;
|
||||
clearTimer("history");
|
||||
maybeCleanup();
|
||||
|
||||
const { pointer: thresholdPointer } = state;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const historyResult = await updateWatchHistoryList(thresholdPointer);
|
||||
if (!historyResult?.ok) {
|
||||
console.warn(
|
||||
"[playVideoWithFallback] Watch history update failed:",
|
||||
historyResult
|
||||
);
|
||||
}
|
||||
} catch (historyError) {
|
||||
console.warn(
|
||||
"[playVideoWithFallback] Exception while updating watch history:",
|
||||
historyError
|
||||
);
|
||||
}
|
||||
})().catch((error) => {
|
||||
if (isDevMode) {
|
||||
console.warn(
|
||||
"[playVideoWithFallback] Unexpected error while updating watch history:",
|
||||
error
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const scheduleTimer = (timerKey, thresholdSeconds, callback) => {
|
||||
const firedKey = `${timerKey}Fired`;
|
||||
const idKey = `${timerKey}TimerId`;
|
||||
@@ -7232,12 +7157,6 @@ class bitvidApp {
|
||||
};
|
||||
|
||||
registerHandler("timeupdate", () => {
|
||||
if (
|
||||
!state.historyFired &&
|
||||
videoEl.currentTime >= WATCH_HISTORY_THRESHOLD_SECONDS
|
||||
) {
|
||||
finalizeWatchHistory();
|
||||
}
|
||||
if (!state.viewFired && videoEl.currentTime >= VIEW_THRESHOLD_SECONDS) {
|
||||
finalizeView();
|
||||
}
|
||||
@@ -7247,9 +7166,6 @@ class bitvidApp {
|
||||
if (!state.viewFired) {
|
||||
clearTimer("view");
|
||||
}
|
||||
if (!state.historyFired) {
|
||||
clearTimer("history");
|
||||
}
|
||||
};
|
||||
|
||||
["pause", "waiting", "stalled", "ended", "emptied"].forEach((event) =>
|
||||
@@ -7257,14 +7173,9 @@ class bitvidApp {
|
||||
);
|
||||
|
||||
const resumeIfNeeded = () => {
|
||||
if (state.viewFired && state.historyFired) {
|
||||
if (state.viewFired) {
|
||||
return;
|
||||
}
|
||||
scheduleTimer(
|
||||
"history",
|
||||
WATCH_HISTORY_THRESHOLD_SECONDS,
|
||||
finalizeWatchHistory
|
||||
);
|
||||
scheduleTimer("view", VIEW_THRESHOLD_SECONDS, finalizeView);
|
||||
};
|
||||
|
||||
|
||||
+71
-1523
File diff suppressed because it is too large.
Load diff
-41
@@ -2,45 +2,6 @@
|
||||
|
||||
import "./bufferPolyfill.js";
|
||||
import { trackPageView } from "./analytics.js";
|
||||
import { nostrClient } from "./nostr.js";
|
||||
|
||||
const HISTORY_PREFETCH_DEBOUNCE_MS = 250;
|
||||
|
||||
function prefetchWatchHistory(options = {}) {
|
||||
if (typeof nostrClient?.fetchWatchHistory !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
const force = options.force === true;
|
||||
const now = Date.now();
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
const last =
|
||||
typeof window.__bitvidLastHistoryPrefetch === "number"
|
||||
? window.__bitvidLastHistoryPrefetch
|
||||
: 0;
|
||||
|
||||
if (!force && now - last < HISTORY_PREFETCH_DEBOUNCE_MS) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.__bitvidLastHistoryPrefetch = now;
|
||||
}
|
||||
|
||||
let actor;
|
||||
if (typeof window?.app?.pubkey === "string" && window.app.pubkey.trim()) {
|
||||
actor = window.app.pubkey.trim();
|
||||
}
|
||||
|
||||
void nostrClient
|
||||
.fetchWatchHistory(actor, { forceRefresh: true })
|
||||
.catch((error) => {
|
||||
console.warn(
|
||||
"[index] Failed to prefetch watch history during navigation:",
|
||||
error
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const INTERFACE_FADE_IN_ANIMATION = "interface-fade-in";
|
||||
const VIDEO_THUMBNAIL_FADE_IN_ANIMATION = "video-thumbnail-fade-in";
|
||||
@@ -298,7 +259,6 @@ export function setHashView(viewName) {
|
||||
window.history.replaceState({}, "", newUrl);
|
||||
|
||||
if (typeof viewName === "string" && viewName.toLowerCase() === "history") {
|
||||
prefetchWatchHistory({ force: true });
|
||||
}
|
||||
|
||||
handleHashChange();
|
||||
@@ -448,7 +408,6 @@ async function handleHashChange() {
|
||||
|
||||
const viewName = match[1]; // only the chunk before any '&'
|
||||
if (typeof viewName === "string" && viewName.toLowerCase() === "history") {
|
||||
prefetchWatchHistory();
|
||||
}
|
||||
const viewUrl = `views/${viewName}.html`;
|
||||
|
||||
|
||||
+30
-3364
File diff suppressed because it is too large.
Load diff
@@ -530,7 +530,6 @@ async function testRecordVideoViewEmitsJsonPayload() {
|
||||
const originalEnsureSessionActor = nostrClient.ensureSessionActor;
|
||||
const originalSessionActor = nostrClient.sessionActor;
|
||||
const originalPubkey = nostrClient.pubkey;
|
||||
const originalUpdateHistory = nostrClient.updateWatchHistoryList;
|
||||
const originalWarn = console.warn;
|
||||
const originalNostrTools = window.NostrTools;
|
||||
|
||||
@@ -582,11 +581,6 @@ async function testRecordVideoViewEmitsJsonPayload() {
|
||||
};
|
||||
nostrClient.pubkey = "";
|
||||
|
||||
nostrClient.updateWatchHistoryList = async () => {
|
||||
throw new Error(
|
||||
"recordVideoView should not invoke updateWatchHistoryList during view publishes"
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await nostrClient.recordVideoView(pointer, {
|
||||
@@ -633,7 +627,6 @@ async function testRecordVideoViewEmitsJsonPayload() {
|
||||
nostrClient.ensureSessionActor = originalEnsureSessionActor;
|
||||
nostrClient.sessionActor = originalSessionActor;
|
||||
nostrClient.pubkey = originalPubkey;
|
||||
nostrClient.updateWatchHistoryList = originalUpdateHistory;
|
||||
console.warn = originalWarn;
|
||||
window.NostrTools = originalNostrTools;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user