Serialize repost targets when available

This commit is contained in:
thePR0M3TH3AN committed 2025-11-03 14:35:43 -05:00
1 parent ceeabddf11
commit a59f214aeb
4 files changed
+151 -1

No files matched your search

+2
View File
@@ -12,6 +12,8 @@ that a `kind 1` text note is worth reading.
The `content` of a repost event is _the stringified JSON of the reposted note_. It MAY also be empty, but that is not recommended.
Reposts of [NIP-70](70.md)-protected events SHOULD always have an empty `content`.
bitvid follows this guidance by serializing the full source event payload (including non-kind-1 targets) whenever it can be fetched from a relay, and falls back to an empty `content` only when the original note is protected or otherwise unavailable.
The repost event MUST include an `e` tag with the `id` of the note that is
being reposted. That tag MUST include a relay URL as its third entry
to indicate where it can be fetched.
+27
View File
@@ -521,6 +521,21 @@ export async function repostEvent({
const cachedVideo = client?.allEvents?.get(normalizedId) || null;
const cachedRaw = client?.rawEvents?.get(normalizedId) || null;
let resolvedRawEvent = cachedRaw;
const serializeEvent = (event) => {
if (!event || typeof event !== "object") {
return "";
}
try {
return JSON.stringify(event);
} catch (error) {
devLogger.warn(
`[nostr] Failed to serialize repost target ${normalizedId}`,
error,
);
return "";
}
};
let serializedSourceEvent = serializeEvent(resolvedRawEvent);
let authorPubkey =
typeof options.authorPubkey === "string" && options.authorPubkey.trim()
@@ -789,6 +804,7 @@ export async function repostEvent({
});
if (fetched) {
resolvedRawEvent = fetched;
serializedSourceEvent = serializeEvent(fetched);
}
} catch (error) {
devLogger.warn(
@@ -809,6 +825,10 @@ export async function repostEvent({
}
}
if (resolvedRawEvent && !serializedSourceEvent) {
serializedSourceEvent = serializeEvent(resolvedRawEvent);
}
if (resolvedRawEvent && !eventRelay) {
const rawRelay = deriveRelayFromRawEvent(resolvedRawEvent);
if (rawRelay) {
@@ -893,6 +913,8 @@ export async function repostEvent({
additionalTags,
repostKind,
targetKind,
targetEvent: resolvedRawEvent,
serializedEvent: serializedSourceEvent,
});
try {
@@ -924,6 +946,10 @@ export async function repostEvent({
normalizedSigner !== normalizedLogged &&
normalizedSigner === sessionPubkey;
const sourceInfo = serializedSourceEvent
? { raw: resolvedRawEvent, serialized: serializedSourceEvent }
: null;
return {
ok: true,
event: signedEvent,
@@ -931,6 +957,7 @@ export async function repostEvent({
relays,
sessionActor: usedSessionActor,
signerPubkey,
source: sourceInfo,
};
} catch (error) {
devLogger.warn("[nostr] Repost publish failed:", error);
+36 -1
View File
@@ -659,6 +659,8 @@ export function buildRepostEvent({
additionalTags = [],
repostKind,
targetKind,
targetEvent = null,
serializedEvent = "",
}) {
const schema = getNostrEventSchema(NOTE_TYPES.REPOST);
const tags = [];
@@ -722,12 +724,45 @@ export function buildRepostEvent({
tags.push(["k", String(normalizedTargetKind)]);
}
const normalizedSerializedEvent =
typeof serializedEvent === "string" ? serializedEvent : "";
let normalizedTargetEvent =
targetEvent && typeof targetEvent === "object" ? targetEvent : null;
if (!normalizedTargetEvent && normalizedSerializedEvent) {
try {
normalizedTargetEvent = JSON.parse(normalizedSerializedEvent);
} catch (error) {
void error;
}
}
const hasProtectedTag = Array.isArray(normalizedTargetEvent?.tags)
? normalizedTargetEvent.tags.some(
(tag) => Array.isArray(tag) && tag.length && tag[0] === "-",
)
: false;
let content = "";
if (!hasProtectedTag) {
if (normalizedSerializedEvent) {
content = normalizedSerializedEvent;
} else if (normalizedTargetEvent) {
try {
content = JSON.stringify(normalizedTargetEvent);
} catch (error) {
void error;
}
}
}
return {
kind: resolvedKind,
pubkey,
created_at,
tags,
content: "",
content,
};
}
+86
View File
@@ -228,6 +228,92 @@ await (async function testRepostEventFailsWhenRelayMissing() {
assert.ok(Array.isArray(result.details?.relays));
})();
await (async function testRepostEventSerializesKindOneTargetsWhenSourceAvailable() {
const eventId = "repost-kind1-content";
const authorPubkey = "d".repeat(64);
const sessionPubkey = "f".repeat(64);
const { client } = createPublishClient({ sessionPubkey });
const rawEvent = {
id: eventId,
pubkey: authorPubkey,
created_at: 1_700_000_100,
kind: 1,
tags: [],
content: "hello from kind1",
};
client.rawEvents.set(eventId, rawEvent);
const result = await repostEvent({
client,
eventId,
options: {
pointer: ["e", eventId, "wss://origin"],
authorPubkey,
},
resolveActiveSigner: resolveActiveSignerStub,
shouldRequestExtensionPermissions: shouldRequestExtensionPermissionsStub,
signEventWithPrivateKey: signEventWithPrivateKeyStub,
eventToAddressPointer: eventToAddressPointerStub,
});
assert.equal(result.ok, true);
const expectedContent = JSON.stringify(rawEvent);
assert.equal(result.event?.kind, 6);
assert.equal(result.event?.content, expectedContent);
assert.equal(result.source?.serialized, expectedContent);
assert.equal(result.source?.raw, rawEvent);
})();
await (async function testRepostEventSerializesGenericTargetsWhenSourceAvailable() {
const eventId = "repost-kind30078-content";
const authorPubkey = "e".repeat(64);
const sessionPubkey = "g".repeat(64);
const { client } = createPublishClient({ sessionPubkey });
const identifier = "video-identifier";
const rawEvent = {
id: eventId,
pubkey: authorPubkey,
created_at: 1_700_000_200,
kind: 30078,
tags: [["d", identifier]],
content: JSON.stringify({ version: 3, title: "Demo" }),
};
client.rawEvents.set(eventId, rawEvent);
const address = `30078:${authorPubkey}:${identifier}`;
const result = await repostEvent({
client,
eventId,
options: {
pointer: ["e", eventId, "wss://origin"],
authorPubkey,
address,
addressRelay: "wss://origin",
},
resolveActiveSigner: resolveActiveSignerStub,
shouldRequestExtensionPermissions: shouldRequestExtensionPermissionsStub,
signEventWithPrivateKey: signEventWithPrivateKeyStub,
eventToAddressPointer: eventToAddressPointerStub,
});
assert.equal(result.ok, true);
const expectedContent = JSON.stringify(rawEvent);
assert.equal(result.event?.kind, 16);
assert.equal(result.event?.content, expectedContent);
assert.equal(result.source?.serialized, expectedContent);
assert.equal(result.source?.raw, rawEvent);
const kTag = result.event?.tags?.find(
(tag) => Array.isArray(tag) && tag[0] === "k",
);
assert.deepEqual(kTag, ["k", String(rawEvent.kind)]);
})();
await (async function testMirrorEventIncludesHostedMetadata() {
const eventId = "mirror-event";
const actorPubkey = "1".repeat(64);