496 lines
15 KiB
JavaScript
496 lines
15 KiB
JavaScript
"use strict";
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
|
|
// nip29.ts
|
|
var nip29_exports = {};
|
|
__export(nip29_exports, {
|
|
GroupAdminPermission: () => GroupAdminPermission,
|
|
encodeGroupReference: () => encodeGroupReference,
|
|
fetchGroupAdminsEvent: () => fetchGroupAdminsEvent,
|
|
fetchGroupMembersEvent: () => fetchGroupMembersEvent,
|
|
fetchGroupMetadataEvent: () => fetchGroupMetadataEvent,
|
|
fetchRelayInformationByGroupReference: () => fetchRelayInformationByGroupReference,
|
|
generateGroupAdminsEventTemplate: () => generateGroupAdminsEventTemplate,
|
|
generateGroupMembersEventTemplate: () => generateGroupMembersEventTemplate,
|
|
generateGroupMetadataEventTemplate: () => generateGroupMetadataEventTemplate,
|
|
getNormalizedRelayURLByGroupReference: () => getNormalizedRelayURLByGroupReference,
|
|
loadGroup: () => loadGroup,
|
|
loadGroupFromCode: () => loadGroupFromCode,
|
|
parseGroupAdminsEvent: () => parseGroupAdminsEvent,
|
|
parseGroupCode: () => parseGroupCode,
|
|
parseGroupMembersEvent: () => parseGroupMembersEvent,
|
|
parseGroupMetadataEvent: () => parseGroupMetadataEvent,
|
|
subscribeRelayGroupsMetadataEvents: () => subscribeRelayGroupsMetadataEvents,
|
|
validateGroupAdminsEvent: () => validateGroupAdminsEvent,
|
|
validateGroupMembersEvent: () => validateGroupMembersEvent,
|
|
validateGroupMetadataEvent: () => validateGroupMetadataEvent
|
|
});
|
|
module.exports = __toCommonJS(nip29_exports);
|
|
|
|
// 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
|
|
var import_utils2 = require("@noble/hashes/utils");
|
|
var import_base = require("@scure/base");
|
|
|
|
// utils.ts
|
|
var import_utils = require("@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 } = import_base.bech32.decode(code, Bech32MaxSize);
|
|
let data = new Uint8Array(import_base.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: (0, import_utils2.bytesToHex)(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: (0, import_utils2.bytesToHex)(tlv[0][0]),
|
|
relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : [],
|
|
author: tlv[2]?.[0] ? (0, import_utils2.bytesToHex)(tlv[2][0]) : void 0,
|
|
kind: tlv[3]?.[0] ? parseInt((0, import_utils2.bytesToHex)(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: (0, import_utils2.bytesToHex)(tlv[2][0]),
|
|
kind: parseInt((0, import_utils2.bytesToHex)(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: (0, import_utils2.bytesToHex)(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();
|
|
}
|