mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Fix admin list npub parsing
This commit is contained in:
1 parent
70dec62d8f
commit
0bf0c1be5c
2 files changed
+160
-3
No files matched your search
+71
-3
@@ -36,6 +36,19 @@ function normalizeNpub(value) {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
const HEX_PUBKEY_REGEX = /^[0-9a-f]{64}$/i;
|
||||
|
||||
function isHexPubkey(value) {
|
||||
if (typeof value !== "string") {
|
||||
return false;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return false;
|
||||
}
|
||||
return HEX_PUBKEY_REGEX.test(trimmed);
|
||||
}
|
||||
|
||||
function dedupeNpubs(values) {
|
||||
const normalized = Array.isArray(values) ? values.map(normalizeNpub) : [];
|
||||
return Array.from(
|
||||
@@ -238,17 +251,67 @@ function encodeHexToNpub(hex) {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeParticipantTagValue(value) {
|
||||
if (typeof value !== "string") {
|
||||
return "";
|
||||
}
|
||||
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const nip19 = window?.NostrTools?.nip19;
|
||||
const lower = trimmed.toLowerCase();
|
||||
if (lower.startsWith("npub")) {
|
||||
if (nip19?.decode) {
|
||||
try {
|
||||
const decoded = nip19.decode(trimmed);
|
||||
if (decoded?.type === "npub") {
|
||||
return trimmed;
|
||||
}
|
||||
} catch (error) {
|
||||
// Swallow decode errors so we can still fall back to the raw npub string.
|
||||
}
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
if (isHexPubkey(trimmed)) {
|
||||
const encoded = encodeHexToNpub(trimmed);
|
||||
if (encoded) {
|
||||
return encoded;
|
||||
}
|
||||
if (!canDecodeNpub()) {
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
if (nip19?.decode) {
|
||||
try {
|
||||
const decoded = nip19.decode(trimmed);
|
||||
if (decoded?.type === "npub") {
|
||||
return trimmed;
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore decode failures; values that cannot be interpreted as npubs are dropped.
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function extractNpubsFromEvent(event) {
|
||||
if (!event || !Array.isArray(event.tags)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const hexKeys = event.tags
|
||||
const candidates = event.tags
|
||||
.filter((tag) => Array.isArray(tag) && tag[0] === "p" && tag[1])
|
||||
.map((tag) => tag[1]);
|
||||
|
||||
const npubs = hexKeys
|
||||
.map((hex) => encodeHexToNpub(hex))
|
||||
const npubs = candidates
|
||||
.map((value) => normalizeParticipantTagValue(value))
|
||||
.filter((npub) => typeof npub === "string" && npub);
|
||||
|
||||
return dedupeNpubs(npubs);
|
||||
@@ -570,3 +633,8 @@ export async function persistAdminState(actorNpub, updates) {
|
||||
|
||||
return persistNostrState(actorNpub, updates);
|
||||
}
|
||||
|
||||
export const __adminListStoreTestHooks = Object.freeze({
|
||||
extractNpubsFromEvent,
|
||||
normalizeParticipantTagValue,
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
// Run with: node tests/admin-list-store.test.mjs
|
||||
|
||||
import "./test-helpers/setup-localstorage.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
if (typeof globalThis.window === "undefined") {
|
||||
globalThis.window = {};
|
||||
}
|
||||
|
||||
if (!globalThis.window.NostrTools) {
|
||||
globalThis.window.NostrTools = {};
|
||||
}
|
||||
|
||||
const mockNip19 = {
|
||||
decode(value) {
|
||||
if (typeof value !== "string") {
|
||||
throw new Error("invalid input");
|
||||
}
|
||||
if (value.trim().toLowerCase().startsWith("npub")) {
|
||||
return { type: "npub", data: "mock" };
|
||||
}
|
||||
throw new Error("unsupported value");
|
||||
},
|
||||
npubEncode(hex) {
|
||||
if (typeof hex !== "string") {
|
||||
throw new Error("invalid hex");
|
||||
}
|
||||
const trimmed = hex.trim();
|
||||
if (!trimmed) {
|
||||
throw new Error("empty hex");
|
||||
}
|
||||
return `npub1${trimmed}`;
|
||||
},
|
||||
};
|
||||
|
||||
globalThis.window.NostrTools.nip19 = mockNip19;
|
||||
|
||||
const { __adminListStoreTestHooks } = await import("../js/adminListStore.js");
|
||||
|
||||
const { extractNpubsFromEvent, normalizeParticipantTagValue } =
|
||||
__adminListStoreTestHooks;
|
||||
|
||||
const sampleHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||
const sampleNpub = "npub1existingparticipant";
|
||||
|
||||
const event = {
|
||||
tags: [
|
||||
["p", sampleHex],
|
||||
["p", sampleNpub],
|
||||
["p", " npub1existingparticipant "],
|
||||
["p", ""],
|
||||
["e", "irrelevant"],
|
||||
],
|
||||
};
|
||||
|
||||
const normalized = extractNpubsFromEvent(event);
|
||||
assert.deepEqual(
|
||||
normalized,
|
||||
[
|
||||
mockNip19.npubEncode(sampleHex),
|
||||
sampleNpub,
|
||||
],
|
||||
"should return unique npubs from hex and npub participant tags"
|
||||
);
|
||||
|
||||
mockNip19.decode = () => {
|
||||
throw new Error("decode unavailable");
|
||||
};
|
||||
|
||||
assert.equal(
|
||||
normalizeParticipantTagValue(sampleNpub),
|
||||
sampleNpub,
|
||||
"should fall back to the raw npub when decode fails"
|
||||
);
|
||||
|
||||
mockNip19.decode = undefined;
|
||||
mockNip19.npubEncode = undefined;
|
||||
|
||||
globalThis.window.NostrTools.nip19 = mockNip19;
|
||||
|
||||
const fallbackHex = "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd";
|
||||
|
||||
assert.equal(
|
||||
normalizeParticipantTagValue(fallbackHex),
|
||||
fallbackHex,
|
||||
"should preserve hex when nip19 helpers are unavailable"
|
||||
);
|
||||
|
||||
console.log("admin-list-store tests passed");
|
||||
Reference in new issue
Block a user