mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-03-13 14:29:07 +00:00
Adjust relay count aggregation for view totals
This commit is contained in:
50
js/nostr.js
50
js/nostr.js
@@ -2465,6 +2465,7 @@ class NostrClient {
|
||||
return {
|
||||
total: Array.isArray(events) ? events.length : 0,
|
||||
perRelay: [],
|
||||
best: null,
|
||||
fallback: true,
|
||||
};
|
||||
}
|
||||
@@ -2539,6 +2540,7 @@ class NostrClient {
|
||||
return {
|
||||
total: Array.isArray(events) ? events.length : 0,
|
||||
perRelay: [],
|
||||
best: null,
|
||||
fallback: true,
|
||||
};
|
||||
}
|
||||
@@ -4766,7 +4768,7 @@ class NostrClient {
|
||||
async countEventsAcrossRelays(filters, options = {}) {
|
||||
const normalizedFilters = this.normalizeCountFilters(filters);
|
||||
if (!normalizedFilters.length) {
|
||||
return { total: 0, perRelay: [] };
|
||||
return { total: 0, best: null, perRelay: [] };
|
||||
}
|
||||
|
||||
const relayList =
|
||||
@@ -4776,7 +4778,7 @@ class NostrClient {
|
||||
? this.relays
|
||||
: RELAY_URLS;
|
||||
|
||||
const perRelay = await Promise.all(
|
||||
const perRelayResults = await Promise.all(
|
||||
relayList.map(async (url) => {
|
||||
try {
|
||||
const frame = await this.sendRawCountFrame(url, normalizedFilters, {
|
||||
@@ -4793,15 +4795,47 @@ class NostrClient {
|
||||
})
|
||||
);
|
||||
|
||||
const total = perRelay.reduce((sum, entry) => {
|
||||
let bestEstimate = null;
|
||||
const perRelay = perRelayResults.map((entry) => {
|
||||
if (!entry || !entry.ok) {
|
||||
return sum;
|
||||
return entry;
|
||||
}
|
||||
const value = Number(entry.count);
|
||||
return Number.isFinite(value) && value > 0 ? sum + value : sum;
|
||||
}, 0);
|
||||
|
||||
return { total, perRelay };
|
||||
const numericValue = Number(entry.count);
|
||||
const normalizedCount =
|
||||
Number.isFinite(numericValue) && numericValue >= 0 ? numericValue : 0;
|
||||
|
||||
const normalizedEntry = {
|
||||
...entry,
|
||||
count: normalizedCount,
|
||||
};
|
||||
|
||||
if (!Number.isFinite(numericValue) || numericValue < 0) {
|
||||
normalizedEntry.rawCount = entry.count;
|
||||
}
|
||||
|
||||
if (
|
||||
!bestEstimate ||
|
||||
normalizedCount > bestEstimate.count ||
|
||||
(bestEstimate && normalizedCount === bestEstimate.count && !bestEstimate.frame)
|
||||
) {
|
||||
bestEstimate = {
|
||||
relay: normalizedEntry.url,
|
||||
count: normalizedCount,
|
||||
frame: normalizedEntry.frame,
|
||||
};
|
||||
}
|
||||
|
||||
return normalizedEntry;
|
||||
});
|
||||
|
||||
const total = bestEstimate ? bestEstimate.count : 0;
|
||||
|
||||
return {
|
||||
total,
|
||||
best: bestEstimate,
|
||||
perRelay,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -485,10 +485,18 @@ async function hydratePointer(key, listeners) {
|
||||
mutated = applyEventToState(key, event) || mutated;
|
||||
}
|
||||
}
|
||||
if (countResult && Number.isFinite(countResult.total)) {
|
||||
const bestCount = Number.isFinite(countResult?.best?.count)
|
||||
? Number(countResult.best.count)
|
||||
: Number.isFinite(countResult?.total)
|
||||
? Number(countResult.total)
|
||||
: null;
|
||||
|
||||
if (bestCount !== null) {
|
||||
const state = ensurePointerState(key);
|
||||
if (countResult.total > state.total) {
|
||||
state.total = Number(countResult.total);
|
||||
const shouldUpdate =
|
||||
!countResult?.fallback || bestCount >= state.total || state.total === 0;
|
||||
if (shouldUpdate && state.total !== bestCount) {
|
||||
state.total = bestCount;
|
||||
state.lastSyncedAt = Date.now();
|
||||
mutated = true;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ const { nostrClient } = await import("../js/nostr.js");
|
||||
|
||||
function createMockNostrHarness() {
|
||||
const storedEvents = new Map();
|
||||
const customTotals = new Map();
|
||||
const customCountResults = new Map();
|
||||
const subscribers = new Map();
|
||||
const metrics = { list: 0, count: 0 };
|
||||
|
||||
@@ -108,11 +108,37 @@ function createMockNostrHarness() {
|
||||
const countVideoViewEvents = async (pointer) => {
|
||||
metrics.count += 1;
|
||||
const key = pointerKeyFromInput(pointer);
|
||||
if (customTotals.has(key)) {
|
||||
return { total: customTotals.get(key) };
|
||||
if (customCountResults.has(key)) {
|
||||
const stored = customCountResults.get(key);
|
||||
if (stored && typeof stored === "object" && !Array.isArray(stored)) {
|
||||
const totalValue = Number(stored.total);
|
||||
const normalizedTotal =
|
||||
Number.isFinite(totalValue) && totalValue >= 0 ? totalValue : 0;
|
||||
const perRelay = Array.isArray(stored.perRelay)
|
||||
? stored.perRelay.map((entry) =>
|
||||
entry && typeof entry === "object" ? { ...entry } : entry
|
||||
)
|
||||
: [];
|
||||
const result = {
|
||||
total: normalizedTotal,
|
||||
perRelay,
|
||||
best:
|
||||
stored.best && typeof stored.best === "object"
|
||||
? { ...stored.best }
|
||||
: null,
|
||||
};
|
||||
if (stored.fallback) {
|
||||
result.fallback = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
const numericTotal = Number(stored);
|
||||
const normalizedTotal =
|
||||
Number.isFinite(numericTotal) && numericTotal >= 0 ? numericTotal : 0;
|
||||
return { total: normalizedTotal, perRelay: [], best: null };
|
||||
}
|
||||
const events = storedEvents.get(key) || [];
|
||||
return { total: events.length };
|
||||
return { total: events.length, perRelay: [], best: null };
|
||||
};
|
||||
|
||||
const subscribeVideoViewEvents = (pointer, options = {}) => {
|
||||
@@ -152,16 +178,22 @@ function createMockNostrHarness() {
|
||||
};
|
||||
|
||||
const setCountTotal = (key, total) => {
|
||||
if (Number.isFinite(total)) {
|
||||
customTotals.set(key, Number(total));
|
||||
if (
|
||||
typeof total === "object" &&
|
||||
total !== null &&
|
||||
!Array.isArray(total)
|
||||
) {
|
||||
customCountResults.set(key, JSON.parse(JSON.stringify(total)));
|
||||
} else if (Number.isFinite(total)) {
|
||||
customCountResults.set(key, Number(total));
|
||||
} else {
|
||||
customTotals.delete(key);
|
||||
customCountResults.delete(key);
|
||||
}
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
storedEvents.clear();
|
||||
customTotals.clear();
|
||||
customCountResults.clear();
|
||||
subscribers.clear();
|
||||
resetMetrics();
|
||||
};
|
||||
@@ -319,6 +351,45 @@ async function testHydrationSkipsStaleEventsAndRollsOff() {
|
||||
}
|
||||
}
|
||||
|
||||
async function testRelayCountAggregationUsesBestEstimate() {
|
||||
localStorage.clear();
|
||||
harness.reset();
|
||||
harness.resetMetrics();
|
||||
|
||||
const pointer = { type: "e", value: "view-counter-multi-relay" };
|
||||
const pointerKey = harness.pointerKeyFromInput(pointer);
|
||||
|
||||
harness.setEvents(pointerKey, []);
|
||||
harness.setCountTotal(pointerKey, {
|
||||
total: 5,
|
||||
best: { relay: "wss://relay.alpha", count: 5 },
|
||||
perRelay: [
|
||||
{ url: "wss://relay.alpha", ok: true, count: 5 },
|
||||
{ url: "wss://relay.beta", ok: true, count: 5 },
|
||||
],
|
||||
});
|
||||
|
||||
const updates = [];
|
||||
const token = subscribeToVideoViewCount(pointer, (state) => {
|
||||
updates.push({ ...state });
|
||||
});
|
||||
|
||||
try {
|
||||
await flushPromises();
|
||||
await flushPromises();
|
||||
|
||||
const final = updates.at(-1);
|
||||
assert.ok(final, "expected hydration update for aggregated relay counts");
|
||||
assert.equal(
|
||||
final.total,
|
||||
5,
|
||||
"identical relay COUNT responses should not be double-counted"
|
||||
);
|
||||
} finally {
|
||||
unsubscribeFromVideoViewCount(pointer, token);
|
||||
}
|
||||
}
|
||||
|
||||
async function testLocalIngestNotifiesImmediately() {
|
||||
localStorage.clear();
|
||||
harness.reset();
|
||||
@@ -458,5 +529,6 @@ await testDedupesWithinWindow();
|
||||
await testHydrationSkipsStaleEventsAndRollsOff();
|
||||
await testLocalIngestNotifiesImmediately();
|
||||
await testUnsubscribeStopsCallbacks();
|
||||
await testRelayCountAggregationUsesBestEstimate();
|
||||
|
||||
console.log("View counter tests completed successfully.");
|
||||
|
||||
Reference in New Issue
Block a user