2110 lines
58 KiB
JavaScript
2110 lines
58 KiB
JavaScript
var __defProp = Object.defineProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
|
|
// keys.ts
|
|
import { schnorr } from "@noble/curves/secp256k1";
|
|
import { bytesToHex } from "@noble/hashes/utils";
|
|
function generatePrivateKey() {
|
|
return bytesToHex(schnorr.utils.randomPrivateKey());
|
|
}
|
|
function getPublicKey(privateKey) {
|
|
return bytesToHex(schnorr.getPublicKey(privateKey));
|
|
}
|
|
|
|
// event.ts
|
|
import { schnorr as schnorr2 } from "@noble/curves/secp256k1";
|
|
import { sha256 } from "@noble/hashes/sha256";
|
|
import { bytesToHex as bytesToHex2 } from "@noble/hashes/utils";
|
|
|
|
// utils.ts
|
|
var utils_exports = {};
|
|
__export(utils_exports, {
|
|
MessageNode: () => MessageNode,
|
|
MessageQueue: () => MessageQueue,
|
|
insertEventIntoAscendingList: () => insertEventIntoAscendingList,
|
|
insertEventIntoDescendingList: () => insertEventIntoDescendingList,
|
|
normalizeURL: () => normalizeURL,
|
|
utf8Decoder: () => utf8Decoder,
|
|
utf8Encoder: () => utf8Encoder
|
|
});
|
|
var utf8Decoder = new TextDecoder("utf-8");
|
|
var utf8Encoder = new TextEncoder();
|
|
function normalizeURL(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();
|
|
}
|
|
function insertEventIntoDescendingList(sortedArray, event) {
|
|
let start = 0;
|
|
let end = sortedArray.length - 1;
|
|
let midPoint;
|
|
let position = start;
|
|
if (end < 0) {
|
|
position = 0;
|
|
} else if (event.created_at < sortedArray[end].created_at) {
|
|
position = end + 1;
|
|
} else if (event.created_at >= sortedArray[start].created_at) {
|
|
position = start;
|
|
} else
|
|
while (true) {
|
|
if (end <= start + 1) {
|
|
position = end;
|
|
break;
|
|
}
|
|
midPoint = Math.floor(start + (end - start) / 2);
|
|
if (sortedArray[midPoint].created_at > event.created_at) {
|
|
start = midPoint;
|
|
} else if (sortedArray[midPoint].created_at < event.created_at) {
|
|
end = midPoint;
|
|
} else {
|
|
position = midPoint;
|
|
break;
|
|
}
|
|
}
|
|
if (sortedArray[position]?.id !== event.id) {
|
|
return [...sortedArray.slice(0, position), event, ...sortedArray.slice(position)];
|
|
}
|
|
return sortedArray;
|
|
}
|
|
function insertEventIntoAscendingList(sortedArray, event) {
|
|
let start = 0;
|
|
let end = sortedArray.length - 1;
|
|
let midPoint;
|
|
let position = start;
|
|
if (end < 0) {
|
|
position = 0;
|
|
} else if (event.created_at > sortedArray[end].created_at) {
|
|
position = end + 1;
|
|
} else if (event.created_at <= sortedArray[start].created_at) {
|
|
position = start;
|
|
} else
|
|
while (true) {
|
|
if (end <= start + 1) {
|
|
position = end;
|
|
break;
|
|
}
|
|
midPoint = Math.floor(start + (end - start) / 2);
|
|
if (sortedArray[midPoint].created_at < event.created_at) {
|
|
start = midPoint;
|
|
} else if (sortedArray[midPoint].created_at > event.created_at) {
|
|
end = midPoint;
|
|
} else {
|
|
position = midPoint;
|
|
break;
|
|
}
|
|
}
|
|
if (sortedArray[position]?.id !== event.id) {
|
|
return [...sortedArray.slice(0, position), event, ...sortedArray.slice(position)];
|
|
}
|
|
return sortedArray;
|
|
}
|
|
var MessageNode = class {
|
|
_value;
|
|
_next;
|
|
get value() {
|
|
return this._value;
|
|
}
|
|
set value(message) {
|
|
this._value = message;
|
|
}
|
|
get next() {
|
|
return this._next;
|
|
}
|
|
set next(node) {
|
|
this._next = node;
|
|
}
|
|
constructor(message) {
|
|
this._value = message;
|
|
this._next = null;
|
|
}
|
|
};
|
|
var MessageQueue = class {
|
|
_first;
|
|
_last;
|
|
get first() {
|
|
return this._first;
|
|
}
|
|
set first(messageNode) {
|
|
this._first = messageNode;
|
|
}
|
|
get last() {
|
|
return this._last;
|
|
}
|
|
set last(messageNode) {
|
|
this._last = messageNode;
|
|
}
|
|
_size;
|
|
get size() {
|
|
return this._size;
|
|
}
|
|
set size(v) {
|
|
this._size = v;
|
|
}
|
|
constructor() {
|
|
this._first = null;
|
|
this._last = null;
|
|
this._size = 0;
|
|
}
|
|
enqueue(message) {
|
|
const newNode = new MessageNode(message);
|
|
if (this._size === 0 || !this._last) {
|
|
this._first = newNode;
|
|
this._last = newNode;
|
|
} else {
|
|
this._last.next = newNode;
|
|
this._last = newNode;
|
|
}
|
|
this._size++;
|
|
return true;
|
|
}
|
|
dequeue() {
|
|
if (this._size === 0 || !this._first)
|
|
return null;
|
|
let prev = this._first;
|
|
this._first = prev.next;
|
|
prev.next = null;
|
|
this._size--;
|
|
return prev.value;
|
|
}
|
|
};
|
|
|
|
// event.ts
|
|
var verifiedSymbol = Symbol("verified");
|
|
var Kind = /* @__PURE__ */ ((Kind3) => {
|
|
Kind3[Kind3["Metadata"] = 0] = "Metadata";
|
|
Kind3[Kind3["Text"] = 1] = "Text";
|
|
Kind3[Kind3["RecommendRelay"] = 2] = "RecommendRelay";
|
|
Kind3[Kind3["Contacts"] = 3] = "Contacts";
|
|
Kind3[Kind3["EncryptedDirectMessage"] = 4] = "EncryptedDirectMessage";
|
|
Kind3[Kind3["EventDeletion"] = 5] = "EventDeletion";
|
|
Kind3[Kind3["Repost"] = 6] = "Repost";
|
|
Kind3[Kind3["Reaction"] = 7] = "Reaction";
|
|
Kind3[Kind3["BadgeAward"] = 8] = "BadgeAward";
|
|
Kind3[Kind3["ChannelCreation"] = 40] = "ChannelCreation";
|
|
Kind3[Kind3["ChannelMetadata"] = 41] = "ChannelMetadata";
|
|
Kind3[Kind3["ChannelMessage"] = 42] = "ChannelMessage";
|
|
Kind3[Kind3["ChannelHideMessage"] = 43] = "ChannelHideMessage";
|
|
Kind3[Kind3["ChannelMuteUser"] = 44] = "ChannelMuteUser";
|
|
Kind3[Kind3["Blank"] = 255] = "Blank";
|
|
Kind3[Kind3["Report"] = 1984] = "Report";
|
|
Kind3[Kind3["ZapRequest"] = 9734] = "ZapRequest";
|
|
Kind3[Kind3["Zap"] = 9735] = "Zap";
|
|
Kind3[Kind3["RelayList"] = 10002] = "RelayList";
|
|
Kind3[Kind3["ClientAuth"] = 22242] = "ClientAuth";
|
|
Kind3[Kind3["NwcRequest"] = 23194] = "NwcRequest";
|
|
Kind3[Kind3["HttpAuth"] = 27235] = "HttpAuth";
|
|
Kind3[Kind3["ProfileBadge"] = 30008] = "ProfileBadge";
|
|
Kind3[Kind3["BadgeDefinition"] = 30009] = "BadgeDefinition";
|
|
Kind3[Kind3["Article"] = 30023] = "Article";
|
|
Kind3[Kind3["FileMetadata"] = 1063] = "FileMetadata";
|
|
return Kind3;
|
|
})(Kind || {});
|
|
function getBlankEvent(kind = 255 /* Blank */) {
|
|
return {
|
|
kind,
|
|
content: "",
|
|
tags: [],
|
|
created_at: 0
|
|
};
|
|
}
|
|
function finishEvent(t, privateKey) {
|
|
const event = t;
|
|
event.pubkey = getPublicKey(privateKey);
|
|
event.id = getEventHash(event);
|
|
event.sig = getSignature(event, privateKey);
|
|
event[verifiedSymbol] = true;
|
|
return event;
|
|
}
|
|
function serializeEvent(evt) {
|
|
if (!validateEvent(evt))
|
|
throw new Error("can't serialize event with wrong or missing properties");
|
|
return JSON.stringify([0, evt.pubkey, evt.created_at, evt.kind, evt.tags, evt.content]);
|
|
}
|
|
function getEventHash(event) {
|
|
let eventHash = sha256(utf8Encoder.encode(serializeEvent(event)));
|
|
return bytesToHex2(eventHash);
|
|
}
|
|
var isRecord = (obj) => obj instanceof Object;
|
|
function validateEvent(event) {
|
|
if (!isRecord(event))
|
|
return false;
|
|
if (typeof event.kind !== "number")
|
|
return false;
|
|
if (typeof event.content !== "string")
|
|
return false;
|
|
if (typeof event.created_at !== "number")
|
|
return false;
|
|
if (typeof event.pubkey !== "string")
|
|
return false;
|
|
if (!event.pubkey.match(/^[a-f0-9]{64}$/))
|
|
return false;
|
|
if (!Array.isArray(event.tags))
|
|
return false;
|
|
for (let i = 0; i < event.tags.length; i++) {
|
|
let tag = event.tags[i];
|
|
if (!Array.isArray(tag))
|
|
return false;
|
|
for (let j = 0; j < tag.length; j++) {
|
|
if (typeof tag[j] === "object")
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function verifySignature(event) {
|
|
if (typeof event[verifiedSymbol] === "boolean")
|
|
return event[verifiedSymbol];
|
|
const hash = getEventHash(event);
|
|
if (hash !== event.id) {
|
|
return event[verifiedSymbol] = false;
|
|
}
|
|
try {
|
|
return event[verifiedSymbol] = schnorr2.verify(event.sig, hash, event.pubkey);
|
|
} catch (err) {
|
|
return event[verifiedSymbol] = false;
|
|
}
|
|
}
|
|
function signEvent(event, key) {
|
|
console.warn(
|
|
"nostr-tools: `signEvent` is deprecated and will be removed or changed in the future. Please use `getSignature` instead."
|
|
);
|
|
return getSignature(event, key);
|
|
}
|
|
function getSignature(event, key) {
|
|
return bytesToHex2(schnorr2.sign(getEventHash(event), key));
|
|
}
|
|
|
|
// filter.ts
|
|
function matchFilter(filter, event) {
|
|
if (filter.ids && filter.ids.indexOf(event.id) === -1) {
|
|
if (!filter.ids.some((prefix) => event.id.startsWith(prefix))) {
|
|
return false;
|
|
}
|
|
}
|
|
if (filter.kinds && filter.kinds.indexOf(event.kind) === -1)
|
|
return false;
|
|
if (filter.authors && filter.authors.indexOf(event.pubkey) === -1) {
|
|
if (!filter.authors.some((prefix) => event.pubkey.startsWith(prefix))) {
|
|
return false;
|
|
}
|
|
}
|
|
for (let f in filter) {
|
|
if (f[0] === "#") {
|
|
let tagName = f.slice(1);
|
|
let values = filter[`#${tagName}`];
|
|
if (values && !event.tags.find(([t, v]) => t === f.slice(1) && values.indexOf(v) !== -1))
|
|
return false;
|
|
}
|
|
}
|
|
if (filter.since && event.created_at < filter.since)
|
|
return false;
|
|
if (filter.until && event.created_at > filter.until)
|
|
return false;
|
|
return true;
|
|
}
|
|
function matchFilters(filters, event) {
|
|
for (let i = 0; i < filters.length; i++) {
|
|
if (matchFilter(filters[i], event))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function mergeFilters(...filters) {
|
|
let result = {};
|
|
for (let i = 0; i < filters.length; i++) {
|
|
let filter = filters[i];
|
|
Object.entries(filter).forEach(([property, values]) => {
|
|
if (property === "kinds" || property === "ids" || property === "authors" || property[0] === "#") {
|
|
result[property] = result[property] || [];
|
|
for (let v = 0; v < values.length; v++) {
|
|
let value = values[v];
|
|
if (!result[property].includes(value))
|
|
result[property].push(value);
|
|
}
|
|
}
|
|
});
|
|
if (filter.limit && (!result.limit || filter.limit > result.limit))
|
|
result.limit = filter.limit;
|
|
if (filter.until && (!result.until || filter.until > result.until))
|
|
result.until = filter.until;
|
|
if (filter.since && (!result.since || filter.since < result.since))
|
|
result.since = filter.since;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// fakejson.ts
|
|
var fakejson_exports = {};
|
|
__export(fakejson_exports, {
|
|
getHex64: () => getHex64,
|
|
getInt: () => getInt,
|
|
getSubscriptionId: () => getSubscriptionId,
|
|
matchEventId: () => matchEventId,
|
|
matchEventKind: () => matchEventKind,
|
|
matchEventPubkey: () => matchEventPubkey
|
|
});
|
|
function getHex64(json, field) {
|
|
let len = field.length + 3;
|
|
let idx = json.indexOf(`"${field}":`) + len;
|
|
let s = json.slice(idx).indexOf(`"`) + idx + 1;
|
|
return json.slice(s, s + 64);
|
|
}
|
|
function getInt(json, field) {
|
|
let len = field.length;
|
|
let idx = json.indexOf(`"${field}":`) + len + 3;
|
|
let sliced = json.slice(idx);
|
|
let end = Math.min(sliced.indexOf(","), sliced.indexOf("}"));
|
|
return parseInt(sliced.slice(0, end), 10);
|
|
}
|
|
function getSubscriptionId(json) {
|
|
let idx = json.slice(0, 22).indexOf(`"EVENT"`);
|
|
if (idx === -1)
|
|
return null;
|
|
let pstart = json.slice(idx + 7 + 1).indexOf(`"`);
|
|
if (pstart === -1)
|
|
return null;
|
|
let start = idx + 7 + 1 + pstart;
|
|
let pend = json.slice(start + 1, 80).indexOf(`"`);
|
|
if (pend === -1)
|
|
return null;
|
|
let end = start + 1 + pend;
|
|
return json.slice(start + 1, end);
|
|
}
|
|
function matchEventId(json, id) {
|
|
return id === getHex64(json, "id");
|
|
}
|
|
function matchEventPubkey(json, pubkey) {
|
|
return pubkey === getHex64(json, "pubkey");
|
|
}
|
|
function matchEventKind(json, kind) {
|
|
return kind === getInt(json, "kind");
|
|
}
|
|
|
|
// relay.ts
|
|
var newListeners = () => ({
|
|
connect: [],
|
|
disconnect: [],
|
|
error: [],
|
|
notice: [],
|
|
auth: []
|
|
});
|
|
function relayInit(url, options = {}) {
|
|
let { listTimeout = 3e3, getTimeout = 3e3, countTimeout = 3e3 } = options;
|
|
var ws;
|
|
var openSubs = {};
|
|
var listeners = newListeners();
|
|
var subListeners = {};
|
|
var pubListeners = {};
|
|
var connectionPromise;
|
|
async function connectRelay() {
|
|
if (connectionPromise)
|
|
return connectionPromise;
|
|
connectionPromise = new Promise((resolve, reject) => {
|
|
try {
|
|
ws = new WebSocket(url);
|
|
} catch (err) {
|
|
reject(err);
|
|
}
|
|
ws.onopen = () => {
|
|
listeners.connect.forEach((cb) => cb());
|
|
resolve();
|
|
};
|
|
ws.onerror = () => {
|
|
connectionPromise = void 0;
|
|
listeners.error.forEach((cb) => cb());
|
|
reject();
|
|
};
|
|
ws.onclose = async () => {
|
|
connectionPromise = void 0;
|
|
listeners.disconnect.forEach((cb) => cb());
|
|
};
|
|
let incomingMessageQueue = new MessageQueue();
|
|
let handleNextInterval;
|
|
ws.onmessage = (e) => {
|
|
incomingMessageQueue.enqueue(e.data);
|
|
if (!handleNextInterval) {
|
|
handleNextInterval = setInterval(handleNext, 0);
|
|
}
|
|
};
|
|
function handleNext() {
|
|
if (incomingMessageQueue.size === 0) {
|
|
clearInterval(handleNextInterval);
|
|
handleNextInterval = null;
|
|
return;
|
|
}
|
|
var json = incomingMessageQueue.dequeue();
|
|
if (!json)
|
|
return;
|
|
let subid = getSubscriptionId(json);
|
|
if (subid) {
|
|
let so = openSubs[subid];
|
|
if (so && so.alreadyHaveEvent && so.alreadyHaveEvent(getHex64(json, "id"), url)) {
|
|
return;
|
|
}
|
|
}
|
|
try {
|
|
let data = JSON.parse(json);
|
|
switch (data[0]) {
|
|
case "EVENT": {
|
|
let id2 = data[1];
|
|
let event = data[2];
|
|
if (validateEvent(event) && openSubs[id2] && (openSubs[id2].skipVerification || verifySignature(event)) && matchFilters(openSubs[id2].filters, event)) {
|
|
openSubs[id2];
|
|
(subListeners[id2]?.event || []).forEach((cb) => cb(event));
|
|
}
|
|
return;
|
|
}
|
|
case "COUNT":
|
|
let id = data[1];
|
|
let payload = data[2];
|
|
if (openSubs[id]) {
|
|
;
|
|
(subListeners[id]?.count || []).forEach((cb) => cb(payload));
|
|
}
|
|
return;
|
|
case "EOSE": {
|
|
let id2 = data[1];
|
|
if (id2 in subListeners) {
|
|
subListeners[id2].eose.forEach((cb) => cb());
|
|
subListeners[id2].eose = [];
|
|
}
|
|
return;
|
|
}
|
|
case "OK": {
|
|
let id2 = data[1];
|
|
let ok = data[2];
|
|
let reason = data[3] || "";
|
|
if (id2 in pubListeners) {
|
|
let { resolve: resolve2, reject: reject2 } = pubListeners[id2];
|
|
if (ok)
|
|
resolve2(null);
|
|
else
|
|
reject2(new Error(reason));
|
|
}
|
|
return;
|
|
}
|
|
case "NOTICE":
|
|
let notice = data[1];
|
|
listeners.notice.forEach((cb) => cb(notice));
|
|
return;
|
|
case "AUTH": {
|
|
let challenge = data[1];
|
|
listeners.auth?.forEach((cb) => cb(challenge));
|
|
return;
|
|
}
|
|
}
|
|
} catch (err) {
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
return connectionPromise;
|
|
}
|
|
function connected() {
|
|
return ws?.readyState === 1;
|
|
}
|
|
async function connect() {
|
|
if (connected())
|
|
return;
|
|
await connectRelay();
|
|
}
|
|
async function trySend(params) {
|
|
let msg = JSON.stringify(params);
|
|
if (!connected()) {
|
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
if (!connected()) {
|
|
return;
|
|
}
|
|
}
|
|
try {
|
|
ws.send(msg);
|
|
} catch (err) {
|
|
console.log(err);
|
|
}
|
|
}
|
|
const sub = (filters, {
|
|
verb = "REQ",
|
|
skipVerification = false,
|
|
alreadyHaveEvent = null,
|
|
id = Math.random().toString().slice(2)
|
|
} = {}) => {
|
|
let subid = id;
|
|
openSubs[subid] = {
|
|
id: subid,
|
|
filters,
|
|
skipVerification,
|
|
alreadyHaveEvent
|
|
};
|
|
trySend([verb, subid, ...filters]);
|
|
let subscription = {
|
|
sub: (newFilters, newOpts = {}) => sub(newFilters || filters, {
|
|
skipVerification: newOpts.skipVerification || skipVerification,
|
|
alreadyHaveEvent: newOpts.alreadyHaveEvent || alreadyHaveEvent,
|
|
id: subid
|
|
}),
|
|
unsub: () => {
|
|
delete openSubs[subid];
|
|
delete subListeners[subid];
|
|
trySend(["CLOSE", subid]);
|
|
},
|
|
on: (type, cb) => {
|
|
subListeners[subid] = subListeners[subid] || {
|
|
event: [],
|
|
count: [],
|
|
eose: []
|
|
};
|
|
subListeners[subid][type].push(cb);
|
|
},
|
|
off: (type, cb) => {
|
|
let listeners2 = subListeners[subid];
|
|
let idx = listeners2[type].indexOf(cb);
|
|
if (idx >= 0)
|
|
listeners2[type].splice(idx, 1);
|
|
},
|
|
get events() {
|
|
return eventsGenerator(subscription);
|
|
}
|
|
};
|
|
return subscription;
|
|
};
|
|
function _publishEvent(event, type) {
|
|
return new Promise((resolve, reject) => {
|
|
if (!event.id) {
|
|
reject(new Error(`event ${event} has no id`));
|
|
return;
|
|
}
|
|
let id = event.id;
|
|
trySend([type, event]);
|
|
pubListeners[id] = { resolve, reject };
|
|
});
|
|
}
|
|
return {
|
|
url,
|
|
sub,
|
|
on: (type, cb) => {
|
|
listeners[type].push(cb);
|
|
if (type === "connect" && ws?.readyState === 1) {
|
|
;
|
|
cb();
|
|
}
|
|
},
|
|
off: (type, cb) => {
|
|
let index = listeners[type].indexOf(cb);
|
|
if (index !== -1)
|
|
listeners[type].splice(index, 1);
|
|
},
|
|
list: (filters, opts) => new Promise((resolve) => {
|
|
let s = sub(filters, opts);
|
|
let events = [];
|
|
let timeout = setTimeout(() => {
|
|
s.unsub();
|
|
resolve(events);
|
|
}, listTimeout);
|
|
s.on("eose", () => {
|
|
s.unsub();
|
|
clearTimeout(timeout);
|
|
resolve(events);
|
|
});
|
|
s.on("event", (event) => {
|
|
events.push(event);
|
|
});
|
|
}),
|
|
get: (filter, opts) => new Promise((resolve) => {
|
|
let s = sub([filter], opts);
|
|
let timeout = setTimeout(() => {
|
|
s.unsub();
|
|
resolve(null);
|
|
}, getTimeout);
|
|
s.on("event", (event) => {
|
|
s.unsub();
|
|
clearTimeout(timeout);
|
|
resolve(event);
|
|
});
|
|
}),
|
|
count: (filters) => new Promise((resolve) => {
|
|
let s = sub(filters, { ...sub, verb: "COUNT" });
|
|
let timeout = setTimeout(() => {
|
|
s.unsub();
|
|
resolve(null);
|
|
}, countTimeout);
|
|
s.on("count", (event) => {
|
|
s.unsub();
|
|
clearTimeout(timeout);
|
|
resolve(event);
|
|
});
|
|
}),
|
|
async publish(event) {
|
|
await _publishEvent(event, "EVENT");
|
|
},
|
|
async auth(event) {
|
|
await _publishEvent(event, "AUTH");
|
|
},
|
|
connect,
|
|
close() {
|
|
listeners = newListeners();
|
|
subListeners = {};
|
|
pubListeners = {};
|
|
if (ws?.readyState === WebSocket.OPEN) {
|
|
ws.close();
|
|
}
|
|
},
|
|
get status() {
|
|
return ws?.readyState ?? 3;
|
|
}
|
|
};
|
|
}
|
|
async function* eventsGenerator(sub) {
|
|
let nextResolve;
|
|
const eventQueue = [];
|
|
const pushToQueue = (event) => {
|
|
if (nextResolve) {
|
|
nextResolve(event);
|
|
nextResolve = void 0;
|
|
} else {
|
|
eventQueue.push(event);
|
|
}
|
|
};
|
|
sub.on("event", pushToQueue);
|
|
try {
|
|
while (true) {
|
|
if (eventQueue.length > 0) {
|
|
yield eventQueue.shift();
|
|
} else {
|
|
const event = await new Promise((resolve) => {
|
|
nextResolve = resolve;
|
|
});
|
|
yield event;
|
|
}
|
|
}
|
|
} finally {
|
|
sub.off("event", pushToQueue);
|
|
}
|
|
}
|
|
|
|
// pool.ts
|
|
var SimplePool = class {
|
|
_conn;
|
|
_seenOn = {};
|
|
batchedByKey = {};
|
|
eoseSubTimeout;
|
|
getTimeout;
|
|
seenOnEnabled = true;
|
|
batchInterval = 100;
|
|
constructor(options = {}) {
|
|
this._conn = {};
|
|
this.eoseSubTimeout = options.eoseSubTimeout || 3400;
|
|
this.getTimeout = options.getTimeout || 3400;
|
|
this.seenOnEnabled = options.seenOnEnabled !== false;
|
|
this.batchInterval = options.batchInterval || 100;
|
|
}
|
|
close(relays) {
|
|
relays.forEach((url) => {
|
|
let relay = this._conn[normalizeURL(url)];
|
|
if (relay)
|
|
relay.close();
|
|
});
|
|
}
|
|
async ensureRelay(url) {
|
|
const nm = normalizeURL(url);
|
|
if (!this._conn[nm]) {
|
|
this._conn[nm] = relayInit(nm, {
|
|
getTimeout: this.getTimeout * 0.9,
|
|
listTimeout: this.getTimeout * 0.9
|
|
});
|
|
}
|
|
const relay = this._conn[nm];
|
|
await relay.connect();
|
|
return relay;
|
|
}
|
|
sub(relays, filters, opts) {
|
|
let _knownIds = /* @__PURE__ */ new Set();
|
|
let modifiedOpts = { ...opts || {} };
|
|
modifiedOpts.alreadyHaveEvent = (id, url) => {
|
|
if (opts?.alreadyHaveEvent?.(id, url)) {
|
|
return true;
|
|
}
|
|
if (this.seenOnEnabled) {
|
|
let set = this._seenOn[id] || /* @__PURE__ */ new Set();
|
|
set.add(url);
|
|
this._seenOn[id] = set;
|
|
}
|
|
return _knownIds.has(id);
|
|
};
|
|
let subs = [];
|
|
let eventListeners = /* @__PURE__ */ new Set();
|
|
let eoseListeners = /* @__PURE__ */ new Set();
|
|
let eosesMissing = relays.length;
|
|
let eoseSent = false;
|
|
let eoseTimeout = setTimeout(
|
|
() => {
|
|
eoseSent = true;
|
|
for (let cb of eoseListeners.values())
|
|
cb();
|
|
},
|
|
opts?.eoseSubTimeout || this.eoseSubTimeout
|
|
);
|
|
relays.filter((r, i, a) => a.indexOf(r) === i).forEach(async (relay) => {
|
|
let r;
|
|
try {
|
|
r = await this.ensureRelay(relay);
|
|
} catch (err) {
|
|
handleEose();
|
|
return;
|
|
}
|
|
if (!r)
|
|
return;
|
|
let s = r.sub(filters, modifiedOpts);
|
|
s.on("event", (event) => {
|
|
_knownIds.add(event.id);
|
|
for (let cb of eventListeners.values())
|
|
cb(event);
|
|
});
|
|
s.on("eose", () => {
|
|
if (eoseSent)
|
|
return;
|
|
handleEose();
|
|
});
|
|
subs.push(s);
|
|
function handleEose() {
|
|
eosesMissing--;
|
|
if (eosesMissing === 0) {
|
|
clearTimeout(eoseTimeout);
|
|
for (let cb of eoseListeners.values())
|
|
cb();
|
|
}
|
|
}
|
|
});
|
|
let greaterSub = {
|
|
sub(filters2, opts2) {
|
|
subs.forEach((sub) => sub.sub(filters2, opts2));
|
|
return greaterSub;
|
|
},
|
|
unsub() {
|
|
subs.forEach((sub) => sub.unsub());
|
|
},
|
|
on(type, cb) {
|
|
if (type === "event") {
|
|
eventListeners.add(cb);
|
|
} else if (type === "eose") {
|
|
eoseListeners.add(cb);
|
|
}
|
|
},
|
|
off(type, cb) {
|
|
if (type === "event") {
|
|
eventListeners.delete(cb);
|
|
} else if (type === "eose")
|
|
eoseListeners.delete(cb);
|
|
},
|
|
get events() {
|
|
return eventsGenerator(greaterSub);
|
|
}
|
|
};
|
|
return greaterSub;
|
|
}
|
|
get(relays, filter, opts) {
|
|
return new Promise((resolve) => {
|
|
let sub = this.sub(relays, [filter], opts);
|
|
let timeout = setTimeout(() => {
|
|
sub.unsub();
|
|
resolve(null);
|
|
}, this.getTimeout);
|
|
sub.on("event", (event) => {
|
|
resolve(event);
|
|
clearTimeout(timeout);
|
|
sub.unsub();
|
|
});
|
|
});
|
|
}
|
|
list(relays, filters, opts) {
|
|
return new Promise((resolve) => {
|
|
let events = [];
|
|
let sub = this.sub(relays, filters, opts);
|
|
sub.on("event", (event) => {
|
|
events.push(event);
|
|
});
|
|
sub.on("eose", () => {
|
|
sub.unsub();
|
|
resolve(events);
|
|
});
|
|
});
|
|
}
|
|
batchedList(batchKey, relays, filters) {
|
|
return new Promise((resolve) => {
|
|
if (!this.batchedByKey[batchKey]) {
|
|
this.batchedByKey[batchKey] = [
|
|
{
|
|
filters,
|
|
relays,
|
|
resolve,
|
|
events: []
|
|
}
|
|
];
|
|
setTimeout(() => {
|
|
Object.keys(this.batchedByKey).forEach(async (batchKey2) => {
|
|
const batchedRequests = this.batchedByKey[batchKey2];
|
|
const filters2 = [];
|
|
const relays2 = [];
|
|
batchedRequests.forEach((br) => {
|
|
filters2.push(...br.filters);
|
|
relays2.push(...br.relays);
|
|
});
|
|
const sub = this.sub(relays2, [mergeFilters(...filters2)]);
|
|
sub.on("event", (event) => {
|
|
batchedRequests.forEach((br) => matchFilters(br.filters, event) && br.events.push(event));
|
|
});
|
|
sub.on("eose", () => {
|
|
sub.unsub();
|
|
batchedRequests.forEach((br) => br.resolve(br.events));
|
|
});
|
|
delete this.batchedByKey[batchKey2];
|
|
});
|
|
}, this.batchInterval);
|
|
} else {
|
|
this.batchedByKey[batchKey].push({
|
|
filters,
|
|
relays,
|
|
resolve,
|
|
events: []
|
|
});
|
|
}
|
|
});
|
|
}
|
|
publish(relays, event) {
|
|
return relays.map(async (relay) => {
|
|
let r = await this.ensureRelay(relay);
|
|
return r.publish(event);
|
|
});
|
|
}
|
|
seenOn(id) {
|
|
return Array.from(this._seenOn[id]?.values?.() || []);
|
|
}
|
|
};
|
|
|
|
// nip19.ts
|
|
var nip19_exports = {};
|
|
__export(nip19_exports, {
|
|
BECH32_REGEX: () => BECH32_REGEX,
|
|
decode: () => decode,
|
|
naddrEncode: () => naddrEncode,
|
|
neventEncode: () => neventEncode,
|
|
noteEncode: () => noteEncode,
|
|
nprofileEncode: () => nprofileEncode,
|
|
npubEncode: () => npubEncode,
|
|
nrelayEncode: () => nrelayEncode,
|
|
nsecEncode: () => nsecEncode
|
|
});
|
|
import { bytesToHex as bytesToHex3, concatBytes, hexToBytes } from "@noble/hashes/utils";
|
|
import { bech32 } from "@scure/base";
|
|
var Bech32MaxSize = 5e3;
|
|
var BECH32_REGEX = /[\x21-\x7E]{1,83}1[023456789acdefghjklmnpqrstuvwxyz]{6,}/;
|
|
function integerToUint8Array(number) {
|
|
const uint8Array = new Uint8Array(4);
|
|
uint8Array[0] = number >> 24 & 255;
|
|
uint8Array[1] = number >> 16 & 255;
|
|
uint8Array[2] = number >> 8 & 255;
|
|
uint8Array[3] = number & 255;
|
|
return uint8Array;
|
|
}
|
|
function decode(nip19) {
|
|
let { prefix, words } = bech32.decode(nip19, 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: bytesToHex3(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: bytesToHex3(tlv[0][0]),
|
|
relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : [],
|
|
author: tlv[2]?.[0] ? bytesToHex3(tlv[2][0]) : void 0,
|
|
kind: tlv[3]?.[0] ? parseInt(bytesToHex3(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: bytesToHex3(tlv[2][0]),
|
|
kind: parseInt(bytesToHex3(tlv[3][0]), 16),
|
|
relays: tlv[1] ? tlv[1].map((d) => utf8Decoder.decode(d)) : []
|
|
}
|
|
};
|
|
}
|
|
case "nrelay": {
|
|
let tlv = parseTLV(data);
|
|
if (!tlv[0]?.[0])
|
|
throw new Error("missing TLV 0 for nrelay");
|
|
return {
|
|
type: "nrelay",
|
|
data: utf8Decoder.decode(tlv[0][0])
|
|
};
|
|
}
|
|
case "nsec":
|
|
case "npub":
|
|
case "note":
|
|
return { type: prefix, data: bytesToHex3(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];
|
|
if (!l)
|
|
throw new Error(`malformed TLV ${t}`);
|
|
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;
|
|
}
|
|
function nsecEncode(hex) {
|
|
return encodeBytes("nsec", hex);
|
|
}
|
|
function npubEncode(hex) {
|
|
return encodeBytes("npub", hex);
|
|
}
|
|
function noteEncode(hex) {
|
|
return encodeBytes("note", hex);
|
|
}
|
|
function encodeBech32(prefix, data) {
|
|
let words = bech32.toWords(data);
|
|
return bech32.encode(prefix, words, Bech32MaxSize);
|
|
}
|
|
function encodeBytes(prefix, hex) {
|
|
let data = hexToBytes(hex);
|
|
return encodeBech32(prefix, data);
|
|
}
|
|
function nprofileEncode(profile) {
|
|
let data = encodeTLV({
|
|
0: [hexToBytes(profile.pubkey)],
|
|
1: (profile.relays || []).map((url) => utf8Encoder.encode(url))
|
|
});
|
|
return encodeBech32("nprofile", data);
|
|
}
|
|
function neventEncode(event) {
|
|
let kindArray;
|
|
if (event.kind != void 0) {
|
|
kindArray = integerToUint8Array(event.kind);
|
|
}
|
|
let data = encodeTLV({
|
|
0: [hexToBytes(event.id)],
|
|
1: (event.relays || []).map((url) => utf8Encoder.encode(url)),
|
|
2: event.author ? [hexToBytes(event.author)] : [],
|
|
3: kindArray ? [new Uint8Array(kindArray)] : []
|
|
});
|
|
return encodeBech32("nevent", data);
|
|
}
|
|
function naddrEncode(addr) {
|
|
let kind = new ArrayBuffer(4);
|
|
new DataView(kind).setUint32(0, addr.kind, false);
|
|
let data = encodeTLV({
|
|
0: [utf8Encoder.encode(addr.identifier)],
|
|
1: (addr.relays || []).map((url) => utf8Encoder.encode(url)),
|
|
2: [hexToBytes(addr.pubkey)],
|
|
3: [new Uint8Array(kind)]
|
|
});
|
|
return encodeBech32("naddr", data);
|
|
}
|
|
function nrelayEncode(url) {
|
|
let data = encodeTLV({
|
|
0: [utf8Encoder.encode(url)]
|
|
});
|
|
return encodeBech32("nrelay", data);
|
|
}
|
|
function encodeTLV(tlv) {
|
|
let entries = [];
|
|
Object.entries(tlv).forEach(([t, vs]) => {
|
|
vs.forEach((v) => {
|
|
let entry = new Uint8Array(v.length + 2);
|
|
entry.set([parseInt(t)], 0);
|
|
entry.set([v.length], 1);
|
|
entry.set(v, 2);
|
|
entries.push(entry);
|
|
});
|
|
});
|
|
return concatBytes(...entries);
|
|
}
|
|
|
|
// references.ts
|
|
var mentionRegex = /\bnostr:((note|npub|naddr|nevent|nprofile)1\w+)\b|#\[(\d+)\]/g;
|
|
function parseReferences(evt) {
|
|
let references = [];
|
|
for (let ref of evt.content.matchAll(mentionRegex)) {
|
|
if (ref[2]) {
|
|
try {
|
|
let { type, data } = decode(ref[1]);
|
|
switch (type) {
|
|
case "npub": {
|
|
references.push({
|
|
text: ref[0],
|
|
profile: { pubkey: data, relays: [] }
|
|
});
|
|
break;
|
|
}
|
|
case "nprofile": {
|
|
references.push({
|
|
text: ref[0],
|
|
profile: data
|
|
});
|
|
break;
|
|
}
|
|
case "note": {
|
|
references.push({
|
|
text: ref[0],
|
|
event: { id: data, relays: [] }
|
|
});
|
|
break;
|
|
}
|
|
case "nevent": {
|
|
references.push({
|
|
text: ref[0],
|
|
event: data
|
|
});
|
|
break;
|
|
}
|
|
case "naddr": {
|
|
references.push({
|
|
text: ref[0],
|
|
address: data
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
} catch (err) {
|
|
}
|
|
} else if (ref[3]) {
|
|
let idx = parseInt(ref[3], 10);
|
|
let tag = evt.tags[idx];
|
|
if (!tag)
|
|
continue;
|
|
switch (tag[0]) {
|
|
case "p": {
|
|
references.push({
|
|
text: ref[0],
|
|
profile: { pubkey: tag[1], relays: tag[2] ? [tag[2]] : [] }
|
|
});
|
|
break;
|
|
}
|
|
case "e": {
|
|
references.push({
|
|
text: ref[0],
|
|
event: { id: tag[1], relays: tag[2] ? [tag[2]] : [] }
|
|
});
|
|
break;
|
|
}
|
|
case "a": {
|
|
try {
|
|
let [kind, pubkey, identifier] = tag[1].split(":");
|
|
references.push({
|
|
text: ref[0],
|
|
address: {
|
|
identifier,
|
|
pubkey,
|
|
kind: parseInt(kind, 10),
|
|
relays: tag[2] ? [tag[2]] : []
|
|
}
|
|
});
|
|
} catch (err) {
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return references;
|
|
}
|
|
|
|
// nip04.ts
|
|
var nip04_exports = {};
|
|
__export(nip04_exports, {
|
|
decrypt: () => decrypt,
|
|
encrypt: () => encrypt
|
|
});
|
|
import { randomBytes } from "@noble/hashes/utils";
|
|
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
import { base64 } from "@scure/base";
|
|
if (typeof crypto !== "undefined" && !crypto.subtle && crypto.webcrypto) {
|
|
crypto.subtle = crypto.webcrypto.subtle;
|
|
}
|
|
async function encrypt(privkey, pubkey, text) {
|
|
const key = secp256k1.getSharedSecret(privkey, "02" + pubkey);
|
|
const normalizedKey = getNormalizedX(key);
|
|
let iv = Uint8Array.from(randomBytes(16));
|
|
let plaintext = utf8Encoder.encode(text);
|
|
let cryptoKey = await crypto.subtle.importKey("raw", normalizedKey, { name: "AES-CBC" }, false, ["encrypt"]);
|
|
let ciphertext = await crypto.subtle.encrypt({ name: "AES-CBC", iv }, cryptoKey, plaintext);
|
|
let ctb64 = base64.encode(new Uint8Array(ciphertext));
|
|
let ivb64 = base64.encode(new Uint8Array(iv.buffer));
|
|
return `${ctb64}?iv=${ivb64}`;
|
|
}
|
|
async function decrypt(privkey, pubkey, data) {
|
|
let [ctb64, ivb64] = data.split("?iv=");
|
|
let key = secp256k1.getSharedSecret(privkey, "02" + pubkey);
|
|
let normalizedKey = getNormalizedX(key);
|
|
let cryptoKey = await crypto.subtle.importKey("raw", normalizedKey, { name: "AES-CBC" }, false, ["decrypt"]);
|
|
let ciphertext = base64.decode(ctb64);
|
|
let iv = base64.decode(ivb64);
|
|
let plaintext = await crypto.subtle.decrypt({ name: "AES-CBC", iv }, cryptoKey, ciphertext);
|
|
let text = utf8Decoder.decode(plaintext);
|
|
return text;
|
|
}
|
|
function getNormalizedX(key) {
|
|
return key.slice(1, 33);
|
|
}
|
|
|
|
// nip05.ts
|
|
var nip05_exports = {};
|
|
__export(nip05_exports, {
|
|
NIP05_REGEX: () => NIP05_REGEX,
|
|
queryProfile: () => queryProfile,
|
|
searchDomain: () => searchDomain,
|
|
useFetchImplementation: () => useFetchImplementation
|
|
});
|
|
var NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w.-]+)$/;
|
|
var _fetch;
|
|
try {
|
|
_fetch = fetch;
|
|
} catch {
|
|
}
|
|
function useFetchImplementation(fetchImplementation) {
|
|
_fetch = fetchImplementation;
|
|
}
|
|
async function searchDomain(domain, query = "") {
|
|
try {
|
|
let res = await (await _fetch(`https://${domain}/.well-known/nostr.json?name=${query}`)).json();
|
|
return res.names;
|
|
} catch (_) {
|
|
return {};
|
|
}
|
|
}
|
|
async function queryProfile(fullname) {
|
|
const match = fullname.match(NIP05_REGEX);
|
|
if (!match)
|
|
return null;
|
|
const [_, name = "_", domain] = match;
|
|
try {
|
|
const res = await _fetch(`https://${domain}/.well-known/nostr.json?name=${name}`);
|
|
const { names, relays } = parseNIP05Result(await res.json());
|
|
const pubkey = names[name];
|
|
return pubkey ? { pubkey, relays: relays?.[pubkey] } : null;
|
|
} catch (_e) {
|
|
return null;
|
|
}
|
|
}
|
|
function parseNIP05Result(json) {
|
|
const result = {
|
|
names: {}
|
|
};
|
|
for (const [name, pubkey] of Object.entries(json.names)) {
|
|
if (typeof name === "string" && typeof pubkey === "string") {
|
|
result.names[name] = pubkey;
|
|
}
|
|
}
|
|
if (json.relays) {
|
|
result.relays = {};
|
|
for (const [pubkey, relays] of Object.entries(json.relays)) {
|
|
if (typeof pubkey === "string" && Array.isArray(relays)) {
|
|
result.relays[pubkey] = relays.filter((relay) => typeof relay === "string");
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// nip06.ts
|
|
var nip06_exports = {};
|
|
__export(nip06_exports, {
|
|
generateSeedWords: () => generateSeedWords,
|
|
privateKeyFromSeedWords: () => privateKeyFromSeedWords,
|
|
validateWords: () => validateWords
|
|
});
|
|
import { bytesToHex as bytesToHex4 } from "@noble/hashes/utils";
|
|
import { wordlist } from "@scure/bip39/wordlists/english";
|
|
import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from "@scure/bip39";
|
|
import { HDKey } from "@scure/bip32";
|
|
function privateKeyFromSeedWords(mnemonic, passphrase) {
|
|
let root = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic, passphrase));
|
|
let privateKey = root.derive(`m/44'/1237'/0'/0/0`).privateKey;
|
|
if (!privateKey)
|
|
throw new Error("could not derive private key");
|
|
return bytesToHex4(privateKey);
|
|
}
|
|
function generateSeedWords() {
|
|
return generateMnemonic(wordlist);
|
|
}
|
|
function validateWords(words) {
|
|
return validateMnemonic(words, wordlist);
|
|
}
|
|
|
|
// nip10.ts
|
|
var nip10_exports = {};
|
|
__export(nip10_exports, {
|
|
parse: () => parse
|
|
});
|
|
function parse(event) {
|
|
const result = {
|
|
reply: void 0,
|
|
root: void 0,
|
|
mentions: [],
|
|
profiles: []
|
|
};
|
|
const eTags = [];
|
|
for (const tag of event.tags) {
|
|
if (tag[0] === "e" && tag[1]) {
|
|
eTags.push(tag);
|
|
}
|
|
if (tag[0] === "p" && tag[1]) {
|
|
result.profiles.push({
|
|
pubkey: tag[1],
|
|
relays: tag[2] ? [tag[2]] : []
|
|
});
|
|
}
|
|
}
|
|
for (let eTagIndex = 0; eTagIndex < eTags.length; eTagIndex++) {
|
|
const eTag = eTags[eTagIndex];
|
|
const [_, eTagEventId, eTagRelayUrl, eTagMarker] = eTag;
|
|
const eventPointer = {
|
|
id: eTagEventId,
|
|
relays: eTagRelayUrl ? [eTagRelayUrl] : []
|
|
};
|
|
const isFirstETag = eTagIndex === 0;
|
|
const isLastETag = eTagIndex === eTags.length - 1;
|
|
if (eTagMarker === "root") {
|
|
result.root = eventPointer;
|
|
continue;
|
|
}
|
|
if (eTagMarker === "reply") {
|
|
result.reply = eventPointer;
|
|
continue;
|
|
}
|
|
if (eTagMarker === "mention") {
|
|
result.mentions.push(eventPointer);
|
|
continue;
|
|
}
|
|
if (isFirstETag) {
|
|
result.root = eventPointer;
|
|
continue;
|
|
}
|
|
if (isLastETag) {
|
|
result.reply = eventPointer;
|
|
continue;
|
|
}
|
|
result.mentions.push(eventPointer);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// nip13.ts
|
|
var nip13_exports = {};
|
|
__export(nip13_exports, {
|
|
getPow: () => getPow,
|
|
minePow: () => minePow
|
|
});
|
|
function getPow(hex) {
|
|
let count = 0;
|
|
for (let i = 0; i < hex.length; i++) {
|
|
const nibble = parseInt(hex[i], 16);
|
|
if (nibble === 0) {
|
|
count += 4;
|
|
} else {
|
|
count += Math.clz32(nibble) - 28;
|
|
break;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
function minePow(unsigned, difficulty) {
|
|
let count = 0;
|
|
const event = unsigned;
|
|
const tag = ["nonce", count.toString(), difficulty.toString()];
|
|
event.tags.push(tag);
|
|
while (true) {
|
|
const now = Math.floor(new Date().getTime() / 1e3);
|
|
if (now !== event.created_at) {
|
|
count = 0;
|
|
event.created_at = now;
|
|
}
|
|
tag[1] = (++count).toString();
|
|
event.id = getEventHash(event);
|
|
if (getPow(event.id) >= difficulty) {
|
|
break;
|
|
}
|
|
}
|
|
return event;
|
|
}
|
|
|
|
// nip18.ts
|
|
var nip18_exports = {};
|
|
__export(nip18_exports, {
|
|
finishRepostEvent: () => finishRepostEvent,
|
|
getRepostedEvent: () => getRepostedEvent,
|
|
getRepostedEventPointer: () => getRepostedEventPointer
|
|
});
|
|
function finishRepostEvent(t, reposted, relayUrl, privateKey) {
|
|
return finishEvent(
|
|
{
|
|
kind: 6 /* Repost */,
|
|
tags: [...t.tags ?? [], ["e", reposted.id, relayUrl], ["p", reposted.pubkey]],
|
|
content: t.content === "" ? "" : JSON.stringify(reposted),
|
|
created_at: t.created_at
|
|
},
|
|
privateKey
|
|
);
|
|
}
|
|
function getRepostedEventPointer(event) {
|
|
if (event.kind !== 6 /* Repost */) {
|
|
return void 0;
|
|
}
|
|
let lastETag;
|
|
let lastPTag;
|
|
for (let i = event.tags.length - 1; i >= 0 && (lastETag === void 0 || lastPTag === void 0); i--) {
|
|
const tag = event.tags[i];
|
|
if (tag.length >= 2) {
|
|
if (tag[0] === "e" && lastETag === void 0) {
|
|
lastETag = tag;
|
|
} else if (tag[0] === "p" && lastPTag === void 0) {
|
|
lastPTag = tag;
|
|
}
|
|
}
|
|
}
|
|
if (lastETag === void 0) {
|
|
return void 0;
|
|
}
|
|
return {
|
|
id: lastETag[1],
|
|
relays: [lastETag[2], lastPTag?.[2]].filter((x) => typeof x === "string"),
|
|
author: lastPTag?.[1]
|
|
};
|
|
}
|
|
function getRepostedEvent(event, { skipVerification } = {}) {
|
|
const pointer = getRepostedEventPointer(event);
|
|
if (pointer === void 0 || event.content === "") {
|
|
return void 0;
|
|
}
|
|
let repostedEvent;
|
|
try {
|
|
repostedEvent = JSON.parse(event.content);
|
|
} catch (error) {
|
|
return void 0;
|
|
}
|
|
if (repostedEvent.id !== pointer.id) {
|
|
return void 0;
|
|
}
|
|
if (!skipVerification && !verifySignature(repostedEvent)) {
|
|
return void 0;
|
|
}
|
|
return repostedEvent;
|
|
}
|
|
|
|
// nip21.ts
|
|
var nip21_exports = {};
|
|
__export(nip21_exports, {
|
|
NOSTR_URI_REGEX: () => NOSTR_URI_REGEX,
|
|
parse: () => parse2,
|
|
test: () => test
|
|
});
|
|
var NOSTR_URI_REGEX = new RegExp(`nostr:(${BECH32_REGEX.source})`);
|
|
function test(value) {
|
|
return typeof value === "string" && new RegExp(`^${NOSTR_URI_REGEX.source}$`).test(value);
|
|
}
|
|
function parse2(uri) {
|
|
const match = uri.match(new RegExp(`^${NOSTR_URI_REGEX.source}$`));
|
|
if (!match)
|
|
throw new Error(`Invalid Nostr URI: ${uri}`);
|
|
return {
|
|
uri: match[0],
|
|
value: match[1],
|
|
decoded: decode(match[1])
|
|
};
|
|
}
|
|
|
|
// nip25.ts
|
|
var nip25_exports = {};
|
|
__export(nip25_exports, {
|
|
finishReactionEvent: () => finishReactionEvent,
|
|
getReactedEventPointer: () => getReactedEventPointer
|
|
});
|
|
function finishReactionEvent(t, reacted, privateKey) {
|
|
const inheritedTags = reacted.tags.filter((tag) => tag.length >= 2 && (tag[0] === "e" || tag[0] === "p"));
|
|
return finishEvent(
|
|
{
|
|
...t,
|
|
kind: 7 /* Reaction */,
|
|
tags: [...t.tags ?? [], ...inheritedTags, ["e", reacted.id], ["p", reacted.pubkey]],
|
|
content: t.content ?? "+"
|
|
},
|
|
privateKey
|
|
);
|
|
}
|
|
function getReactedEventPointer(event) {
|
|
if (event.kind !== 7 /* Reaction */) {
|
|
return void 0;
|
|
}
|
|
let lastETag;
|
|
let lastPTag;
|
|
for (let i = event.tags.length - 1; i >= 0 && (lastETag === void 0 || lastPTag === void 0); i--) {
|
|
const tag = event.tags[i];
|
|
if (tag.length >= 2) {
|
|
if (tag[0] === "e" && lastETag === void 0) {
|
|
lastETag = tag;
|
|
} else if (tag[0] === "p" && lastPTag === void 0) {
|
|
lastPTag = tag;
|
|
}
|
|
}
|
|
}
|
|
if (lastETag === void 0 || lastPTag === void 0) {
|
|
return void 0;
|
|
}
|
|
return {
|
|
id: lastETag[1],
|
|
relays: [lastETag[2], lastPTag[2]].filter((x) => x !== void 0),
|
|
author: lastPTag[1]
|
|
};
|
|
}
|
|
|
|
// nip26.ts
|
|
var nip26_exports = {};
|
|
__export(nip26_exports, {
|
|
createDelegation: () => createDelegation,
|
|
getDelegator: () => getDelegator
|
|
});
|
|
import { schnorr as schnorr3 } from "@noble/curves/secp256k1";
|
|
import { bytesToHex as bytesToHex5 } from "@noble/hashes/utils";
|
|
import { sha256 as sha2562 } from "@noble/hashes/sha256";
|
|
function createDelegation(privateKey, parameters) {
|
|
let conditions = [];
|
|
if ((parameters.kind || -1) >= 0)
|
|
conditions.push(`kind=${parameters.kind}`);
|
|
if (parameters.until)
|
|
conditions.push(`created_at<${parameters.until}`);
|
|
if (parameters.since)
|
|
conditions.push(`created_at>${parameters.since}`);
|
|
let cond = conditions.join("&");
|
|
if (cond === "")
|
|
throw new Error("refusing to create a delegation without any conditions");
|
|
let sighash = sha2562(utf8Encoder.encode(`nostr:delegation:${parameters.pubkey}:${cond}`));
|
|
let sig = bytesToHex5(schnorr3.sign(sighash, privateKey));
|
|
return {
|
|
from: getPublicKey(privateKey),
|
|
to: parameters.pubkey,
|
|
cond,
|
|
sig
|
|
};
|
|
}
|
|
function getDelegator(event) {
|
|
let tag = event.tags.find((tag2) => tag2[0] === "delegation" && tag2.length >= 4);
|
|
if (!tag)
|
|
return null;
|
|
let pubkey = tag[1];
|
|
let cond = tag[2];
|
|
let sig = tag[3];
|
|
let conditions = cond.split("&");
|
|
for (let i = 0; i < conditions.length; i++) {
|
|
let [key, operator, value] = conditions[i].split(/\b/);
|
|
if (key === "kind" && operator === "=" && event.kind === parseInt(value))
|
|
continue;
|
|
else if (key === "created_at" && operator === "<" && event.created_at < parseInt(value))
|
|
continue;
|
|
else if (key === "created_at" && operator === ">" && event.created_at > parseInt(value))
|
|
continue;
|
|
else
|
|
return null;
|
|
}
|
|
let sighash = sha2562(utf8Encoder.encode(`nostr:delegation:${event.pubkey}:${cond}`));
|
|
if (!schnorr3.verify(sig, sighash, pubkey))
|
|
return null;
|
|
return pubkey;
|
|
}
|
|
|
|
// nip27.ts
|
|
var nip27_exports = {};
|
|
__export(nip27_exports, {
|
|
matchAll: () => matchAll,
|
|
regex: () => regex,
|
|
replaceAll: () => replaceAll
|
|
});
|
|
var regex = () => new RegExp(`\\b${NOSTR_URI_REGEX.source}\\b`, "g");
|
|
function* matchAll(content) {
|
|
const matches = content.matchAll(regex());
|
|
for (const match of matches) {
|
|
try {
|
|
const [uri, value] = match;
|
|
yield {
|
|
uri,
|
|
value,
|
|
decoded: decode(value),
|
|
start: match.index,
|
|
end: match.index + uri.length
|
|
};
|
|
} catch (_e) {
|
|
}
|
|
}
|
|
}
|
|
function replaceAll(content, replacer) {
|
|
return content.replaceAll(regex(), (uri, value) => {
|
|
return replacer({
|
|
uri,
|
|
value,
|
|
decoded: decode(value)
|
|
});
|
|
});
|
|
}
|
|
|
|
// nip28.ts
|
|
var nip28_exports = {};
|
|
__export(nip28_exports, {
|
|
channelCreateEvent: () => channelCreateEvent,
|
|
channelHideMessageEvent: () => channelHideMessageEvent,
|
|
channelMessageEvent: () => channelMessageEvent,
|
|
channelMetadataEvent: () => channelMetadataEvent,
|
|
channelMuteUserEvent: () => channelMuteUserEvent
|
|
});
|
|
var channelCreateEvent = (t, privateKey) => {
|
|
let content;
|
|
if (typeof t.content === "object") {
|
|
content = JSON.stringify(t.content);
|
|
} else if (typeof t.content === "string") {
|
|
content = t.content;
|
|
} else {
|
|
return void 0;
|
|
}
|
|
return finishEvent(
|
|
{
|
|
kind: 40 /* ChannelCreation */,
|
|
tags: [...t.tags ?? []],
|
|
content,
|
|
created_at: t.created_at
|
|
},
|
|
privateKey
|
|
);
|
|
};
|
|
var channelMetadataEvent = (t, privateKey) => {
|
|
let content;
|
|
if (typeof t.content === "object") {
|
|
content = JSON.stringify(t.content);
|
|
} else if (typeof t.content === "string") {
|
|
content = t.content;
|
|
} else {
|
|
return void 0;
|
|
}
|
|
return finishEvent(
|
|
{
|
|
kind: 41 /* ChannelMetadata */,
|
|
tags: [["e", t.channel_create_event_id], ...t.tags ?? []],
|
|
content,
|
|
created_at: t.created_at
|
|
},
|
|
privateKey
|
|
);
|
|
};
|
|
var channelMessageEvent = (t, privateKey) => {
|
|
const tags = [["e", t.channel_create_event_id, t.relay_url, "root"]];
|
|
if (t.reply_to_channel_message_event_id) {
|
|
tags.push(["e", t.reply_to_channel_message_event_id, t.relay_url, "reply"]);
|
|
}
|
|
return finishEvent(
|
|
{
|
|
kind: 42 /* ChannelMessage */,
|
|
tags: [...tags, ...t.tags ?? []],
|
|
content: t.content,
|
|
created_at: t.created_at
|
|
},
|
|
privateKey
|
|
);
|
|
};
|
|
var channelHideMessageEvent = (t, privateKey) => {
|
|
let content;
|
|
if (typeof t.content === "object") {
|
|
content = JSON.stringify(t.content);
|
|
} else if (typeof t.content === "string") {
|
|
content = t.content;
|
|
} else {
|
|
return void 0;
|
|
}
|
|
return finishEvent(
|
|
{
|
|
kind: 43 /* ChannelHideMessage */,
|
|
tags: [["e", t.channel_message_event_id], ...t.tags ?? []],
|
|
content,
|
|
created_at: t.created_at
|
|
},
|
|
privateKey
|
|
);
|
|
};
|
|
var channelMuteUserEvent = (t, privateKey) => {
|
|
let content;
|
|
if (typeof t.content === "object") {
|
|
content = JSON.stringify(t.content);
|
|
} else if (typeof t.content === "string") {
|
|
content = t.content;
|
|
} else {
|
|
return void 0;
|
|
}
|
|
return finishEvent(
|
|
{
|
|
kind: 44 /* ChannelMuteUser */,
|
|
tags: [["p", t.pubkey_to_mute], ...t.tags ?? []],
|
|
content,
|
|
created_at: t.created_at
|
|
},
|
|
privateKey
|
|
);
|
|
};
|
|
|
|
// nip39.ts
|
|
var nip39_exports = {};
|
|
__export(nip39_exports, {
|
|
useFetchImplementation: () => useFetchImplementation2,
|
|
validateGithub: () => validateGithub
|
|
});
|
|
var _fetch2;
|
|
try {
|
|
_fetch2 = fetch;
|
|
} catch {
|
|
}
|
|
function useFetchImplementation2(fetchImplementation) {
|
|
_fetch2 = fetchImplementation;
|
|
}
|
|
async function validateGithub(pubkey, username, proof) {
|
|
try {
|
|
let res = await (await _fetch2(`https://gist.github.com/${username}/${proof}/raw`)).text();
|
|
return res === `Verifying that I control the following Nostr public key: ${pubkey}`;
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// nip42.ts
|
|
var nip42_exports = {};
|
|
__export(nip42_exports, {
|
|
authenticate: () => authenticate
|
|
});
|
|
var authenticate = async ({
|
|
challenge,
|
|
relay,
|
|
sign
|
|
}) => {
|
|
const e = {
|
|
kind: 22242 /* ClientAuth */,
|
|
created_at: Math.floor(Date.now() / 1e3),
|
|
tags: [
|
|
["relay", relay.url],
|
|
["challenge", challenge]
|
|
],
|
|
content: ""
|
|
};
|
|
return relay.auth(await sign(e));
|
|
};
|
|
|
|
// nip44.ts
|
|
var nip44_exports = {};
|
|
__export(nip44_exports, {
|
|
decrypt: () => decrypt2,
|
|
encrypt: () => encrypt2,
|
|
utils: () => utils
|
|
});
|
|
import { chacha20 } from "@noble/ciphers/chacha";
|
|
import { ensureBytes, equalBytes } from "@noble/ciphers/utils";
|
|
import { secp256k1 as secp256k12 } from "@noble/curves/secp256k1";
|
|
import { hkdf } from "@noble/hashes/hkdf";
|
|
import { hmac } from "@noble/hashes/hmac";
|
|
import { sha256 as sha2563 } from "@noble/hashes/sha256";
|
|
import { concatBytes as concatBytes2, randomBytes as randomBytes2 } from "@noble/hashes/utils";
|
|
import { base64 as base642 } from "@scure/base";
|
|
var utils = {
|
|
v2: {
|
|
maxPlaintextSize: 65536 - 128,
|
|
minCiphertextSize: 100,
|
|
maxCiphertextSize: 102400,
|
|
getConversationKey(privkeyA, pubkeyB) {
|
|
const key = secp256k12.getSharedSecret(privkeyA, "02" + pubkeyB);
|
|
return key.subarray(1, 33);
|
|
},
|
|
getMessageKeys(conversationKey, salt) {
|
|
const keys = hkdf(sha2563, conversationKey, salt, "nip44-v2", 76);
|
|
return {
|
|
encryption: keys.subarray(0, 32),
|
|
nonce: keys.subarray(32, 44),
|
|
auth: keys.subarray(44, 76)
|
|
};
|
|
},
|
|
calcPadding(len) {
|
|
if (!Number.isSafeInteger(len) || len < 0)
|
|
throw new Error("expected positive integer");
|
|
if (len <= 32)
|
|
return 32;
|
|
const nextpower = 1 << Math.floor(Math.log2(len - 1)) + 1;
|
|
const chunk = nextpower <= 256 ? 32 : nextpower / 8;
|
|
return chunk * (Math.floor((len - 1) / chunk) + 1);
|
|
},
|
|
pad(unpadded) {
|
|
const unpaddedB = utf8Encoder.encode(unpadded);
|
|
const len = unpaddedB.length;
|
|
if (len < 1 || len >= utils.v2.maxPlaintextSize)
|
|
throw new Error("invalid plaintext length: must be between 1b and 64KB");
|
|
const paddedLen = utils.v2.calcPadding(len);
|
|
const zeros = new Uint8Array(paddedLen - len);
|
|
const lenBuf = new Uint8Array(2);
|
|
new DataView(lenBuf.buffer).setUint16(0, len);
|
|
return concatBytes2(lenBuf, unpaddedB, zeros);
|
|
},
|
|
unpad(padded) {
|
|
const unpaddedLen = new DataView(padded.buffer).getUint16(0);
|
|
const unpadded = padded.subarray(2, 2 + unpaddedLen);
|
|
if (unpaddedLen === 0 || unpadded.length !== unpaddedLen || padded.length !== 2 + utils.v2.calcPadding(unpaddedLen))
|
|
throw new Error("invalid padding");
|
|
return utf8Decoder.decode(unpadded);
|
|
}
|
|
}
|
|
};
|
|
function encrypt2(key, plaintext, options = {}) {
|
|
const version = options.version ?? 2;
|
|
if (version !== 2)
|
|
throw new Error("unknown encryption version " + version);
|
|
const salt = options.salt ?? randomBytes2(32);
|
|
ensureBytes(salt, 32);
|
|
const keys = utils.v2.getMessageKeys(key, salt);
|
|
const padded = utils.v2.pad(plaintext);
|
|
const ciphertext = chacha20(keys.encryption, keys.nonce, padded);
|
|
const mac = hmac(sha2563, keys.auth, ciphertext);
|
|
return base642.encode(concatBytes2(new Uint8Array([version]), salt, ciphertext, mac));
|
|
}
|
|
function decrypt2(key, ciphertext) {
|
|
const u = utils.v2;
|
|
ensureBytes(key, 32);
|
|
const clen = ciphertext.length;
|
|
if (clen < u.minCiphertextSize || clen >= u.maxCiphertextSize)
|
|
throw new Error("invalid ciphertext length: " + clen);
|
|
if (ciphertext[0] === "#")
|
|
throw new Error("unknown encryption version");
|
|
let data;
|
|
try {
|
|
data = base642.decode(ciphertext);
|
|
} catch (error) {
|
|
throw new Error("invalid base64: " + error.message);
|
|
}
|
|
const vers = data.subarray(0, 1)[0];
|
|
if (vers !== 2)
|
|
throw new Error("unknown encryption version " + vers);
|
|
const salt = data.subarray(1, 33);
|
|
const ciphertext_ = data.subarray(33, -32);
|
|
const mac = data.subarray(-32);
|
|
const keys = u.getMessageKeys(key, salt);
|
|
const calculatedMac = hmac(sha2563, keys.auth, ciphertext_);
|
|
if (!equalBytes(calculatedMac, mac))
|
|
throw new Error("invalid MAC");
|
|
const padded = chacha20(keys.encryption, keys.nonce, ciphertext_);
|
|
return u.unpad(padded);
|
|
}
|
|
|
|
// nip47.ts
|
|
var nip47_exports = {};
|
|
__export(nip47_exports, {
|
|
makeNwcRequestEvent: () => makeNwcRequestEvent,
|
|
parseConnectionString: () => parseConnectionString
|
|
});
|
|
function parseConnectionString(connectionString) {
|
|
const { pathname, searchParams } = new URL(connectionString);
|
|
const pubkey = pathname;
|
|
const relay = searchParams.get("relay");
|
|
const secret = searchParams.get("secret");
|
|
if (!pubkey || !relay || !secret) {
|
|
throw new Error("invalid connection string");
|
|
}
|
|
return { pubkey, relay, secret };
|
|
}
|
|
async function makeNwcRequestEvent({
|
|
pubkey,
|
|
secret,
|
|
invoice
|
|
}) {
|
|
const content = {
|
|
method: "pay_invoice",
|
|
params: {
|
|
invoice
|
|
}
|
|
};
|
|
const encryptedContent = await encrypt(secret, pubkey, JSON.stringify(content));
|
|
const eventTemplate = {
|
|
kind: 23194 /* NwcRequest */,
|
|
created_at: Math.round(Date.now() / 1e3),
|
|
content: encryptedContent,
|
|
tags: [["p", pubkey]]
|
|
};
|
|
return finishEvent(eventTemplate, secret);
|
|
}
|
|
|
|
// nip57.ts
|
|
var nip57_exports = {};
|
|
__export(nip57_exports, {
|
|
getZapEndpoint: () => getZapEndpoint,
|
|
makeZapReceipt: () => makeZapReceipt,
|
|
makeZapRequest: () => makeZapRequest,
|
|
useFetchImplementation: () => useFetchImplementation3,
|
|
validateZapRequest: () => validateZapRequest
|
|
});
|
|
import { bech32 as bech322 } from "@scure/base";
|
|
var _fetch3;
|
|
try {
|
|
_fetch3 = fetch;
|
|
} catch {
|
|
}
|
|
function useFetchImplementation3(fetchImplementation) {
|
|
_fetch3 = fetchImplementation;
|
|
}
|
|
async function getZapEndpoint(metadata) {
|
|
try {
|
|
let lnurl = "";
|
|
let { lud06, lud16 } = JSON.parse(metadata.content);
|
|
if (lud06) {
|
|
let { words } = bech322.decode(lud06, 1e3);
|
|
let data = bech322.fromWords(words);
|
|
lnurl = utf8Decoder.decode(data);
|
|
} else if (lud16) {
|
|
let [name, domain] = lud16.split("@");
|
|
lnurl = `https://${domain}/.well-known/lnurlp/${name}`;
|
|
} else {
|
|
return null;
|
|
}
|
|
let res = await _fetch3(lnurl);
|
|
let body = await res.json();
|
|
if (body.allowsNostr && body.nostrPubkey) {
|
|
return body.callback;
|
|
}
|
|
} catch (err) {
|
|
}
|
|
return null;
|
|
}
|
|
function makeZapRequest({
|
|
profile,
|
|
event,
|
|
amount,
|
|
relays,
|
|
comment = ""
|
|
}) {
|
|
if (!amount)
|
|
throw new Error("amount not given");
|
|
if (!profile)
|
|
throw new Error("profile not given");
|
|
let zr = {
|
|
kind: 9734,
|
|
created_at: Math.round(Date.now() / 1e3),
|
|
content: comment,
|
|
tags: [
|
|
["p", profile],
|
|
["amount", amount.toString()],
|
|
["relays", ...relays]
|
|
]
|
|
};
|
|
if (event) {
|
|
zr.tags.push(["e", event]);
|
|
}
|
|
return zr;
|
|
}
|
|
function validateZapRequest(zapRequestString) {
|
|
let zapRequest;
|
|
try {
|
|
zapRequest = JSON.parse(zapRequestString);
|
|
} catch (err) {
|
|
return "Invalid zap request JSON.";
|
|
}
|
|
if (!validateEvent(zapRequest))
|
|
return "Zap request is not a valid Nostr event.";
|
|
if (!verifySignature(zapRequest))
|
|
return "Invalid signature on zap request.";
|
|
let p = zapRequest.tags.find(([t, v]) => t === "p" && v);
|
|
if (!p)
|
|
return "Zap request doesn't have a 'p' tag.";
|
|
if (!p[1].match(/^[a-f0-9]{64}$/))
|
|
return "Zap request 'p' tag is not valid hex.";
|
|
let e = zapRequest.tags.find(([t, v]) => t === "e" && v);
|
|
if (e && !e[1].match(/^[a-f0-9]{64}$/))
|
|
return "Zap request 'e' tag is not valid hex.";
|
|
let relays = zapRequest.tags.find(([t, v]) => t === "relays" && v);
|
|
if (!relays)
|
|
return "Zap request doesn't have a 'relays' tag.";
|
|
return null;
|
|
}
|
|
function makeZapReceipt({
|
|
zapRequest,
|
|
preimage,
|
|
bolt11,
|
|
paidAt
|
|
}) {
|
|
let zr = JSON.parse(zapRequest);
|
|
let tagsFromZapRequest = zr.tags.filter(([t]) => t === "e" || t === "p" || t === "a");
|
|
let zap = {
|
|
kind: 9735,
|
|
created_at: Math.round(paidAt.getTime() / 1e3),
|
|
content: "",
|
|
tags: [...tagsFromZapRequest, ["bolt11", bolt11], ["description", zapRequest]]
|
|
};
|
|
if (preimage) {
|
|
zap.tags.push(["preimage", preimage]);
|
|
}
|
|
return zap;
|
|
}
|
|
|
|
// nip98.ts
|
|
var nip98_exports = {};
|
|
__export(nip98_exports, {
|
|
getToken: () => getToken,
|
|
unpackEventFromToken: () => unpackEventFromToken,
|
|
validateEvent: () => validateEvent2,
|
|
validateToken: () => validateToken
|
|
});
|
|
import { base64 as base643 } from "@scure/base";
|
|
var _authorizationScheme = "Nostr ";
|
|
async function getToken(loginUrl, httpMethod, sign, includeAuthorizationScheme = false) {
|
|
if (!loginUrl || !httpMethod)
|
|
throw new Error("Missing loginUrl or httpMethod");
|
|
const event = getBlankEvent(27235 /* HttpAuth */);
|
|
event.tags = [
|
|
["u", loginUrl],
|
|
["method", httpMethod]
|
|
];
|
|
event.created_at = Math.round(new Date().getTime() / 1e3);
|
|
const signedEvent = await sign(event);
|
|
const authorizationScheme = includeAuthorizationScheme ? _authorizationScheme : "";
|
|
return authorizationScheme + base643.encode(utf8Encoder.encode(JSON.stringify(signedEvent)));
|
|
}
|
|
async function validateToken(token, url, method) {
|
|
const event = await unpackEventFromToken(token).catch((error) => {
|
|
throw error;
|
|
});
|
|
const valid = await validateEvent2(event, url, method).catch((error) => {
|
|
throw error;
|
|
});
|
|
return valid;
|
|
}
|
|
async function unpackEventFromToken(token) {
|
|
if (!token) {
|
|
throw new Error("Missing token");
|
|
}
|
|
token = token.replace(_authorizationScheme, "");
|
|
const eventB64 = utf8Decoder.decode(base643.decode(token));
|
|
if (!eventB64 || eventB64.length === 0 || !eventB64.startsWith("{")) {
|
|
throw new Error("Invalid token");
|
|
}
|
|
const event = JSON.parse(eventB64);
|
|
return event;
|
|
}
|
|
async function validateEvent2(event, url, method) {
|
|
if (!event) {
|
|
throw new Error("Invalid nostr event");
|
|
}
|
|
if (!verifySignature(event)) {
|
|
throw new Error("Invalid nostr event, signature invalid");
|
|
}
|
|
if (event.kind !== 27235 /* HttpAuth */) {
|
|
throw new Error("Invalid nostr event, kind invalid");
|
|
}
|
|
if (!event.created_at) {
|
|
throw new Error("Invalid nostr event, created_at invalid");
|
|
}
|
|
if (Math.round(new Date().getTime() / 1e3) - event.created_at > 60) {
|
|
throw new Error("Invalid nostr event, expired");
|
|
}
|
|
const urlTag = event.tags.find((t) => t[0] === "u");
|
|
if (urlTag?.length !== 1 && urlTag?.[1] !== url) {
|
|
throw new Error("Invalid nostr event, url tag invalid");
|
|
}
|
|
const methodTag = event.tags.find((t) => t[0] === "method");
|
|
if (methodTag?.length !== 1 && methodTag?.[1].toLowerCase() !== method.toLowerCase()) {
|
|
throw new Error("Invalid nostr event, method tag invalid");
|
|
}
|
|
return true;
|
|
}
|
|
export {
|
|
Kind,
|
|
SimplePool,
|
|
eventsGenerator,
|
|
finishEvent,
|
|
fakejson_exports as fj,
|
|
generatePrivateKey,
|
|
getBlankEvent,
|
|
getEventHash,
|
|
getPublicKey,
|
|
getSignature,
|
|
matchFilter,
|
|
matchFilters,
|
|
mergeFilters,
|
|
nip04_exports as nip04,
|
|
nip05_exports as nip05,
|
|
nip06_exports as nip06,
|
|
nip10_exports as nip10,
|
|
nip13_exports as nip13,
|
|
nip18_exports as nip18,
|
|
nip19_exports as nip19,
|
|
nip21_exports as nip21,
|
|
nip25_exports as nip25,
|
|
nip26_exports as nip26,
|
|
nip27_exports as nip27,
|
|
nip28_exports as nip28,
|
|
nip39_exports as nip39,
|
|
nip42_exports as nip42,
|
|
nip44_exports as nip44,
|
|
nip47_exports as nip47,
|
|
nip57_exports as nip57,
|
|
nip98_exports as nip98,
|
|
parseReferences,
|
|
relayInit,
|
|
serializeEvent,
|
|
signEvent,
|
|
utils_exports as utils,
|
|
validateEvent,
|
|
verifiedSymbol,
|
|
verifySignature
|
|
};
|