mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-29 17:02:28 +00:00
Add default view event payload and cover recordVideoView
This commit is contained in:
1 parent
c06233947f
commit
cf99059564
3 files changed
+244
-3
No files matched your search
+44
-2
@@ -2785,8 +2785,50 @@ class NostrClient {
|
||||
? ["e", pointer.value, pointer.relay]
|
||||
: ["e", pointer.value];
|
||||
|
||||
const content =
|
||||
typeof options.content === "string" ? options.content : "";
|
||||
let content = "";
|
||||
if (typeof options.content === "string") {
|
||||
content = options.content;
|
||||
} else if (
|
||||
options.content &&
|
||||
typeof options.content === "object" &&
|
||||
!Array.isArray(options.content)
|
||||
) {
|
||||
try {
|
||||
content = JSON.stringify(options.content);
|
||||
} catch (error) {
|
||||
if (isDevMode) {
|
||||
console.warn(
|
||||
"[nostr] Failed to serialize custom view event content:",
|
||||
error
|
||||
);
|
||||
}
|
||||
content = "";
|
||||
}
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
const payload = {
|
||||
target: {
|
||||
type: pointer.type,
|
||||
value: pointer.value,
|
||||
},
|
||||
created_at: createdAt,
|
||||
};
|
||||
if (pointer.relay) {
|
||||
payload.target.relay = pointer.relay;
|
||||
}
|
||||
try {
|
||||
content = JSON.stringify(payload);
|
||||
} catch (error) {
|
||||
if (isDevMode) {
|
||||
console.warn(
|
||||
"[nostr] Failed to serialize default view event content:",
|
||||
error
|
||||
);
|
||||
}
|
||||
content = "";
|
||||
}
|
||||
}
|
||||
|
||||
const event = buildViewEvent({
|
||||
pubkey: actorPubkey,
|
||||
|
||||
+66
-1
@@ -30,6 +30,69 @@ export const ADMIN_LIST_IDENTIFIERS = Object.freeze({
|
||||
|
||||
const DEFAULT_APPEND_TAGS = [];
|
||||
|
||||
let cachedUtf8Encoder = null;
|
||||
|
||||
function ensureValidUtf8Content(value) {
|
||||
let normalized = "";
|
||||
if (typeof value === "string") {
|
||||
normalized = value;
|
||||
} else if (value === undefined || value === null) {
|
||||
normalized = "";
|
||||
} else if (typeof value === "object") {
|
||||
try {
|
||||
const stringified = JSON.stringify(value);
|
||||
normalized = typeof stringified === "string" ? stringified : "";
|
||||
} catch (error) {
|
||||
if (isDevMode) {
|
||||
console.warn(
|
||||
"[nostrEventSchemas] Failed to serialize view event content:",
|
||||
error
|
||||
);
|
||||
}
|
||||
normalized = "";
|
||||
}
|
||||
} else {
|
||||
normalized = String(value);
|
||||
}
|
||||
|
||||
if (!cachedUtf8Encoder && typeof TextEncoder !== "undefined") {
|
||||
cachedUtf8Encoder = new TextEncoder();
|
||||
}
|
||||
|
||||
if (cachedUtf8Encoder) {
|
||||
try {
|
||||
cachedUtf8Encoder.encode(normalized);
|
||||
} catch (error) {
|
||||
if (isDevMode) {
|
||||
console.warn(
|
||||
"[nostrEventSchemas] Dropping invalid UTF-8 characters from event content",
|
||||
error
|
||||
);
|
||||
}
|
||||
const sanitized = Array.from(normalized)
|
||||
.filter((char) => {
|
||||
const code = char.charCodeAt(0);
|
||||
return code < 0xd800 || code > 0xdfff;
|
||||
})
|
||||
.join("");
|
||||
try {
|
||||
cachedUtf8Encoder.encode(sanitized);
|
||||
normalized = sanitized;
|
||||
} catch (encodeError) {
|
||||
if (isDevMode) {
|
||||
console.warn(
|
||||
"[nostrEventSchemas] Failed to normalize view event content; defaulting to empty string",
|
||||
encodeError
|
||||
);
|
||||
}
|
||||
normalized = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
const BASE_SCHEMAS = {
|
||||
[NOTE_TYPES.VIDEO_POST]: {
|
||||
type: NOTE_TYPES.VIDEO_POST,
|
||||
@@ -477,12 +540,14 @@ export function buildViewEvent({
|
||||
}
|
||||
appendSchemaTags(tags, schema);
|
||||
|
||||
const resolvedContent = ensureValidUtf8Content(content);
|
||||
|
||||
return {
|
||||
kind: schema?.kind ?? WATCH_HISTORY_KIND,
|
||||
pubkey,
|
||||
created_at,
|
||||
tags,
|
||||
content: typeof content === "string" ? content : String(content ?? ""),
|
||||
content: resolvedContent,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -506,6 +506,139 @@ async function testUnsubscribeStopsCallbacks() {
|
||||
);
|
||||
}
|
||||
|
||||
async function testRecordVideoViewEmitsJsonPayload() {
|
||||
localStorage.clear();
|
||||
harness.reset();
|
||||
harness.resetMetrics();
|
||||
|
||||
const pointer = {
|
||||
type: "a",
|
||||
value: "30078:pub:view-json",
|
||||
relay: "wss://relay.example",
|
||||
};
|
||||
const createdAt = Math.floor(Date.now() / 1000);
|
||||
|
||||
const originalPool = nostrClient.pool;
|
||||
const originalRelays = Array.isArray(nostrClient.relays)
|
||||
? [...nostrClient.relays]
|
||||
: nostrClient.relays;
|
||||
const originalEnsureSessionActor = nostrClient.ensureSessionActor;
|
||||
const originalSessionActor = nostrClient.sessionActor;
|
||||
const originalPubkey = nostrClient.pubkey;
|
||||
const originalUpdateHistory = nostrClient.updateWatchHistoryList;
|
||||
const originalWarn = console.warn;
|
||||
const originalNostrTools = window.NostrTools;
|
||||
|
||||
const warnings = [];
|
||||
console.warn = (...args) => {
|
||||
warnings.push(
|
||||
args
|
||||
.map((value) => {
|
||||
if (typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(value);
|
||||
} catch (error) {
|
||||
return String(value);
|
||||
}
|
||||
})
|
||||
.join(" ")
|
||||
);
|
||||
};
|
||||
|
||||
window.NostrTools = {
|
||||
...window.NostrTools,
|
||||
getEventHash: () => "event-hash-record", // deterministic stub for tests
|
||||
signEvent: () => "event-sig-record",
|
||||
};
|
||||
|
||||
const publishCalls = [];
|
||||
nostrClient.pool = {
|
||||
publish(relayUrls, event) {
|
||||
publishCalls.push({ relays: relayUrls, event });
|
||||
return {
|
||||
on(type, handler) {
|
||||
if (type === "ok") {
|
||||
setTimeout(() => handler(), 0);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
nostrClient.relays = ["wss://relay.example"];
|
||||
nostrClient.ensureSessionActor = async () => {
|
||||
nostrClient.sessionActor = {
|
||||
pubkey: "pub-record-json",
|
||||
privateKey: "priv-record-json",
|
||||
};
|
||||
return "pub-record-json";
|
||||
};
|
||||
nostrClient.pubkey = "";
|
||||
|
||||
nostrClient.updateWatchHistoryList = async () => ({ ok: true });
|
||||
|
||||
try {
|
||||
const result = await nostrClient.recordVideoView(pointer, {
|
||||
created_at: createdAt,
|
||||
});
|
||||
|
||||
assert.ok(result.ok, "recordVideoView should succeed with stubbed handlers");
|
||||
assert.equal(
|
||||
publishCalls.length,
|
||||
1,
|
||||
"publishViewEvent should be invoked once per recordVideoView call"
|
||||
);
|
||||
|
||||
const emittedEvent = publishCalls[0]?.event || null;
|
||||
assert.ok(emittedEvent, "publishViewEvent should enqueue an event for relays");
|
||||
assert.equal(
|
||||
typeof emittedEvent.content,
|
||||
"string",
|
||||
"recordVideoView should provide serialized view content"
|
||||
);
|
||||
|
||||
const payload = JSON.parse(emittedEvent.content);
|
||||
assert.deepEqual(
|
||||
payload,
|
||||
{
|
||||
target: {
|
||||
type: pointer.type,
|
||||
value: pointer.value,
|
||||
relay: pointer.relay,
|
||||
},
|
||||
created_at: createdAt,
|
||||
},
|
||||
"default view content should capture pointer metadata and timestamp"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
result.view.event.content,
|
||||
emittedEvent.content,
|
||||
"serialized payload should persist on the emitted event"
|
||||
);
|
||||
} finally {
|
||||
nostrClient.pool = originalPool;
|
||||
nostrClient.relays = originalRelays;
|
||||
nostrClient.ensureSessionActor = originalEnsureSessionActor;
|
||||
nostrClient.sessionActor = originalSessionActor;
|
||||
nostrClient.pubkey = originalPubkey;
|
||||
nostrClient.updateWatchHistoryList = originalUpdateHistory;
|
||||
console.warn = originalWarn;
|
||||
window.NostrTools = originalNostrTools;
|
||||
}
|
||||
|
||||
const viewCounterWarnings = warnings.filter((message) =>
|
||||
message.includes("[viewCounter] Failed to ingest local view event")
|
||||
);
|
||||
assert.equal(
|
||||
viewCounterWarnings.length,
|
||||
0,
|
||||
"viewCounter.ingestLocalViewEvent should not reject JSON view payloads"
|
||||
);
|
||||
}
|
||||
|
||||
// Cached totals older than VIEW_COUNT_CACHE_TTL_MS should be discarded and rehydrated from relays.
|
||||
async function testHydrationRefreshesAfterCacheTtl() {
|
||||
harness.reset();
|
||||
@@ -568,5 +701,6 @@ await testHydrationSkipsStaleEventsAndRollsOff();
|
||||
await testLocalIngestNotifiesImmediately();
|
||||
await testUnsubscribeStopsCallbacks();
|
||||
await testRelayCountAggregationUsesBestEstimate();
|
||||
await testRecordVideoViewEmitsJsonPayload();
|
||||
|
||||
console.log("View counter tests completed successfully.");
|
||||
Reference in new issue
Block a user