mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-05 12:22:28 +00:00
Merge pull request #260 from PR0M3TH3AN/codex/refactor-watch-history-batching-logic
Clarify watch history batching window
This commit is contained in:
6 files changed
+100
-6
No files matched your search
@@ -106,6 +106,17 @@ export const WATCH_HISTORY_MAX_ITEMS = 1500;
|
||||
*/
|
||||
export const WATCH_HISTORY_BATCH_RESOLVE = true;
|
||||
|
||||
/**
|
||||
* Maximum number of canonical items each batched watch-history response should
|
||||
* include.
|
||||
*
|
||||
* Leave this as `null` (the default) to return the full
|
||||
* `WATCH_HISTORY_MAX_ITEMS` window, even when batching is enabled. Deployments
|
||||
* that page through histories in smaller slices can set a positive integer to
|
||||
* cap the resolver output so pagination and API responses stay aligned.
|
||||
*/
|
||||
export const WATCH_HISTORY_BATCH_PAGE_SIZE = null;
|
||||
|
||||
/**
|
||||
* Maximum size of the JSON payload for each watch-history chunk, measured
|
||||
* before encryption.
|
||||
|
||||
@@ -27,6 +27,15 @@ 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】
|
||||
|
||||
## Batching configuration
|
||||
|
||||
Operators can toggle grouped resolution with `WATCH_HISTORY_BATCH_RESOLVE` and
|
||||
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】
|
||||
|
||||
## Snapshot cadence
|
||||
|
||||
`WatchHistoryService.publishView` queues pointer entries while the feature flag
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
WATCH_HISTORY_LEGACY_LIST_IDENTIFIERS,
|
||||
WATCH_HISTORY_MAX_ITEMS,
|
||||
WATCH_HISTORY_BATCH_RESOLVE,
|
||||
WATCH_HISTORY_BATCH_PAGE_SIZE,
|
||||
WATCH_HISTORY_PAYLOAD_MAX_BYTES,
|
||||
WATCH_HISTORY_FETCH_EVENT_LIMIT,
|
||||
WATCH_HISTORY_CACHE_TTL_MS,
|
||||
@@ -63,6 +64,7 @@ export { WATCH_HISTORY_LIST_IDENTIFIER };
|
||||
export { WATCH_HISTORY_LEGACY_LIST_IDENTIFIERS };
|
||||
export { WATCH_HISTORY_MAX_ITEMS };
|
||||
export { WATCH_HISTORY_BATCH_RESOLVE };
|
||||
export { WATCH_HISTORY_BATCH_PAGE_SIZE };
|
||||
export { WATCH_HISTORY_PAYLOAD_MAX_BYTES };
|
||||
export { WATCH_HISTORY_FETCH_EVENT_LIMIT };
|
||||
export { WATCH_HISTORY_CACHE_TTL_MS };
|
||||
|
||||
+12
-2
@@ -7,7 +7,10 @@ import {
|
||||
nostrClient,
|
||||
updateWatchHistoryList,
|
||||
} from "./nostr.js";
|
||||
import { WATCH_HISTORY_BATCH_RESOLVE } from "./config.js";
|
||||
import {
|
||||
WATCH_HISTORY_BATCH_RESOLVE,
|
||||
WATCH_HISTORY_BATCH_PAGE_SIZE,
|
||||
} from "./config.js";
|
||||
|
||||
export const WATCH_HISTORY_EMPTY_COPY =
|
||||
"Your watch history is empty. Watch some videos to populate this list.";
|
||||
@@ -19,7 +22,14 @@ const WATCH_HISTORY_METADATA_PREF_KEY =
|
||||
"bitvid:watch-history:metadata-preference";
|
||||
const WATCH_HISTORY_PRIVACY_DISMISSED_KEY =
|
||||
"bitvid:watch-history:privacy-banner-dismissed";
|
||||
const WATCH_HISTORY_BATCH_SIZE = 12;
|
||||
const DEFAULT_WATCH_HISTORY_BATCH_SIZE = 12;
|
||||
const WATCH_HISTORY_BATCH_SIZE = (() => {
|
||||
const raw = Number(WATCH_HISTORY_BATCH_PAGE_SIZE);
|
||||
if (!Number.isFinite(raw) || raw <= 0) {
|
||||
return DEFAULT_WATCH_HISTORY_BATCH_SIZE;
|
||||
}
|
||||
return Math.floor(raw);
|
||||
})();
|
||||
const FALLBACK_THUMBNAIL = "assets/svg/default-thumbnail.svg";
|
||||
const FALLBACK_AVATAR = "assets/svg/default-profile.svg";
|
||||
const isDevEnv =
|
||||
|
||||
+7
-3
@@ -9,6 +9,7 @@ import {
|
||||
WATCH_HISTORY_LEGACY_LIST_IDENTIFIERS,
|
||||
WATCH_HISTORY_MAX_ITEMS,
|
||||
WATCH_HISTORY_BATCH_RESOLVE,
|
||||
WATCH_HISTORY_BATCH_PAGE_SIZE,
|
||||
WATCH_HISTORY_PAYLOAD_MAX_BYTES,
|
||||
WATCH_HISTORY_FETCH_EVENT_LIMIT,
|
||||
WATCH_HISTORY_CACHE_TTL_MS,
|
||||
@@ -2986,9 +2987,12 @@ class NostrClient {
|
||||
fallbackItems,
|
||||
);
|
||||
|
||||
const batchLimitRaw = Number(WATCH_HISTORY_BATCH_RESOLVE);
|
||||
const batchLimit = Number.isFinite(batchLimitRaw) && batchLimitRaw > 0
|
||||
? Math.min(Math.floor(batchLimitRaw), WATCH_HISTORY_MAX_ITEMS)
|
||||
const shouldBatch = Boolean(WATCH_HISTORY_BATCH_RESOLVE);
|
||||
const batchPageSizeRaw = Number(WATCH_HISTORY_BATCH_PAGE_SIZE);
|
||||
const hasCustomBatchSize =
|
||||
Number.isFinite(batchPageSizeRaw) && batchPageSizeRaw > 0;
|
||||
const batchLimit = shouldBatch && hasCustomBatchSize
|
||||
? Math.min(Math.floor(batchPageSizeRaw), WATCH_HISTORY_MAX_ITEMS)
|
||||
: WATCH_HISTORY_MAX_ITEMS;
|
||||
|
||||
const canonicalItems = canonicalizeWatchHistoryItems(
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
import "./test-helpers/setup-localstorage.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { WATCH_HISTORY_PAYLOAD_MAX_BYTES } = await import("../js/config.js");
|
||||
const { WATCH_HISTORY_PAYLOAD_MAX_BYTES, WATCH_HISTORY_MAX_ITEMS } =
|
||||
await import("../js/config.js");
|
||||
const {
|
||||
getWatchHistoryV2Enabled,
|
||||
setWatchHistoryV2Enabled,
|
||||
@@ -604,6 +605,62 @@ async function testPublishSnapshotFailureRetry() {
|
||||
}
|
||||
}
|
||||
|
||||
async function testResolveWatchHistoryBatchingWindow() {
|
||||
const actor = "batch-window-actor";
|
||||
const originalEnsure = nostrClient.ensureSessionActor;
|
||||
const originalPub = nostrClient.pubkey;
|
||||
const originalFetch = nostrClient.fetchWatchHistory;
|
||||
const originalCache = nostrClient.watchHistoryCache;
|
||||
const originalStorage = nostrClient.watchHistoryStorage;
|
||||
|
||||
try {
|
||||
nostrClient.pubkey = "";
|
||||
nostrClient.ensureSessionActor = async () => actor;
|
||||
nostrClient.watchHistoryCache = new Map();
|
||||
nostrClient.watchHistoryStorage = { version: 2, actors: {} };
|
||||
|
||||
const syntheticItems = Array.from({ length: 24 }, (_, index) => ({
|
||||
type: "e",
|
||||
value: `pointer-${index}`,
|
||||
watchedAt: 1_700_100_000 + index,
|
||||
}));
|
||||
|
||||
nostrClient.fetchWatchHistory = async () => ({
|
||||
items: syntheticItems.map((item) => ({ ...item })),
|
||||
snapshotId: "batch-snapshot",
|
||||
pointerEvent: null,
|
||||
});
|
||||
|
||||
const resolved = await nostrClient.resolveWatchHistory(actor, {
|
||||
forceRefresh: true,
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
resolved.length,
|
||||
Math.min(WATCH_HISTORY_MAX_ITEMS, syntheticItems.length),
|
||||
"batched resolve should return the full window when no page size override is configured",
|
||||
);
|
||||
assert(resolved.length > 1, "resolved history should include multiple entries");
|
||||
assert.equal(
|
||||
resolved[0]?.value,
|
||||
"pointer-23",
|
||||
"resolved items should remain sorted by newest watchedAt value",
|
||||
);
|
||||
const uniqueValues = new Set(resolved.map((item) => item?.value));
|
||||
assert.equal(
|
||||
uniqueValues.size,
|
||||
resolved.length,
|
||||
"resolved history should retain all unique pointers",
|
||||
);
|
||||
} finally {
|
||||
nostrClient.ensureSessionActor = originalEnsure;
|
||||
nostrClient.pubkey = originalPub;
|
||||
nostrClient.fetchWatchHistory = originalFetch;
|
||||
nostrClient.watchHistoryCache = originalCache;
|
||||
nostrClient.watchHistoryStorage = originalStorage;
|
||||
}
|
||||
}
|
||||
|
||||
async function testWatchHistoryServiceIntegration() {
|
||||
poolHarness.reset();
|
||||
poolHarness.setResolver(() => ({ ok: true }));
|
||||
@@ -719,6 +776,7 @@ async function testWatchHistoryServiceIntegration() {
|
||||
await testPublishSnapshotCanonicalizationAndChunking();
|
||||
await testPublishSnapshotUsesExtensionCrypto();
|
||||
await testPublishSnapshotFailureRetry();
|
||||
await testResolveWatchHistoryBatchingWindow();
|
||||
await testWatchHistoryServiceIntegration();
|
||||
|
||||
console.log("watch-history.test.mjs completed successfully");
|
||||
|
||||
Reference in new issue
Block a user