mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-08 05:43:04 +00:00
fix(watch-history): resolve freshly-watched videos from cache (default video kind in address lookup)
Watch-history cards rendered "Untitled video" / "Unknown" for newly watched videos. nostrClient.allEvents stores CONVERTED video objects (convertEventToVideo), which carry tags/pubkey but no `kind` field — only raw relay events do. resolveEventAddress() did Number(event.kind) → NaN and bailed with "", so the address (cache-by-"a"-pointer) scan matched nothing. Every video pointer (all are "a" addresses) was forced to a relay round-trip, and when that returned nothing the card showed Unknown — even though the just-watched video was sitting right there in allEvents. Fix: default to the video kind (30078) when a cached object has no `kind`, so the local cache scan resolves freshly-watched "a" pointers without a relay round-trip. Adds tests/watch-history-hydration-cache.test.mjs: relays return nothing, so a pass can only come from the cache scan (mutation-verified against the NaN-reject regression). Exports createWatchHistoryHydrationStage for boundary testing. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
48b6f10e0d
commit
ebacf223c8
2 files changed
+101
-3
No files matched your search
@@ -2,6 +2,7 @@
|
||||
|
||||
import { pointerKey, normalizePointerInput } from "../nostr/watchHistory.js";
|
||||
import { convertEventToVideo } from "../nostr/index.js";
|
||||
import { DEFAULT_VIDEO_KIND } from "../utils/videoPointer.js";
|
||||
import watchHistoryService from "../watchHistoryService.js";
|
||||
import nostrService from "../services/nostrService.js";
|
||||
import { nostrClient } from "../nostrClientFacade.js";
|
||||
@@ -237,9 +238,16 @@ function resolveEventAddress(event) {
|
||||
if (!event || typeof event !== "object") {
|
||||
return "";
|
||||
}
|
||||
const kind = Number(event.kind);
|
||||
// Converted video objects (convertEventToVideo) carry tags/pubkey but NOT a
|
||||
// `kind` field — only raw relay events do. Watch-history pointers are always
|
||||
// video-kind addresses, so default to the video kind when it's absent.
|
||||
// Without this, the cache-by-address scan produced "" for every cached video
|
||||
// and never matched, forcing a relay round-trip that left freshly-watched
|
||||
// videos (already sitting in allEvents) showing "Unknown".
|
||||
const rawKind = Number(event.kind);
|
||||
const kind = Number.isFinite(rawKind) ? rawKind : DEFAULT_VIDEO_KIND;
|
||||
const pubkey = typeof event.pubkey === "string" ? event.pubkey.toLowerCase() : "";
|
||||
if (!Number.isFinite(kind) || !pubkey) {
|
||||
if (!pubkey) {
|
||||
return "";
|
||||
}
|
||||
const dTag = event.tags?.find((t) => t[0] === "d" && t[1]);
|
||||
@@ -266,7 +274,7 @@ function checkLocalCacheForVideo(pointer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function createWatchHistoryHydrationStage() {
|
||||
export function createWatchHistoryHydrationStage() {
|
||||
return async function watchHistoryHydrationStage(items = [], context = {}) {
|
||||
debugInfo(`Starting hydration stage for ${items.length} items.`);
|
||||
const missing = [];
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// A freshly-watched video is already in nostrClient.allEvents (you just played
|
||||
// it). The watch-history feed stores an "a" (address) pointer for it and must
|
||||
// resolve the title/thumbnail from that local cache WITHOUT a relay round-trip.
|
||||
//
|
||||
// Bug: allEvents holds *converted* video objects (convertEventToVideo), which
|
||||
// carry tags/pubkey but NO `kind` field. resolveEventAddress did Number(kind) →
|
||||
// NaN → returned "" for every cached video, so the address cache-scan never
|
||||
// matched. Every video pointer fell through to a relay fetch, and when that came
|
||||
// up empty the card rendered "Untitled video" / "Unknown".
|
||||
//
|
||||
// Correct behavior: the cache-scan resolves an "a" pointer to the cached video
|
||||
// using the video kind by default, so freshly-watched videos show their real
|
||||
// metadata immediately — even when relays return nothing.
|
||||
|
||||
import "./test-helpers/setup-localstorage.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { createWatchHistoryHydrationStage } from "../js/feedEngine/watchHistoryFeed.js";
|
||||
import { nostrClient } from "../js/nostrClientFacade.js";
|
||||
|
||||
const PUBKEY = "c".repeat(64);
|
||||
const DTAG = "my-video-root-id";
|
||||
const ADDRESS = `30078:${PUBKEY}:${DTAG}`;
|
||||
|
||||
// Mirrors convertEventToVideo output: tags + pubkey, but NO `kind`.
|
||||
function cachedVideoWithoutKind() {
|
||||
return {
|
||||
id: "e".repeat(64),
|
||||
pubkey: PUBKEY,
|
||||
title: "My Real Video Title",
|
||||
thumbnail: "https://example.com/thumb.jpg",
|
||||
tags: [["d", DTAG]],
|
||||
created_at: 1750000000,
|
||||
invalid: false,
|
||||
};
|
||||
}
|
||||
|
||||
test("an 'a'-pointer resolves from the local cache (no relay needed) for a converted video lacking `kind`", async () => {
|
||||
const prevAllEvents = nostrClient.allEvents;
|
||||
const prevPool = nostrClient.pool;
|
||||
const prevRead = nostrClient.readRelays;
|
||||
|
||||
nostrClient.allEvents = new Map([
|
||||
[cachedVideoWithoutKind().id, cachedVideoWithoutKind()],
|
||||
]);
|
||||
// Relays return nothing — so a pass can ONLY come from the cache scan.
|
||||
let relayHits = 0;
|
||||
nostrClient.pool = {
|
||||
list: async () => {
|
||||
relayHits += 1;
|
||||
return [];
|
||||
},
|
||||
};
|
||||
nostrClient.readRelays = ["wss://relay.example"];
|
||||
|
||||
try {
|
||||
const stage = createWatchHistoryHydrationStage();
|
||||
const items = [
|
||||
{
|
||||
pointer: { type: "a", value: ADDRESS },
|
||||
pointerKey: `a:${ADDRESS}`,
|
||||
watchedAt: 1750000001,
|
||||
video: null,
|
||||
metadata: {},
|
||||
},
|
||||
];
|
||||
|
||||
const result = await stage(items, {});
|
||||
|
||||
assert.equal(result.length, 1);
|
||||
assert.ok(
|
||||
result[0].video,
|
||||
"freshly-watched video must resolve from the local cache",
|
||||
);
|
||||
assert.equal(
|
||||
result[0].video.title,
|
||||
"My Real Video Title",
|
||||
"resolved video must carry the real title (not Unknown)",
|
||||
);
|
||||
assert.equal(
|
||||
relayHits,
|
||||
0,
|
||||
"cache hit must avoid a relay round-trip entirely",
|
||||
);
|
||||
} finally {
|
||||
nostrClient.allEvents = prevAllEvents;
|
||||
nostrClient.pool = prevPool;
|
||||
nostrClient.readRelays = prevRead;
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user