Restore canonical watch history snapshot format

This commit is contained in:
thePR0M3TH3AN committed 2025-10-01 18:47:57 -04:00
1 parent af7738b4d8
commit ecd3f4026e
6 files changed
+122 -8

No files matched your search

+2 -2
View File
@@ -63,14 +63,14 @@ export const WATCH_HISTORY_KIND = 30078;
* BitVid deployments that share relays, but keep it stable once clients begin
* syncing history.
*/
export const WATCH_HISTORY_LIST_IDENTIFIER = "watch-history:v2:index";
export const WATCH_HISTORY_LIST_IDENTIFIER = "watch-history";
/**
* Legacy identifiers that BitVid clients should continue honoring when
* fetching historical watch-history snapshots.
*/
export const WATCH_HISTORY_LEGACY_LIST_IDENTIFIERS = Object.freeze([
"watch-history",
"watch-history:v2:index",
]);
/**
+4 -2
View File
@@ -64,10 +64,12 @@ 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:v2:index`). The
`#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.
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
+23
View File
@@ -4283,6 +4283,29 @@ 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);
});
});
+47
View File
@@ -2,6 +2,45 @@
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";
@@ -257,6 +296,11 @@ export function setHashView(viewName) {
url.searchParams.delete("v");
const newUrl = url.pathname + url.search + `#view=${viewName}`;
window.history.replaceState({}, "", newUrl);
if (typeof viewName === "string" && viewName.toLowerCase() === "history") {
prefetchWatchHistory({ force: true });
}
handleHashChange();
}
@@ -403,6 +447,9 @@ 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`;
// Now dynamically load that partial, then call its init function
+22 -1
View File
@@ -58,7 +58,28 @@ const WATCH_HISTORY_INDEX_IDENTIFIER_LOWER =
? WATCH_HISTORY_LIST_IDENTIFIER.trim().toLowerCase()
: "";
const WATCH_HISTORY_CHUNK_V2_PREFIX = "watch-history:v2";
const WATCH_HISTORY_CHUNK_V2_PREFIX = (() => {
const rawIdentifier =
typeof WATCH_HISTORY_LIST_IDENTIFIER === "string"
? WATCH_HISTORY_LIST_IDENTIFIER.trim()
: "";
if (rawIdentifier) {
const trimmed = rawIdentifier.replace(/\/+$/, "");
if (/[:/]index$/i.test(trimmed)) {
const base = trimmed.replace(/[:/]index$/i, "").replace(/\/+$/, "");
return base || "watch-history:v2";
}
if (/:v\d+$/i.test(trimmed)) {
return trimmed;
}
return `${trimmed}:v2`;
}
return "watch-history:v2";
})();
const LEGACY_WATCH_HISTORY_CHUNK_IDENTIFIER_PREFIX = (() => {
const rawIdentifier =
+24 -3
View File
@@ -109,9 +109,30 @@ function createDecryptClient(actorPubkey) {
}
const ACTOR = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
const WATCH_HISTORY_CHUNK_PREFIX = WATCH_HISTORY_LIST_IDENTIFIER.replace(
/[:/]index$/i,
""
function deriveChunkPrefix(identifier) {
if (typeof identifier !== "string") {
return "watch-history:v2";
}
const trimmed = identifier.trim().replace(/\/+$/, "");
if (!trimmed) {
return "watch-history:v2";
}
if (/[:/]index$/i.test(trimmed)) {
const base = trimmed.replace(/[:/]index$/i, "").replace(/\/+$/, "");
return base || "watch-history:v2";
}
if (/:v\d+$/i.test(trimmed)) {
return trimmed;
}
return `${trimmed}:v2`;
}
const WATCH_HISTORY_CHUNK_PREFIX = deriveChunkPrefix(
WATCH_HISTORY_LIST_IDENTIFIER
);
const indexSchema = getNostrEventSchema(NOTE_TYPES.WATCH_HISTORY_INDEX);