Files
bitvid/tests/share-event-builder.test.mjs
T
thePR0M3TH3ANandClaude Opus 5 38b51f13a5 [share] Give share-to-Nostr notes a real preview
"Share on Nostr" published a kind-1 note whose body ended with a bare thumbnail
URL. Many clients only inline such a URL when a NIP-92 `imeta` entry describes
it, so the viewer often saw a plain link instead of a preview image.

- buildShareEvent now emits an `imeta` entry for the thumbnail. The media type
  is inferred from the extension and OMITTED when unknown -- a wrong `m` is
  worse than a missing one, since clients use it to decide whether to render
  inline at all, so extensionless Blossom URLs ship url + alt only.
- inferImageMimeTypeFromUrl lives in a SEPARATE map from EXTENSION_MIME_MAP:
  that map gates video upload/publish paths, and folding image types into it
  would let an image pass a video check.
- shareNostrController was passing only { id, pubkey } to buildShareEvent, so
  the tag could never be built; it now forwards title and thumbnail.

When a video has a NIP-71 mirror, the note instead quotes it with
`nostr:naddr1...`, which nostr clients render as a native video quote card --
the closest thing to a rich preview that does not depend on any client fetching
OpenGraph tags (most never do).

- The mirror is addressable (kind 34235/34236, author = video pubkey,
  d = videoRootId), so js/nostr/mirrorPointer.js derives the coordinate with no
  relay lookup, and the pointer survives the author editing the video where a
  plain `nevent` would rot on the next replace.
- The pointer is only embedded after nip71MirrorService.findMirror() confirms
  the mirror exists. A pointer to a missing event renders as a broken/empty
  quote box, which is worse than the plain-link fallback, so a disabled
  service, a throwing lookup, a 2.5s timeout or a missing videoRootId all fall
  back to the thumbnail note.
- resolveMirrorKind prefers the kinds actually observed on relays and only then
  falls back to the same portrait rule the mirror builder uses, so a derived
  pointer matches what would be published rather than guessing independently.
- While quoting, the raw thumbnail URL is dropped from the body and the imeta
  tag is skipped: the quote card already shows the thumbnail, and emitting both
  rendered the same image twice. An `a` tag links the note to the coordinate.

Known limitation: a client rendering the quote card still has to play the
video, so mirrors whose imeta url is an .m3u8 hit the same HLS wall -- title and
thumbnail render, playback depends on the client.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 13:59:10 -04:00

284 lines
8.4 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
const SAMPLE_HEX = "deadbeef".repeat(8);
const SAMPLE_NPUB = "npub1shareeventfixture000000000000000000000";
if (typeof globalThis.window === "undefined") {
globalThis.window = {};
}
if (!globalThis.window.NostrTools) {
globalThis.window.NostrTools = {};
}
const existingNip19 =
typeof globalThis.window.NostrTools.nip19 === "object"
? globalThis.window.NostrTools.nip19
: {};
if (typeof existingNip19.decode !== "function") {
globalThis.window.NostrTools.nip19 = {
...existingNip19,
decode: () => ({ type: "npub", data: SAMPLE_HEX }),
};
}
const { buildShareEvent } = await import("../js/nostrEventSchemas.js");
test("buildShareEvent preserves share content", () => {
const event = buildShareEvent({
pubkey: "share-author",
created_at: 1700000800,
content: "Check this out!",
video: { id: "a".repeat(64), pubkey: "b".repeat(64) },
});
assert.equal(event.content, "Check this out!");
});
test("buildShareEvent normalizes hex identifiers for e and p tags", () => {
const event = buildShareEvent({
pubkey: "share-author",
created_at: 1700000801,
content: "Normalize IDs",
video: {
id: "ABCDEF".repeat(10) + "ABCD",
pubkey: SAMPLE_NPUB,
},
});
const eventTags = event.tags.filter((tag) => Array.isArray(tag) && tag[0] === "e");
const authorTags = event.tags.filter((tag) => Array.isArray(tag) && tag[0] === "p");
assert.deepEqual(eventTags, [["e", "abcdef".repeat(10) + "abcd", "", "mention"]]);
assert.deepEqual(authorTags, [["p", SAMPLE_HEX, "", "mention"]]);
});
test("buildShareEvent includes sanitized relay hints", () => {
const event = buildShareEvent({
pubkey: "share-author",
created_at: 1700000802,
content: "Relay hints",
video: { id: "c".repeat(64), pubkey: "d".repeat(64) },
relays: [
" wss://relay.one ",
{ url: " wss://relay.two ", mode: "READ" },
["r", "wss://relay.one", "write"],
{ relay: "wss://relay.three", write: true, read: false },
["wss://relay.four", "write"],
],
});
const relayTags = event.tags.filter((tag) => Array.isArray(tag) && tag[0] === "r");
assert.deepEqual(relayTags, [
["r", "wss://relay.one"],
["r", "wss://relay.two", "read"],
["r", "wss://relay.one", "write"],
["r", "wss://relay.three", "write"],
["r", "wss://relay.four", "write"],
]);
});
test("buildShareEvent tolerates missing optional fields", () => {
const event = buildShareEvent({
pubkey: "share-author",
created_at: 1700000803,
});
assert.equal(event.kind, 1);
assert.equal(event.content, "");
assert.deepEqual(event.tags, []);
});
// SCN-share-imeta
// Given a share note whose body ends with the raw thumbnail URL, when the
// video has an http(s) thumbnail, then a NIP-92 `imeta` entry must describe it
// — many clients only render such a URL inline when imeta backs it, otherwise
// the viewer sees a bare link instead of a preview image.
const imetaOf = (event) => event.tags.find((tag) => tag[0] === "imeta") || null;
test("buildShareEvent emits a NIP-92 imeta entry for the thumbnail", () => {
const event = buildShareEvent({
pubkey: "share-author",
created_at: 1700000804,
video: {
id: "a".repeat(64),
pubkey: "b".repeat(64),
title: "Why I'm (sort of) not worried about AI",
thumbnail: "https://cdn.example/thumb.jpg",
},
});
assert.deepEqual(imetaOf(event), [
"imeta",
"url https://cdn.example/thumb.jpg",
"m image/jpeg",
"alt Why I'm (sort of) not worried about AI",
]);
});
test("buildShareEvent infers the image type per extension, query strings included", () => {
const typeFor = (thumbnail) => {
const event = buildShareEvent({
pubkey: "share-author",
created_at: 1700000805,
video: { id: "a".repeat(64), thumbnail },
});
return (imetaOf(event) || []).find((entry) => entry.startsWith("m ")) || "";
};
assert.equal(typeFor("https://cdn.example/a.png"), "m image/png");
assert.equal(typeFor("https://cdn.example/a.WEBP"), "m image/webp");
assert.equal(typeFor("https://cdn.example/a.jpeg?sig=abc&exp=1"), "m image/jpeg");
assert.equal(typeFor("https://cdn.example/a.avif#x"), "m image/avif");
});
test("buildShareEvent omits the media type when the extension is unknown", () => {
// A wrong `m` is worse than a missing one: clients use it to decide whether
// to render inline at all. Blossom-style extensionless URLs hit this path.
const event = buildShareEvent({
pubkey: "share-author",
created_at: 1700000806,
video: {
id: "a".repeat(64),
title: "Untitled",
thumbnail: "https://blossom.example/0101591966f5764e57abb79f8e0305d7",
},
});
assert.deepEqual(imetaOf(event), [
"imeta",
"url https://blossom.example/0101591966f5764e57abb79f8e0305d7",
"alt Untitled",
]);
});
test("buildShareEvent skips imeta for non-http thumbnails", () => {
for (const thumbnail of [
"",
" ",
"assets/jpg/fallback.jpg",
"data:image/png;base64,iVBORw0KGgo=",
"javascript:alert(1)",
null,
undefined,
]) {
const event = buildShareEvent({
pubkey: "share-author",
created_at: 1700000807,
video: { id: "a".repeat(64), thumbnail },
});
assert.equal(
imetaOf(event),
null,
`expected no imeta for thumbnail ${JSON.stringify(thumbnail)}`
);
}
});
test("buildShareEvent collapses newlines in imeta alt text", () => {
// imeta entries are space-delimited "key value" strings; a raw newline in the
// title would corrupt the tag for parsers reading it back.
const event = buildShareEvent({
pubkey: "share-author",
created_at: 1700000808,
video: {
id: "a".repeat(64),
title: "Line one\nline two\t spaced",
thumbnail: "https://cdn.example/t.png",
},
});
const alt = imetaOf(event).find((entry) => entry.startsWith("alt "));
assert.equal(alt, "alt Line one line two spaced");
assert.equal(/[\n\r\t]/.test(alt), false);
});
// SCN-share-quote
// When a NIP-71 mirror exists, the note body quotes it via `nostr:naddr1…`
// instead of carrying a bare thumbnail URL. The imeta tag must then be dropped
// — the quote card already renders the thumbnail, and emitting both showed the
// same image twice — while an `a` tag links the note to the quoted coordinate.
const MIRROR_COORD = `34235:${"b".repeat(64)}:root-abc`;
test("buildShareEvent adds an `a` tag for the quoted mirror", () => {
const event = buildShareEvent({
pubkey: "share-author",
created_at: 1700000809,
video: {
id: "a".repeat(64),
pubkey: "b".repeat(64),
title: "Quoted video",
thumbnail: "https://cdn.example/thumb.jpg",
mirrorNaddr: "naddr1qqxnzd3exuerjvfhx",
mirrorCoordinate: MIRROR_COORD,
},
});
const aTag = event.tags.find((tag) => tag[0] === "a");
assert.deepEqual(aTag, ["a", MIRROR_COORD, "", "mention"]);
});
test("buildShareEvent drops the imeta when the note quotes a mirror", () => {
const event = buildShareEvent({
pubkey: "share-author",
created_at: 1700000810,
video: {
id: "a".repeat(64),
title: "Quoted video",
thumbnail: "https://cdn.example/thumb.jpg",
mirrorNaddr: "naddr1qqxnzd3exuerjvfhx",
mirrorCoordinate: MIRROR_COORD,
},
});
assert.equal(
event.tags.some((tag) => tag[0] === "imeta"),
false,
"quote card renders the thumbnail; a second imeta duplicates it"
);
});
test("buildShareEvent keeps the imeta when there is no mirror to quote", () => {
const event = buildShareEvent({
pubkey: "share-author",
created_at: 1700000811,
video: {
id: "a".repeat(64),
title: "Unmirrored video",
thumbnail: "https://cdn.example/thumb.jpg",
},
});
assert.equal(event.tags.some((tag) => tag[0] === "imeta"), true);
assert.equal(event.tags.some((tag) => tag[0] === "a"), false);
});
test("buildShareEvent ignores a malformed mirror coordinate", () => {
for (const coordinate of [
"not-a-coordinate",
"34235:notahexpubkey:root",
":::",
"",
]) {
const event = buildShareEvent({
pubkey: "share-author",
created_at: 1700000812,
video: {
id: "a".repeat(64),
title: "T",
mirrorCoordinate: coordinate,
},
});
assert.equal(
event.tags.some((tag) => tag[0] === "a"),
false,
`must not emit an a tag for ${JSON.stringify(coordinate)}`
);
}
});