Files
bitvid/tests/mirror-pointer.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

188 lines
5.8 KiB
JavaScript

// Addressable pointers to a video's NIP-71 mirror — see js/nostr/mirrorPointer.js.
//
// SCN-mirror-quote: a share note embeds `nostr:naddr1…` so nostr clients render
// a native video quote card. The pointer MUST address the event that actually
// exists: a pointer to a missing or wrong-kind event renders as a broken/empty
// quote box, which is strictly worse than the plain-link fallback.
import test from "node:test";
import assert from "node:assert/strict";
import {
NIP71_NORMAL_VIDEO_KIND,
NIP71_SHORT_VIDEO_KIND,
buildMirrorCoordinate,
buildMirrorNaddr,
resolveMirrorKind,
} from "../js/nostr/mirrorPointer.js";
const PUBKEY = "b7c6f6915cfa9a62fff6a1f02604de88c23c6c6c6d1b8f62c7cc10749f307e81";
const ROOT = "b52ab495-ac9c-4a29-a454-aff142a43691";
// Records what it was asked to encode so the tests can assert on inputs rather
// than on bech32 output.
const makeNip19 = () => {
const calls = [];
return {
calls,
naddrEncode(pointer) {
calls.push(pointer);
return `naddr1${pointer.kind}${pointer.identifier}`;
},
};
};
// --- kind resolution ------------------------------------------------------
test("observed relay kinds win over the dimension heuristic", () => {
// Ground truth from nip71MirrorService.findMirror must beat any guess —
// a portrait video whose mirror was published as 34235 is still 34235.
assert.equal(
resolveMirrorKind({ kinds: [NIP71_NORMAL_VIDEO_KIND], width: 720, height: 1280 }),
NIP71_NORMAL_VIDEO_KIND
);
assert.equal(
resolveMirrorKind({ kinds: [NIP71_SHORT_VIDEO_KIND], width: 1920, height: 1080 }),
NIP71_SHORT_VIDEO_KIND
);
});
test("without observed kinds it mirrors nip71Mirror.js's portrait rule", () => {
// nip71Mirror.js:179 — `hasDims && height > width` selects the short kind.
assert.equal(
resolveMirrorKind({ width: 720, height: 1280 }),
NIP71_SHORT_VIDEO_KIND
);
assert.equal(
resolveMirrorKind({ width: 1280, height: 720 }),
NIP71_NORMAL_VIDEO_KIND
);
// Square is not portrait.
assert.equal(
resolveMirrorKind({ width: 1000, height: 1000 }),
NIP71_NORMAL_VIDEO_KIND
);
});
test("missing or nonsensical dimensions default to the normal kind", () => {
for (const dims of [
{},
{ width: 0, height: 0 },
{ width: NaN, height: 10 },
{ width: "wide", height: "tall" },
{ width: -100, height: -200 },
]) {
assert.equal(resolveMirrorKind(dims), NIP71_NORMAL_VIDEO_KIND);
}
assert.equal(resolveMirrorKind(), NIP71_NORMAL_VIDEO_KIND);
});
test("a cross-kind duplicate resolves deterministically to the normal kind", () => {
// nip71MirrorService flags this as `duplicate`. Picking arbitrarily would
// make the shared pointer unstable between shares of the same video.
assert.equal(
resolveMirrorKind({ kinds: [NIP71_SHORT_VIDEO_KIND, NIP71_NORMAL_VIDEO_KIND] }),
NIP71_NORMAL_VIDEO_KIND
);
});
test("unknown kinds are ignored rather than pointed at", () => {
assert.equal(
resolveMirrorKind({ kinds: [1, 30078], width: 720, height: 1280 }),
NIP71_SHORT_VIDEO_KIND,
"falls through to the heuristic instead of addressing kind 1"
);
});
// --- coordinate -----------------------------------------------------------
test("buildMirrorCoordinate produces a kind:pubkey:d address", () => {
assert.equal(
buildMirrorCoordinate({ pubkey: PUBKEY, videoRootId: ROOT, kind: 34235 }),
`34235:${PUBKEY}:${ROOT}`
);
});
test("buildMirrorCoordinate refuses incomplete input", () => {
assert.equal(buildMirrorCoordinate({ videoRootId: ROOT, kind: 34235 }), "");
assert.equal(buildMirrorCoordinate({ pubkey: PUBKEY, kind: 34235 }), "");
assert.equal(buildMirrorCoordinate({ pubkey: PUBKEY, videoRootId: ROOT }), "");
assert.equal(buildMirrorCoordinate({ pubkey: PUBKEY, videoRootId: ROOT, kind: 0 }), "");
assert.equal(buildMirrorCoordinate(), "");
});
// --- naddr ----------------------------------------------------------------
test("buildMirrorNaddr encodes kind, author, identifier and relay hints", () => {
const nip19 = makeNip19();
const naddr = buildMirrorNaddr({
pubkey: PUBKEY,
videoRootId: ROOT,
kind: NIP71_NORMAL_VIDEO_KIND,
relays: ["wss://relay.damus.io", "wss://nos.lol"],
nip19,
});
assert.equal(naddr.startsWith("naddr1"), true);
assert.deepEqual(nip19.calls[0], {
kind: NIP71_NORMAL_VIDEO_KIND,
pubkey: PUBKEY,
identifier: ROOT,
relays: ["wss://relay.damus.io", "wss://nos.lol"],
});
});
test("relay hints are filtered to wss/ws and capped at three", () => {
const nip19 = makeNip19();
buildMirrorNaddr({
pubkey: PUBKEY,
videoRootId: ROOT,
kind: 34235,
relays: [
"https://not-a-relay.example",
"wss://a.example",
"",
"wss://b.example",
"ws://c.example",
"wss://d.example",
null,
],
nip19,
});
assert.deepEqual(nip19.calls[0].relays, [
"wss://a.example",
"wss://b.example",
"ws://c.example",
]);
});
test("buildMirrorNaddr returns empty rather than a malformed pointer", () => {
const nip19 = makeNip19();
const base = { pubkey: PUBKEY, videoRootId: ROOT, kind: 34235, nip19 };
assert.equal(buildMirrorNaddr({ ...base, pubkey: "" }), "");
assert.equal(buildMirrorNaddr({ ...base, videoRootId: "" }), "");
assert.equal(buildMirrorNaddr({ ...base, kind: 0 }), "");
assert.equal(buildMirrorNaddr({ ...base, nip19: null }), "");
assert.equal(buildMirrorNaddr({ ...base, nip19: {} }), "");
assert.equal(buildMirrorNaddr(), "");
assert.equal(nip19.calls.length, 0, "never attempted an encode");
});
test("an encoder that throws yields empty, never a broken quote", () => {
const hostile = {
naddrEncode() {
throw new Error("bad pointer");
},
};
assert.equal(
buildMirrorNaddr({
pubkey: PUBKEY,
videoRootId: ROOT,
kind: 34235,
nip19: hostile,
}),
""
);
});