mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-29 08:52:25 +00:00
Fix watch history snapshot merging
This commit is contained in:
1 parent
397b38d5b8
commit
7c9db2eeaf
2 files changed
+156
-12
No files matched your search
+53
-11
@@ -843,6 +843,42 @@ function collectQueueItems(actorKey) {
|
||||
return items.slice(0, WATCH_HISTORY_MAX_ITEMS);
|
||||
}
|
||||
|
||||
function getCachedSnapshotItems(actorKey) {
|
||||
if (!actorKey) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const fingerprintEntry = state.fingerprintCache.get(actorKey);
|
||||
if (Array.isArray(fingerprintEntry?.items) && fingerprintEntry.items.length) {
|
||||
return Array.from(fingerprintEntry.items);
|
||||
}
|
||||
|
||||
const clientCache =
|
||||
nostrClient &&
|
||||
nostrClient.watchHistoryCache instanceof Map &&
|
||||
nostrClient.watchHistoryCache.get(actorKey);
|
||||
if (Array.isArray(clientCache?.items) && clientCache.items.length) {
|
||||
return Array.from(clientCache.items);
|
||||
}
|
||||
|
||||
if (typeof nostrClient?.getWatchHistoryStorage === "function") {
|
||||
try {
|
||||
const storage = nostrClient.getWatchHistoryStorage();
|
||||
const storedItems = storage?.actors?.[actorKey]?.items;
|
||||
if (Array.isArray(storedItems) && storedItems.length) {
|
||||
return Array.from(storedItems);
|
||||
}
|
||||
} catch (error) {
|
||||
devLogger.warn(
|
||||
"[watchHistoryService] Failed to read cached watch history items:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function notifyQueueChange(actorKey) {
|
||||
emit("queue-changed", {
|
||||
actor: actorKey,
|
||||
@@ -1215,10 +1251,17 @@ async function snapshot(items, options = {}) {
|
||||
|
||||
const queue = ensureQueue(actorKey);
|
||||
|
||||
if (!items) {
|
||||
const cachedItems = getCachedSnapshotItems(actorKey);
|
||||
if (cachedItems.length) {
|
||||
payloadItems = [...payloadItems, ...cachedItems];
|
||||
}
|
||||
}
|
||||
|
||||
devLogger.info(
|
||||
"[watchHistoryService] Initiating watch list snapshot publish.",
|
||||
{
|
||||
actor: actorKey,
|
||||
actor: actorKey,
|
||||
reason,
|
||||
itemCount: payloadItems.length,
|
||||
queued: queue?.items?.size ?? 0,
|
||||
@@ -1303,16 +1346,15 @@ function scheduleWatchHistoryRefresh(actorKey, cacheEntry = {}) {
|
||||
return cacheEntry.promise;
|
||||
}
|
||||
|
||||
const promise = Promise.resolve()
|
||||
.then(async () => {
|
||||
const permissionResult = await ensureWatchHistoryExtensionPermissions(
|
||||
actorKey,
|
||||
);
|
||||
if (!permissionResult.ok) {
|
||||
throw permissionResult.error;
|
||||
}
|
||||
return nostrClient.resolveWatchHistory(actorKey, { forceRefresh: true });
|
||||
})
|
||||
const promise = (async () => {
|
||||
const permissionResult = await ensureWatchHistoryExtensionPermissions(
|
||||
actorKey,
|
||||
);
|
||||
if (!permissionResult.ok) {
|
||||
throw permissionResult.error;
|
||||
}
|
||||
return nostrClient.resolveWatchHistory(actorKey, { forceRefresh: true });
|
||||
})()
|
||||
.then((resolvedItems) => updateFingerprintCache(actorKey, resolvedItems))
|
||||
.then(() => {
|
||||
const latest = state.fingerprintCache.get(actorKey);
|
||||
|
||||
@@ -1282,7 +1282,7 @@ async function testWatchHistoryPartialRelayRetry() {
|
||||
nostrClient.sessionActor = originalSession;
|
||||
nostrClient.pubkey = originalPub;
|
||||
poolHarness.setResolver(() => ({ ok: true }));
|
||||
watchHistoryService.resetProgress(actor);
|
||||
watchHistoryService.resetProgress();
|
||||
restoreCrypto.restore();
|
||||
}
|
||||
}
|
||||
@@ -1533,6 +1533,107 @@ async function testWatchHistoryServiceIntegration() {
|
||||
}
|
||||
}
|
||||
|
||||
async function testWatchHistorySnapshotMergesQueuedWithCachedItems() {
|
||||
console.log("Running watch history snapshot merge test...");
|
||||
|
||||
const actor = "npub-snapshot-merge";
|
||||
const originalPublishSnapshot = nostrClient.publishWatchHistorySnapshot;
|
||||
const originalRecordView = nostrClient.recordVideoView;
|
||||
const originalEnsure = nostrClient.ensureSessionActor;
|
||||
const originalSession = nostrClient.sessionActor;
|
||||
const originalPub = nostrClient.pubkey;
|
||||
const originalFingerprint = nostrClient.getWatchHistoryFingerprint;
|
||||
|
||||
try {
|
||||
nostrClient.pubkey = actor;
|
||||
nostrClient.sessionActor = { pubkey: actor, privateKey: "merge-priv" };
|
||||
nostrClient.ensureSessionActor = async () => actor;
|
||||
nostrClient.recordVideoView = async (_pointer, options = {}) => ({
|
||||
ok: true,
|
||||
event: {
|
||||
id: `view-${options.created_at || Date.now()}`,
|
||||
pubkey: actor,
|
||||
created_at: options.created_at || Math.floor(Date.now() / 1000),
|
||||
},
|
||||
});
|
||||
|
||||
const publishedPayloads = [];
|
||||
nostrClient.publishWatchHistorySnapshot = async (items, options = {}) => {
|
||||
const clonedItems = Array.isArray(items)
|
||||
? items.map((entry) => ({ ...entry }))
|
||||
: [];
|
||||
publishedPayloads.push({
|
||||
items: clonedItems,
|
||||
options: { ...options },
|
||||
});
|
||||
return {
|
||||
ok: true,
|
||||
snapshotId: `snapshot-${publishedPayloads.length}`,
|
||||
items: clonedItems,
|
||||
publishResults: {},
|
||||
};
|
||||
};
|
||||
|
||||
nostrClient.getWatchHistoryFingerprint = async (_actor, items = []) =>
|
||||
`fingerprint-${Array.isArray(items) ? items.length : 0}-${Date.now()}`;
|
||||
|
||||
watchHistoryService.resetProgress(actor);
|
||||
|
||||
const seedItems = [
|
||||
{ type: "e", value: "seed-one", watchedAt: 1_700_000_000 },
|
||||
{ type: "e", value: "seed-two", watchedAt: 1_700_000_100 },
|
||||
];
|
||||
|
||||
await watchHistoryService.snapshot(seedItems, {
|
||||
actor,
|
||||
reason: "seed-history",
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
publishedPayloads.length,
|
||||
1,
|
||||
"initial snapshot should publish seed items",
|
||||
);
|
||||
|
||||
await watchHistoryService.publishView(
|
||||
{ type: "e", value: "fresh-entry" },
|
||||
1_700_000_200,
|
||||
{ actor },
|
||||
);
|
||||
|
||||
const mergeResult = await watchHistoryService.snapshot(null, {
|
||||
actor,
|
||||
reason: "merge-queued-items",
|
||||
});
|
||||
assert.ok(mergeResult.ok, "merge snapshot should succeed");
|
||||
|
||||
assert.equal(
|
||||
publishedPayloads.length,
|
||||
2,
|
||||
"merge snapshot should trigger a second publish",
|
||||
);
|
||||
|
||||
const mergedCall = publishedPayloads[1];
|
||||
const mergedKeys = mergedCall.items.map(
|
||||
(entry) => `${entry?.type}:${entry?.value}`,
|
||||
);
|
||||
mergedKeys.sort();
|
||||
assert.deepEqual(
|
||||
mergedKeys,
|
||||
["e:fresh-entry", "e:seed-one", "e:seed-two"],
|
||||
"merged snapshot should include seed and queued pointers",
|
||||
);
|
||||
} finally {
|
||||
nostrClient.publishWatchHistorySnapshot = originalPublishSnapshot;
|
||||
nostrClient.recordVideoView = originalRecordView;
|
||||
nostrClient.ensureSessionActor = originalEnsure;
|
||||
nostrClient.sessionActor = originalSession;
|
||||
nostrClient.pubkey = originalPub;
|
||||
nostrClient.getWatchHistoryFingerprint = originalFingerprint;
|
||||
watchHistoryService.resetProgress();
|
||||
}
|
||||
}
|
||||
|
||||
async function testHistoryCardsUseDecryptedPlaybackMetadata() {
|
||||
console.log("Running watch history card playback metadata test...");
|
||||
|
||||
@@ -2047,6 +2148,7 @@ await testPublishSnapshotFailureRetry();
|
||||
await testWatchHistoryPartialRelayRetry();
|
||||
await testResolveWatchHistoryBatchingWindow();
|
||||
await testWatchHistoryServiceIntegration();
|
||||
await testWatchHistorySnapshotMergesQueuedWithCachedItems();
|
||||
await testHistoryCardsUseDecryptedPlaybackMetadata();
|
||||
await testWatchHistoryStaleCacheRefresh();
|
||||
await testWatchHistoryLocalFallbackWhenDisabled();
|
||||
|
||||
Reference in new issue
Block a user