mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
View counts inflated. viewCounter.js dedupes locally by (viewer, time-window) correctly, but the published events couldn't be deduped by relays: generateViewEventDedupeTag built the kind-30079 `d` tag with random entropy + exact timestamp, so every view was a UNIQUE event. And exactCountForPointer runs a NIP-45 COUNT (raw event count, no dedupe) and bumped the displayed total up to it (max(deduped, raw)). Net: reloads / multi-device / re-watches each added a view. Make the `d` tag deterministic and window-bucketed (scope:viewer:bucket, no entropy) so kind-30079 view events are genuinely parameterized-replaceable: relays keep one event per (viewer, video, window), so the cheap NIP-45 COUNT becomes accurate AND scalable and the raw count stops inflating. buildViewEvent no longer invents a random `d` tag (the only production caller, publishViewEvent, always supplies the deterministic one). Pre-existing entropy events age out over the backfill horizon. Tests: tests/view-event-dedupe.test.mjs. Co-Authored-By: Claude Opus 4.8 <[email protected]>
75 lines
2.9 KiB
JavaScript
75 lines
2.9 KiB
JavaScript
// Item #4 (view-count accuracy): a view event's `d` tag must be DETERMINISTIC and
|
|
// window-bucketed so kind-30079 view events are parameterized-replaceable —
|
|
// relays then collapse a viewer's repeat views in the same window into one event,
|
|
// making both the NIP-45 COUNT and a list() accurate and scalable. The old
|
|
// entropy+timestamp tag made every view unique and inflated counts.
|
|
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { buildViewEvent } from "../js/nostrEventSchemas.js";
|
|
import { __testExports } from "../js/nostr/viewEvents.js";
|
|
import { VIEW_COUNT_DEDUPE_WINDOW_SECONDS } from "../js/config.js";
|
|
|
|
const { generateViewEventDedupeTag } = __testExports;
|
|
|
|
const VIEWER = "f".repeat(64);
|
|
const OTHER = "0".repeat(64);
|
|
const VIDEO = { type: "e", value: "video-abc" };
|
|
const VIDEO2 = { type: "e", value: "video-xyz" };
|
|
const W = VIEW_COUNT_DEDUPE_WINDOW_SECONDS;
|
|
// A timestamp comfortably inside one window bucket, and another in the same one.
|
|
const T0 = 100 * W + 10;
|
|
const T0_SAME = 100 * W + (W - 1); // same bucket as T0
|
|
const T_NEXT = 101 * W + 5; // next bucket
|
|
|
|
test("same viewer + video within one window → identical d-tag (replaceable)", () => {
|
|
const a = generateViewEventDedupeTag(VIEWER, VIDEO, T0);
|
|
const b = generateViewEventDedupeTag(VIEWER, VIDEO, T0_SAME);
|
|
assert.equal(a, b, "repeat views in the same window must dedupe to one event");
|
|
});
|
|
|
|
test("d-tag has no entropy: stable across calls", () => {
|
|
const a = generateViewEventDedupeTag(VIEWER, VIDEO, T0);
|
|
const b = generateViewEventDedupeTag(VIEWER, VIDEO, T0);
|
|
assert.equal(a, b);
|
|
});
|
|
|
|
test("next window → different d-tag (counts as a new view)", () => {
|
|
const a = generateViewEventDedupeTag(VIEWER, VIDEO, T0);
|
|
const c = generateViewEventDedupeTag(VIEWER, VIDEO, T_NEXT);
|
|
assert.notEqual(a, c);
|
|
});
|
|
|
|
test("different viewer or different video → different d-tag", () => {
|
|
const base = generateViewEventDedupeTag(VIEWER, VIDEO, T0);
|
|
assert.notEqual(base, generateViewEventDedupeTag(OTHER, VIDEO, T0));
|
|
assert.notEqual(base, generateViewEventDedupeTag(VIEWER, VIDEO2, T0));
|
|
});
|
|
|
|
test("buildViewEvent does NOT invent a random d-tag when none is supplied", () => {
|
|
const event = buildViewEvent({
|
|
pubkey: VIEWER,
|
|
created_at: T0,
|
|
pointerValue: VIDEO.value,
|
|
pointerTag: ["e", VIDEO.value],
|
|
});
|
|
const dTags = event.tags.filter((t) => Array.isArray(t) && t[0] === "d");
|
|
assert.equal(dTags.length, 0, "no d tag should be auto-generated (no entropy)");
|
|
});
|
|
|
|
test("buildViewEvent uses the exact deterministic d-tag it is given", () => {
|
|
const dedupeTag = generateViewEventDedupeTag(VIEWER, VIDEO, T0);
|
|
const event = buildViewEvent({
|
|
pubkey: VIEWER,
|
|
created_at: T0,
|
|
pointerValue: VIDEO.value,
|
|
pointerTag: ["e", VIDEO.value],
|
|
dedupeTag,
|
|
});
|
|
assert.ok(
|
|
event.tags.some((t) => Array.isArray(t) && t[0] === "d" && t[1] === dedupeTag),
|
|
"the supplied deterministic d-tag must be on the event",
|
|
);
|
|
});
|