mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Normalize NIP-71 participant pubkeys
This commit is contained in:
1 parent
7556d8d265
commit
d1ec1dcb7b
2 files changed
+99
-4
No files matched your search
+55
-1
@@ -2,6 +2,7 @@ import { ACCEPT_LEGACY_V1 } from "../constants.js";
|
||||
import { deriveTitleFromEvent, magnetFromText } from "../videoEventUtils.js";
|
||||
import { extractMagnetHints } from "../magnet.js";
|
||||
import { devLogger } from "../utils/logger.js";
|
||||
import { getCachedNostrTools } from "./toolkit.js";
|
||||
|
||||
function stringFromInput(value) {
|
||||
if (value === null || value === undefined) {
|
||||
@@ -16,6 +17,50 @@ function stringFromInput(value) {
|
||||
return String(value).trim();
|
||||
}
|
||||
|
||||
const HEX_32_BYTE_REGEX = /^[0-9a-f]{64}$/i;
|
||||
|
||||
function decodeNpubToHex(candidate) {
|
||||
const globalDecoder =
|
||||
typeof window !== "undefined" ? window?.NostrTools?.nip19?.decode : null;
|
||||
const toolkit = getCachedNostrTools();
|
||||
const toolkitDecoder = toolkit?.nip19?.decode;
|
||||
const decoder =
|
||||
typeof globalDecoder === "function" ? globalDecoder : toolkitDecoder;
|
||||
if (typeof decoder !== "function") {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const decoded = decoder(candidate);
|
||||
if (decoded?.type === "npub" && typeof decoded.data === "string") {
|
||||
return decoded.data.toLowerCase();
|
||||
}
|
||||
if (decoded?.type === "npub" && typeof decoded.data?.pubkey === "string") {
|
||||
return decoded.data.pubkey.toLowerCase();
|
||||
}
|
||||
} catch (error) {
|
||||
devLogger.warn("[nostr] Failed to decode npub participant pubkey", error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeNostrPubkey(candidate) {
|
||||
const value = stringFromInput(candidate);
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
if (HEX_32_BYTE_REGEX.test(value)) {
|
||||
return value.toLowerCase();
|
||||
}
|
||||
if (value.toLowerCase().startsWith("npub1")) {
|
||||
const decoded = decodeNpubToHex(value);
|
||||
if (decoded && HEX_32_BYTE_REGEX.test(decoded)) {
|
||||
return decoded;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeUnixSeconds(value) {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return "";
|
||||
@@ -224,11 +269,20 @@ function buildParticipantTag(participant) {
|
||||
if (!participant || typeof participant !== "object") {
|
||||
return null;
|
||||
}
|
||||
const pubkey = stringFromInput(participant.pubkey);
|
||||
const rawPubkey = participant.pubkey;
|
||||
const pubkey = normalizeNostrPubkey(rawPubkey);
|
||||
if (!pubkey) {
|
||||
const candidate = stringFromInput(rawPubkey);
|
||||
if (candidate) {
|
||||
devLogger.warn(
|
||||
"[nostr] Dropping NIP-71 participant tag with invalid pubkey",
|
||||
{ pubkey: candidate }
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const relay = stringFromInput(participant.relay);
|
||||
// NIP-71 "Other tags" mandates 32-byte hex participant pubkeys.
|
||||
const values = ["p", pubkey];
|
||||
if (relay) {
|
||||
values.push(relay);
|
||||
|
||||
@@ -6,6 +6,47 @@ import {
|
||||
extractNip71MetadataFromTags,
|
||||
} from "../js/nostr.js";
|
||||
|
||||
const participantNpub = "npub1bitvidparticipantfixture";
|
||||
const participantHexFromNpub = "c".repeat(64);
|
||||
|
||||
const hadWindow = typeof globalThis.window !== "undefined";
|
||||
const windowRef = hadWindow ? globalThis.window : {};
|
||||
if (!hadWindow) {
|
||||
globalThis.window = windowRef;
|
||||
}
|
||||
const hadNostrTools = typeof windowRef.NostrTools !== "undefined";
|
||||
const nostrToolsRef = hadNostrTools ? windowRef.NostrTools : {};
|
||||
if (!hadNostrTools) {
|
||||
windowRef.NostrTools = nostrToolsRef;
|
||||
}
|
||||
const previousNip19 = nostrToolsRef.nip19;
|
||||
nostrToolsRef.nip19 = {
|
||||
...previousNip19,
|
||||
decode: (value) => {
|
||||
if (value === participantNpub) {
|
||||
return { type: "npub", data: participantHexFromNpub };
|
||||
}
|
||||
if (typeof previousNip19?.decode === "function") {
|
||||
return previousNip19.decode(value);
|
||||
}
|
||||
throw new Error("Unsupported npub fixture");
|
||||
},
|
||||
};
|
||||
|
||||
test.after(() => {
|
||||
if (previousNip19) {
|
||||
nostrToolsRef.nip19 = previousNip19;
|
||||
} else {
|
||||
delete nostrToolsRef.nip19;
|
||||
}
|
||||
if (!hadNostrTools) {
|
||||
delete windowRef.NostrTools;
|
||||
}
|
||||
if (!hadWindow) {
|
||||
delete globalThis.window;
|
||||
}
|
||||
});
|
||||
|
||||
test("buildNip71VideoEvent assembles rich metadata", () => {
|
||||
const metadata = {
|
||||
kind: 21,
|
||||
@@ -53,8 +94,8 @@ test("buildNip71VideoEvent assembles rich metadata", () => {
|
||||
],
|
||||
hashtags: ["bitvid", "nostr"],
|
||||
participants: [
|
||||
{ pubkey: "a".repeat(64), relay: "wss://relay.example" },
|
||||
{ pubkey: "b".repeat(64) },
|
||||
{ pubkey: "A".repeat(64), relay: "wss://relay.example" },
|
||||
{ pubkey: ` ${participantNpub} ` },
|
||||
],
|
||||
references: [
|
||||
"https://example.com/info",
|
||||
@@ -135,7 +176,7 @@ test("buildNip71VideoEvent assembles rich metadata", () => {
|
||||
const participantTags = event.tags.filter((tag) => tag[0] === "p");
|
||||
assert.deepEqual(participantTags, [
|
||||
["p", "a".repeat(64), "wss://relay.example"],
|
||||
["p", "b".repeat(64)],
|
||||
["p", participantHexFromNpub],
|
||||
]);
|
||||
|
||||
const referenceTags = event.tags.filter((tag) => tag[0] === "r");
|
||||
|
||||
Reference in new issue
Block a user