Limit view count fallback window

This commit is contained in:
thePR0M3TH3AN committed 2025-10-01 17:37:53 -04:00
1 parent eaa81f56ab
commit d1a9b8c79d
2 files changed
+197 -5

No files matched your search

+46 -5
View File
@@ -11,6 +11,7 @@ import {
WATCH_HISTORY_FETCH_EVENT_LIMIT,
WATCH_HISTORY_CACHE_TTL_MS,
VIEW_COUNT_DEDUPE_WINDOW_SECONDS,
VIEW_COUNT_BACKFILL_MAX_DAYS,
} from "./config.js";
import {
ACCEPT_LEGACY_V1,
@@ -3052,8 +3053,51 @@ class NostrClient {
}
async countVideoViewEvents(pointer, options = {}) {
const relayList =
Array.isArray(options?.relays) && options.relays.length
? options.relays
: undefined;
const fallbackListOptions = (() => {
const listOptions = {};
if (relayList) {
listOptions.relays = relayList;
}
if (Number.isFinite(options?.since)) {
listOptions.since = Math.floor(options.since);
} else {
const horizonDaysRaw = Number(VIEW_COUNT_BACKFILL_MAX_DAYS);
const horizonDays = Number.isFinite(horizonDaysRaw)
? Math.max(0, Math.floor(horizonDaysRaw))
: 0;
if (horizonDays > 0) {
const secondsPerDay = 86_400;
const nowSeconds = Math.floor(Date.now() / 1000);
const fallbackSinceSeconds = Math.max(
0,
nowSeconds - horizonDays * secondsPerDay
);
listOptions.since = Math.floor(fallbackSinceSeconds);
}
}
if (Number.isFinite(options?.until)) {
listOptions.until = Math.floor(options.until);
}
if (Number.isFinite(options?.limit) && options.limit > 0) {
listOptions.limit = Math.floor(options.limit);
}
return listOptions;
})();
if (!this.pool) {
const events = await this.listVideoViewEvents(pointer, options);
const events = await this.listVideoViewEvents(pointer, {
...fallbackListOptions,
});
return {
total: Array.isArray(events) ? events.length : 0,
perRelay: [],
@@ -3067,9 +3111,6 @@ class NostrClient {
if (!pointerFilter || typeof pointerFilter !== "object") {
throw new Error("Invalid video pointer supplied for view lookup.");
}
const relayList = Array.isArray(options?.relays) && options.relays.length
? options.relays
: undefined;
const signal = options?.signal;
const normalizeAbortError = () => {
@@ -3122,7 +3163,7 @@ class NostrClient {
: null;
const listPromise = this.listVideoViewEvents(pointer, {
relays: relayList,
...fallbackListOptions,
});
const events = abortPromise
+151
View File
@@ -0,0 +1,151 @@
// Run with: node tests/nostr-count-fallback.test.mjs
import "./test-helpers/setup-localstorage.mjs";
import assert from "node:assert/strict";
if (typeof globalThis.window === "undefined") {
globalThis.window = {};
}
if (!globalThis.window.NostrTools) {
globalThis.window.NostrTools = {};
}
const { VIEW_COUNT_BACKFILL_MAX_DAYS } = await import("../js/config.js");
const { nostrClient } = await import("../js/nostr.js");
const NostrClient = nostrClient.constructor;
async function withFrozenTime(timestampSeconds, callback) {
const originalNow = Date.now;
Date.now = () => timestampSeconds * 1000;
try {
return await callback();
} finally {
Date.now = originalNow;
}
}
function getConfiguredHorizonDays() {
const horizonDaysRaw = Number(VIEW_COUNT_BACKFILL_MAX_DAYS);
return Number.isFinite(horizonDaysRaw)
? Math.max(0, Math.floor(horizonDaysRaw))
: 0;
}
async function testFallbackAddsDefaultSinceWhenPoolMissing() {
const client = new NostrClient();
client.pool = null;
const pointer = { type: "e", value: "count-fallback-default" };
const calls = [];
client.listVideoViewEvents = async (_, options = {}) => {
calls.push({ ...options });
return [];
};
const horizonDays = getConfiguredHorizonDays();
const nowSeconds = 1_700_000_000;
await withFrozenTime(nowSeconds, async () => {
const result = await client.countVideoViewEvents(pointer, {});
assert.equal(calls.length, 1, "fallback should issue a single list query");
const [{ since }] = calls;
if (horizonDays > 0) {
const expectedSince = Math.max(0, nowSeconds - horizonDays * 86_400);
assert.equal(
since,
expectedSince,
"fallback list should clamp history to the configured horizon"
);
} else {
assert.equal(
Object.prototype.hasOwnProperty.call(calls[0], "since"),
false,
"without a configured horizon the fallback should avoid forcing a since filter"
);
}
assert.equal(result.fallback, true, "fallback branch should be flagged");
});
}
async function testFallbackHonorsCallerOverrides() {
const client = new NostrClient();
client.pool = null;
const pointer = { type: "e", value: "count-fallback-overrides" };
let receivedOptions = null;
client.listVideoViewEvents = async (_, options = {}) => {
receivedOptions = { ...options };
return [];
};
const overrides = {
since: 1_234_567,
until: 1_235_000,
limit: 42,
};
await client.countVideoViewEvents(pointer, overrides);
assert.ok(receivedOptions, "fallback should forward overrides to list");
assert.equal(
receivedOptions.since,
Math.floor(overrides.since),
"caller supplied since should be preserved"
);
assert.equal(
receivedOptions.until,
Math.floor(overrides.until),
"caller supplied until should be preserved"
);
assert.equal(
receivedOptions.limit,
Math.floor(overrides.limit),
"caller supplied limit should be preserved"
);
}
async function testFallbackAfterCountFailureUsesDefaultWindow() {
const client = new NostrClient();
client.pool = {};
client.countEventsAcrossRelays = async () => {
throw new Error("count failed");
};
const pointer = { type: "e", value: "count-fallback-error" };
let receivedOptions = null;
client.listVideoViewEvents = async (_, options = {}) => {
receivedOptions = { ...options };
return [];
};
const horizonDays = getConfiguredHorizonDays();
const nowSeconds = 1_800_000_000;
await withFrozenTime(nowSeconds, async () => {
await client.countVideoViewEvents(pointer, {});
});
assert.ok(receivedOptions, "fallback should invoke the list helper");
if (horizonDays > 0) {
const expectedSince = Math.max(0, nowSeconds - horizonDays * 86_400);
assert.equal(
receivedOptions.since,
expectedSince,
"COUNT fallback should restrict scans to the configured horizon"
);
} else {
assert.equal(
Object.prototype.hasOwnProperty.call(receivedOptions, "since"),
false,
"COUNT fallback should avoid forcing a since filter when no horizon is set"
);
}
}
await testFallbackAddsDefaultSinceWhenPoolMissing();
await testFallbackHonorsCallerOverrides();
await testFallbackAfterCountFailureUsesDefaultWindow();
console.log("nostr COUNT fallback tests completed successfully.");