Files
super_ball/thrower_daemon/node_modules/nostr-tools/lib/esm/nip29.js
2025-09-29 07:21:46 -04:00

473 lines
14 KiB
JavaScript

// nip11.ts
var _fetch;
try {
_fetch = fetch;
} catch {
}
async function fetchRelayInformation(url) {
return await (await fetch(url.replace("ws://", "http://").replace("wss://", "https://"), {
headers: { Accept: "application/nostr+json" }
})).json();
}
// nip19.ts
import { bytesToHex as bytesToHex2, concatBytes, hexToBytes as hexToBytes2 } from "@noble/hashes/utils";
import { bech32 } from "@scure/base";
// utils.ts
import { bytesToHex, hexToBytes } from "@noble/hashes/utils";
var utf8Decoder = new TextDecoder("utf-8");
var utf8Encoder = new TextEncoder();
function normalizeURL(url) {
try {
if (url.indexOf("://") === -1)
url = "wss://" + url;
let p = new URL(url);
p.pathname = p.pathname.replace(/\/+/g, "/");
if (p.pathname.endsWith("/"))
p.pathname = p.pathname.slice(0, -1);
if (p.port === "80" && p.protocol === "ws:" || p.port === "443" && p.protocol === "wss:")
p.port = "";
p.searchParams.sort();
p.hash = "";
return p.toString();
} catch (e) {
throw new Error(`Invalid URL: ${url}`);
}
}
// nip19.ts
var NostrTypeGuard = {
isNProfile: (value) => /^nprofile1[a-z\d]+$/.test(value || ""),
isNEvent: (value) => /^nevent1[a-z\d]+$/.test(value || ""),
isNAddr: (value) => /^naddr1[a-z\d]+$/.test(value || ""),
isNSec: (value) => /^nsec1[a-z\d]{58}$/.test(value || ""),
isNPub: (value) => /^npub1[a-z\d]{58}$/.test(value || ""),
isNote: (value) => /^note1[a-z\d]+$/.test(value || ""),
isNcryptsec: (value) => /^ncryptsec1[a-z\d]+$/.test(value || "")
};
var Bech32MaxSize = 5e3;
function decode(code) {
let { prefix, words } = bech32.decode(code, Bech32MaxSize);
let data = new Uint8Array(bech32.fromWords(words));
switch (prefix) {
case "nprofile": {
let tlv = parseTLV(data);
if (!tlv[0]?.[0])
throw new Error("missing TLV 0 for nprofile");
if (tlv[0][0].length !== 32)
throw new Error("TLV 0 should be 32 bytes");
return {
type: "nprofile",
data: {
pubkey: bytesToHex2(tlv[0][0]),
relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : []
}
};
}
case "nevent": {
let tlv = parseTLV(data);
if (!tlv[0]?.[0])
throw new Error("missing TLV 0 for nevent");
if (tlv[0][0].length !== 32)
throw new Error("TLV 0 should be 32 bytes");
if (tlv[2] && tlv[2][0].length !== 32)
throw new Error("TLV 2 should be 32 bytes");
if (tlv[3] && tlv[3][0].length !== 4)
throw new Error("TLV 3 should be 4 bytes");
return {
type: "nevent",
data: {
id: bytesToHex2(tlv[0][0]),
relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : [],
author: tlv[2]?.[0] ? bytesToHex2(tlv[2][0]) : void 0,
kind: tlv[3]?.[0] ? parseInt(bytesToHex2(tlv[3][0]), 16) : void 0
}
};
}
case "naddr": {
let tlv = parseTLV(data);
if (!tlv[0]?.[0])
throw new Error("missing TLV 0 for naddr");
if (!tlv[2]?.[0])
throw new Error("missing TLV 2 for naddr");
if (tlv[2][0].length !== 32)
throw new Error("TLV 2 should be 32 bytes");
if (!tlv[3]?.[0])
throw new Error("missing TLV 3 for naddr");
if (tlv[3][0].length !== 4)
throw new Error("TLV 3 should be 4 bytes");
return {
type: "naddr",
data: {
identifier: utf8Decoder.decode(tlv[0][0]),
pubkey: bytesToHex2(tlv[2][0]),
kind: parseInt(bytesToHex2(tlv[3][0]), 16),
relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : []
}
};
}
case "nsec":
return { type: prefix, data };
case "npub":
case "note":
return { type: prefix, data: bytesToHex2(data) };
default:
throw new Error(`unknown prefix ${prefix}`);
}
}
function parseTLV(data) {
let result = {};
let rest = data;
while (rest.length > 0) {
let t = rest[0];
let l = rest[1];
let v = rest.slice(2, 2 + l);
rest = rest.slice(2 + l);
if (v.length < l)
throw new Error(`not enough data to read on TLV ${t}`);
result[t] = result[t] || [];
result[t].push(v);
}
return result;
}
// nip29.ts
var GroupAdminPermission = /* @__PURE__ */ ((GroupAdminPermission2) => {
GroupAdminPermission2["AddUser"] = "add-user";
GroupAdminPermission2["EditMetadata"] = "edit-metadata";
GroupAdminPermission2["DeleteEvent"] = "delete-event";
GroupAdminPermission2["RemoveUser"] = "remove-user";
GroupAdminPermission2["AddPermission"] = "add-permission";
GroupAdminPermission2["RemovePermission"] = "remove-permission";
GroupAdminPermission2["EditGroupStatus"] = "edit-group-status";
GroupAdminPermission2["PutUser"] = "put-user";
GroupAdminPermission2["CreateGroup"] = "create-group";
GroupAdminPermission2["DeleteGroup"] = "delete-group";
GroupAdminPermission2["CreateInvite"] = "create-invite";
return GroupAdminPermission2;
})(GroupAdminPermission || {});
function generateGroupMetadataEventTemplate(group) {
const tags = [["d", group.metadata.id]];
group.metadata.name && tags.push(["name", group.metadata.name]);
group.metadata.picture && tags.push(["picture", group.metadata.picture]);
group.metadata.about && tags.push(["about", group.metadata.about]);
group.metadata.isPublic && tags.push(["public"]);
group.metadata.isOpen && tags.push(["open"]);
return {
content: "",
created_at: Math.floor(Date.now() / 1e3),
kind: 39e3,
tags
};
}
function validateGroupMetadataEvent(event) {
if (event.kind !== 39e3)
return false;
if (!event.pubkey)
return false;
const requiredTags = ["d"];
for (const tag of requiredTags) {
if (!event.tags.find(([t]) => t == tag))
return false;
}
return true;
}
function generateGroupAdminsEventTemplate(group, admins) {
const tags = [["d", group.metadata.id]];
for (const admin of admins) {
tags.push(["p", admin.pubkey, admin.label || "", ...admin.permissions]);
}
return {
content: "",
created_at: Math.floor(Date.now() / 1e3),
kind: 39001,
tags
};
}
function validateGroupAdminsEvent(event) {
if (event.kind !== 39001)
return false;
const requiredTags = ["d"];
for (const tag of requiredTags) {
if (!event.tags.find(([t]) => t == tag))
return false;
}
for (const [tag, _value, _label, ...permissions] of event.tags) {
if (tag !== "p")
continue;
for (let i = 0; i < permissions.length; i += 1) {
if (typeof permissions[i] !== "string")
return false;
if (!Object.values(GroupAdminPermission).includes(permissions[i]))
return false;
}
}
return true;
}
function generateGroupMembersEventTemplate(group, members) {
const tags = [["d", group.metadata.id]];
for (const member of members) {
tags.push(["p", member.pubkey, member.label || ""]);
}
return {
content: "",
created_at: Math.floor(Date.now() / 1e3),
kind: 39002,
tags
};
}
function validateGroupMembersEvent(event) {
if (event.kind !== 39002)
return false;
const requiredTags = ["d"];
for (const tag of requiredTags) {
if (!event.tags.find(([t]) => t == tag))
return false;
}
return true;
}
function getNormalizedRelayURLByGroupReference(groupReference) {
return normalizeURL(groupReference.host);
}
async function fetchRelayInformationByGroupReference(groupReference) {
const normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference);
return fetchRelayInformation(normalizedRelayURL);
}
async function fetchGroupMetadataEvent({
pool,
groupReference,
relayInformation,
normalizedRelayURL
}) {
if (!normalizedRelayURL) {
normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference);
}
if (!relayInformation) {
relayInformation = await fetchRelayInformation(normalizedRelayURL);
}
const groupMetadataEvent = await pool.get([normalizedRelayURL], {
kinds: [39e3],
authors: [relayInformation.pubkey],
"#d": [groupReference.id]
});
if (!groupMetadataEvent)
throw new Error(`group '${groupReference.id}' not found on ${normalizedRelayURL}`);
return groupMetadataEvent;
}
function parseGroupMetadataEvent(event) {
if (!validateGroupMetadataEvent(event))
throw new Error("invalid group metadata event");
const metadata = {
id: "",
pubkey: event.pubkey
};
for (const [tag, value] of event.tags) {
switch (tag) {
case "d":
metadata.id = value;
break;
case "name":
metadata.name = value;
break;
case "picture":
metadata.picture = value;
break;
case "about":
metadata.about = value;
break;
case "public":
metadata.isPublic = true;
break;
case "open":
metadata.isOpen = true;
break;
}
}
return metadata;
}
async function fetchGroupAdminsEvent({
pool,
groupReference,
relayInformation,
normalizedRelayURL
}) {
if (!normalizedRelayURL) {
normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference);
}
if (!relayInformation) {
relayInformation = await fetchRelayInformation(normalizedRelayURL);
}
const groupAdminsEvent = await pool.get([normalizedRelayURL], {
kinds: [39001],
authors: [relayInformation.pubkey],
"#d": [groupReference.id]
});
if (!groupAdminsEvent)
throw new Error(`admins for group '${groupReference.id}' not found on ${normalizedRelayURL}`);
return groupAdminsEvent;
}
function parseGroupAdminsEvent(event) {
if (!validateGroupAdminsEvent(event))
throw new Error("invalid group admins event");
const admins = [];
for (const [tag, value, label, ...permissions] of event.tags) {
if (tag !== "p")
continue;
admins.push({
pubkey: value,
label,
permissions
});
}
return admins;
}
async function fetchGroupMembersEvent({
pool,
groupReference,
relayInformation,
normalizedRelayURL
}) {
if (!normalizedRelayURL) {
normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference);
}
if (!relayInformation) {
relayInformation = await fetchRelayInformation(normalizedRelayURL);
}
const groupMembersEvent = await pool.get([normalizedRelayURL], {
kinds: [39002],
authors: [relayInformation.pubkey],
"#d": [groupReference.id]
});
if (!groupMembersEvent)
throw new Error(`members for group '${groupReference.id}' not found on ${normalizedRelayURL}`);
return groupMembersEvent;
}
function parseGroupMembersEvent(event) {
if (!validateGroupMembersEvent(event))
throw new Error("invalid group members event");
const members = [];
for (const [tag, value, label] of event.tags) {
if (tag !== "p")
continue;
members.push({
pubkey: value,
label
});
}
return members;
}
async function loadGroup({
pool,
groupReference,
normalizedRelayURL,
relayInformation
}) {
if (!normalizedRelayURL) {
normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference);
}
if (!relayInformation) {
relayInformation = await fetchRelayInformation(normalizedRelayURL);
}
const metadataEvent = await fetchGroupMetadataEvent({ pool, groupReference, normalizedRelayURL, relayInformation });
const metadata = parseGroupMetadataEvent(metadataEvent);
const adminsEvent = await fetchGroupAdminsEvent({ pool, groupReference, normalizedRelayURL, relayInformation });
const admins = parseGroupAdminsEvent(adminsEvent);
const membersEvent = await fetchGroupMembersEvent({ pool, groupReference, normalizedRelayURL, relayInformation });
const members = parseGroupMembersEvent(membersEvent);
const group = {
relay: normalizedRelayURL,
metadata,
admins,
members,
reference: groupReference
};
return group;
}
async function loadGroupFromCode(pool, code) {
const groupReference = parseGroupCode(code);
if (!groupReference)
throw new Error("invalid group code");
return loadGroup({ pool, groupReference });
}
function parseGroupCode(code) {
if (NostrTypeGuard.isNAddr(code)) {
try {
let { data } = decode(code);
let { relays, identifier } = data;
if (!relays || relays.length === 0)
return null;
let host = relays[0];
if (host.startsWith("wss://")) {
host = host.slice(6);
}
return { host, id: identifier };
} catch (err) {
return null;
}
} else if (code.split("'").length === 2) {
let spl = code.split("'");
return { host: spl[0], id: spl[1] };
}
return null;
}
function encodeGroupReference(gr) {
const { host, id } = gr;
const normalizedHost = host.replace(/^(https?:\/\/|wss?:\/\/)/, "");
return `${normalizedHost}'${id}`;
}
function subscribeRelayGroupsMetadataEvents({
pool,
relayURL,
onError,
onEvent,
onConnect
}) {
let sub;
const normalizedRelayURL = normalizeURL(relayURL);
fetchRelayInformation(normalizedRelayURL).then(async (info) => {
const abstractedRelay = await pool.ensureRelay(normalizedRelayURL);
onConnect?.();
sub = abstractedRelay.prepareSubscription(
[
{
kinds: [39e3],
limit: 50,
authors: [info.pubkey]
}
],
{
onevent(event) {
onEvent(event);
}
}
);
}).catch((err) => {
sub.close();
onError(err);
});
return () => sub.close();
}
export {
GroupAdminPermission,
encodeGroupReference,
fetchGroupAdminsEvent,
fetchGroupMembersEvent,
fetchGroupMetadataEvent,
fetchRelayInformationByGroupReference,
generateGroupAdminsEventTemplate,
generateGroupMembersEventTemplate,
generateGroupMetadataEventTemplate,
getNormalizedRelayURLByGroupReference,
loadGroup,
loadGroupFromCode,
parseGroupAdminsEvent,
parseGroupCode,
parseGroupMembersEvent,
parseGroupMetadataEvent,
subscribeRelayGroupsMetadataEvents,
validateGroupAdminsEvent,
validateGroupMembersEvent,
validateGroupMetadataEvent
};