You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
499 lines
15 KiB
499 lines
15 KiB
"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); |
|
if (p.protocol === "http:") |
|
p.protocol = "ws:"; |
|
else if (p.protocol === "https:") |
|
p.protocol = "wss:"; |
|
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(); |
|
}
|
|
|