Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

nostr-tools

Package Overview
Dependencies
Maintainers
1
Versions
151
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

nostr-tools - npm Package Compare versions

Comparing version 2.1.5 to 2.1.7

lib/cjs/nip49.js

2

lib/cjs/kinds.js

@@ -77,2 +77,3 @@ "use strict";

NostrConnect: () => NostrConnect,
NostrConnectAdmin: () => NostrConnectAdmin,
OpenTimestamps: () => OpenTimestamps,

@@ -176,2 +177,3 @@ Pinlist: () => Pinlist,

var NostrConnect = 24133;
var NostrConnectAdmin = 24134;
var HTTPAuth = 27235;

@@ -178,0 +180,0 @@ var Followsets = 3e4;

38

lib/cjs/nip05.js

@@ -24,2 +24,3 @@ "use strict";

NIP05_REGEX: () => NIP05_REGEX,
isValid: () => isValid,
queryProfile: () => queryProfile,

@@ -30,3 +31,3 @@ searchDomain: () => searchDomain,

module.exports = __toCommonJS(nip05_exports);
var NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w.-]+)$/;
var NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$/;
var _fetch;

@@ -42,4 +43,6 @@ try {

try {
let res = await (await _fetch(`https://${domain}/.well-known/nostr.json?name=${query}`)).json();
return res.names;
const url = `https://${domain}/.well-known/nostr.json?name=${query}`;
const res = await _fetch(url, { redirect: "error" });
const json = await res.json();
return json.names;
} catch (_) {

@@ -55,6 +58,6 @@ return {};

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;
const url = `https://${domain}/.well-known/nostr.json?name=${name}`;
const res = await (await _fetch(url, { redirect: "error" })).json();
let pubkey = res.names[name];
return pubkey ? { pubkey, relays: res.relays?.[pubkey] } : null;
} catch (_e) {

@@ -64,20 +67,5 @@ 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;
async function isValid(pubkey, nip05) {
let res = await queryProfile(nip05);
return res ? res.pubkey === pubkey : false;
}

@@ -24,3 +24,5 @@ "use strict";

BECH32_REGEX: () => BECH32_REGEX,
Bech32MaxSize: () => Bech32MaxSize,
decode: () => decode,
encodeBytes: () => encodeBytes,
naddrEncode: () => naddrEncode,

@@ -27,0 +29,0 @@ neventEncode: () => neventEncode,

@@ -23,9 +23,912 @@ "use strict";

__export(nip46_exports, {
BUNKER_REGEX: () => BUNKER_REGEX,
BunkerSigner: () => BunkerSigner,
createAccount: () => createAccount,
fetchCustodialbunkers: () => fetchCustodialbunkers,
parseBunkerInput: () => parseBunkerInput,
requestSignedEvent: () => requestSignedEvent
useFetchImplementation: () => useFetchImplementation
});
module.exports = __toCommonJS(nip46_exports);
function parseBunkerInput() {
// pure.ts
var import_secp256k1 = require("@noble/curves/secp256k1");
var import_utils = require("@noble/hashes/utils");
// core.ts
var verifiedSymbol = Symbol("verified");
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 i2 = 0; i2 < event.tags.length; i2++) {
let tag = event.tags[i2];
if (!Array.isArray(tag))
return false;
for (let j = 0; j < tag.length; j++) {
if (typeof tag[j] === "object")
return false;
}
}
return true;
}
function requestSignedEvent() {
// pure.ts
var import_sha256 = require("@noble/hashes/sha256");
// utils.ts
var utf8Decoder = new TextDecoder("utf-8");
var utf8Encoder = new TextEncoder();
function normalizeURL(url) {
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();
}
var QueueNode = class {
value;
next = null;
prev = null;
constructor(message) {
this.value = message;
}
};
var Queue = class {
first;
last;
constructor() {
this.first = null;
this.last = null;
}
enqueue(value) {
const newNode = new QueueNode(value);
if (!this.last) {
this.first = newNode;
this.last = newNode;
} else if (this.last === this.first) {
this.last = newNode;
this.last.prev = this.first;
this.first.next = newNode;
} else {
newNode.prev = this.last;
this.last.next = newNode;
this.last = newNode;
}
return true;
}
dequeue() {
if (!this.first)
return null;
if (this.first === this.last) {
const target2 = this.first;
this.first = null;
this.last = null;
return target2.value;
}
const target = this.first;
this.first = target.next;
return target.value;
}
};
// pure.ts
var JS = class {
generateSecretKey() {
return import_secp256k1.schnorr.utils.randomPrivateKey();
}
getPublicKey(secretKey) {
return (0, import_utils.bytesToHex)(import_secp256k1.schnorr.getPublicKey(secretKey));
}
finalizeEvent(t, secretKey) {
const event = t;
event.pubkey = (0, import_utils.bytesToHex)(import_secp256k1.schnorr.getPublicKey(secretKey));
event.id = getEventHash(event);
event.sig = (0, import_utils.bytesToHex)(import_secp256k1.schnorr.sign(getEventHash(event), secretKey));
event[verifiedSymbol] = true;
return event;
}
verifyEvent(event) {
if (typeof event[verifiedSymbol] === "boolean")
return event[verifiedSymbol];
const hash = getEventHash(event);
if (hash !== event.id) {
event[verifiedSymbol] = false;
return false;
}
try {
const valid = import_secp256k1.schnorr.verify(event.sig, hash, event.pubkey);
event[verifiedSymbol] = valid;
return valid;
} catch (err) {
event[verifiedSymbol] = false;
return false;
}
}
};
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 = (0, import_sha256.sha256)(utf8Encoder.encode(serializeEvent(event)));
return (0, import_utils.bytesToHex)(eventHash);
}
var i = new JS();
var generateSecretKey = i.generateSecretKey;
var getPublicKey = i.getPublicKey;
var finalizeEvent = i.finalizeEvent;
var verifyEvent = i.verifyEvent;
// nip04.ts
var import_utils3 = require("@noble/hashes/utils");
var import_secp256k12 = require("@noble/curves/secp256k1");
var import_base = require("@scure/base");
if (typeof crypto !== "undefined" && !crypto.subtle && crypto.webcrypto) {
crypto.subtle = crypto.webcrypto.subtle;
}
async function encrypt(secretKey, pubkey, text) {
const privkey = secretKey instanceof Uint8Array ? (0, import_utils3.bytesToHex)(secretKey) : secretKey;
const key = import_secp256k12.secp256k1.getSharedSecret(privkey, "02" + pubkey);
const normalizedKey = getNormalizedX(key);
let iv = Uint8Array.from((0, import_utils3.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 = import_base.base64.encode(new Uint8Array(ciphertext));
let ivb64 = import_base.base64.encode(new Uint8Array(iv.buffer));
return `${ctb64}?iv=${ivb64}`;
}
async function decrypt(secretKey, pubkey, data) {
const privkey = secretKey instanceof Uint8Array ? (0, import_utils3.bytesToHex)(secretKey) : secretKey;
let [ctb64, ivb64] = data.split("?iv=");
let key = import_secp256k12.secp256k1.getSharedSecret(privkey, "02" + pubkey);
let normalizedKey = getNormalizedX(key);
let cryptoKey = await crypto.subtle.importKey("raw", normalizedKey, { name: "AES-CBC" }, false, ["decrypt"]);
let ciphertext = import_base.base64.decode(ctb64);
let iv = import_base.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_REGEX = /^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$/;
var _fetch;
try {
_fetch = fetch;
} catch {
}
// kinds.ts
var ClientAuth = 22242;
var NostrConnect = 24133;
var NostrConnectAdmin = 24134;
var Handlerinformation = 31990;
// 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 i2 = 0; i2 < filters.length; i2++) {
if (matchFilter(filters[i2], event))
return true;
}
return false;
}
// fakejson.ts
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 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);
}
// nip42.ts
function makeAuthEvent(relayURL, challenge) {
return {
kind: ClientAuth,
created_at: Math.floor(Date.now() / 1e3),
tags: [
["relay", relayURL],
["challenge", challenge]
],
content: ""
};
}
// helpers.ts
async function yieldThread() {
return new Promise((resolve) => {
const ch = new MessageChannel();
const handler = () => {
ch.port1.removeEventListener("message", handler);
resolve();
};
ch.port1.addEventListener("message", handler);
ch.port2.postMessage(0);
ch.port1.start();
});
}
var alwaysTrue = (t) => {
t[verifiedSymbol] = true;
return true;
};
// abstract-relay.ts
var AbstractRelay = class {
url;
_connected = false;
onclose = null;
onnotice = (msg) => console.debug(`NOTICE from ${this.url}: ${msg}`);
baseEoseTimeout = 4400;
connectionTimeout = 4400;
openSubs = /* @__PURE__ */ new Map();
connectionTimeoutHandle;
connectionPromise;
openCountRequests = /* @__PURE__ */ new Map();
openEventPublishes = /* @__PURE__ */ new Map();
ws;
incomingMessageQueue = new Queue();
queueRunning = false;
challenge;
serial = 0;
verifyEvent;
constructor(url, opts) {
this.url = normalizeURL(url);
this.verifyEvent = opts.verifyEvent;
}
static async connect(url, opts) {
const relay = new AbstractRelay(url, opts);
await relay.connect();
return relay;
}
closeAllSubscriptions(reason) {
for (let [_, sub] of this.openSubs) {
sub.close(reason);
}
this.openSubs.clear();
for (let [_, ep] of this.openEventPublishes) {
ep.reject(new Error(reason));
}
this.openEventPublishes.clear();
for (let [_, cr] of this.openCountRequests) {
cr.reject(new Error(reason));
}
this.openCountRequests.clear();
}
get connected() {
return this._connected;
}
async connect() {
if (this.connectionPromise)
return this.connectionPromise;
this.challenge = void 0;
this.connectionPromise = new Promise((resolve, reject) => {
this.connectionTimeoutHandle = setTimeout(() => {
reject("connection timed out");
this.connectionPromise = void 0;
this.onclose?.();
this.closeAllSubscriptions("relay connection timed out");
}, this.connectionTimeout);
try {
this.ws = new WebSocket(this.url);
} catch (err) {
reject(err);
return;
}
this.ws.onopen = () => {
clearTimeout(this.connectionTimeoutHandle);
this._connected = true;
resolve();
};
this.ws.onerror = (ev) => {
reject(ev.message);
if (this._connected) {
this.onclose?.();
this.closeAllSubscriptions("relay connection errored");
this._connected = false;
}
};
this.ws.onclose = async () => {
this.connectionPromise = void 0;
this.onclose?.();
this.closeAllSubscriptions("relay connection closed");
this._connected = false;
};
this.ws.onmessage = this._onmessage.bind(this);
});
return this.connectionPromise;
}
async runQueue() {
this.queueRunning = true;
while (true) {
if (false === this.handleNext()) {
break;
}
await yieldThread();
}
this.queueRunning = false;
}
handleNext() {
const json = this.incomingMessageQueue.dequeue();
if (!json) {
return false;
}
const subid = getSubscriptionId(json);
if (subid) {
const so = this.openSubs.get(subid);
if (!so) {
return;
}
const id = getHex64(json, "id");
const alreadyHave = so.alreadyHaveEvent?.(id);
so.receivedEvent?.(this, id);
if (alreadyHave) {
return;
}
}
try {
let data = JSON.parse(json);
switch (data[0]) {
case "EVENT": {
const so = this.openSubs.get(data[1]);
const event = data[2];
if (this.verifyEvent(event) && matchFilters(so.filters, event)) {
so.onevent(event);
}
return;
}
case "COUNT": {
const id = data[1];
const payload = data[2];
const cr = this.openCountRequests.get(id);
if (cr) {
cr.resolve(payload.count);
this.openCountRequests.delete(id);
}
return;
}
case "EOSE": {
const so = this.openSubs.get(data[1]);
if (!so)
return;
so.receivedEose();
return;
}
case "OK": {
const id = data[1];
const ok = data[2];
const reason = data[3];
const ep = this.openEventPublishes.get(id);
if (ok)
ep.resolve(reason);
else
ep.reject(new Error(reason));
this.openEventPublishes.delete(id);
return;
}
case "CLOSED": {
const id = data[1];
const so = this.openSubs.get(id);
if (!so)
return;
so.closed = true;
so.close(data[2]);
return;
}
case "NOTICE":
this.onnotice(data[1]);
return;
case "AUTH": {
this.challenge = data[1];
return;
}
}
} catch (err) {
return;
}
}
async send(message) {
if (!this.connectionPromise)
throw new Error("sending on closed connection");
this.connectionPromise.then(() => {
this.ws?.send(message);
});
}
async auth(signAuthEvent) {
if (!this.challenge)
throw new Error("can't perform auth, no challenge was received");
const evt = await signAuthEvent(makeAuthEvent(this.url, this.challenge));
const ret = new Promise((resolve, reject) => {
this.openEventPublishes.set(evt.id, { resolve, reject });
});
this.send('["AUTH",' + JSON.stringify(evt) + "]");
return ret;
}
async publish(event) {
const ret = new Promise((resolve, reject) => {
this.openEventPublishes.set(event.id, { resolve, reject });
});
this.send('["EVENT",' + JSON.stringify(event) + "]");
return ret;
}
async count(filters, params) {
this.serial++;
const id = params?.id || "count:" + this.serial;
const ret = new Promise((resolve, reject) => {
this.openCountRequests.set(id, { resolve, reject });
});
this.send('["COUNT","' + id + '",' + JSON.stringify(filters) + "]");
return ret;
}
subscribe(filters, params) {
const subscription = this.prepareSubscription(filters, params);
subscription.fire();
return subscription;
}
prepareSubscription(filters, params) {
this.serial++;
const id = params.id || "sub:" + this.serial;
const subscription = new Subscription(this, id, filters, params);
this.openSubs.set(id, subscription);
return subscription;
}
close() {
this.closeAllSubscriptions("relay connection closed by us");
this._connected = false;
this.ws?.close();
}
_onmessage(ev) {
this.incomingMessageQueue.enqueue(ev.data);
if (!this.queueRunning) {
this.runQueue();
}
}
};
var Subscription = class {
relay;
id;
closed = false;
eosed = false;
filters;
alreadyHaveEvent;
receivedEvent;
onevent;
oneose;
onclose;
eoseTimeout;
eoseTimeoutHandle;
constructor(relay, id, filters, params) {
this.relay = relay;
this.filters = filters;
this.id = id;
this.alreadyHaveEvent = params.alreadyHaveEvent;
this.receivedEvent = params.receivedEvent;
this.eoseTimeout = params.eoseTimeout || relay.baseEoseTimeout;
this.oneose = params.oneose;
this.onclose = params.onclose;
this.onevent = params.onevent || ((event) => {
console.warn(
`onevent() callback not defined for subscription '${this.id}' in relay ${this.relay.url}. event received:`,
event
);
});
}
fire() {
this.relay.send('["REQ","' + this.id + '",' + JSON.stringify(this.filters).substring(1));
this.eoseTimeoutHandle = setTimeout(this.receivedEose.bind(this), this.eoseTimeout);
}
receivedEose() {
if (this.eosed)
return;
clearTimeout(this.eoseTimeoutHandle);
this.eosed = true;
this.oneose?.();
}
close(reason = "closed by caller") {
if (!this.closed) {
this.relay.send('["CLOSE",' + JSON.stringify(this.id) + "]");
this.closed = true;
}
this.relay.openSubs.delete(this.id);
this.onclose?.(reason);
}
};
// abstract-pool.ts
var AbstractSimplePool = class {
relays = /* @__PURE__ */ new Map();
seenOn = /* @__PURE__ */ new Map();
trackRelays = false;
verifyEvent;
trustedRelayURLs = /* @__PURE__ */ new Set();
constructor(opts) {
this.verifyEvent = opts.verifyEvent;
}
async ensureRelay(url, params) {
url = normalizeURL(url);
let relay = this.relays.get(url);
if (!relay) {
relay = new AbstractRelay(url, {
verifyEvent: this.trustedRelayURLs.has(url) ? alwaysTrue : this.verifyEvent
});
if (params?.connectionTimeout)
relay.connectionTimeout = params.connectionTimeout;
this.relays.set(url, relay);
}
await relay.connect();
return relay;
}
close(relays) {
relays.map(normalizeURL).forEach((url) => {
this.relays.get(url)?.close();
});
}
subscribeMany(relays, filters, params) {
if (this.trackRelays) {
params.receivedEvent = (relay, id) => {
let set = this.seenOn.get(id);
if (!set) {
set = /* @__PURE__ */ new Set();
this.seenOn.set(id, set);
}
set.add(relay);
};
}
const _knownIds = /* @__PURE__ */ new Set();
const subs = [];
const eosesReceived = [];
let handleEose = (i2) => {
eosesReceived[i2] = true;
if (eosesReceived.filter((a) => a).length === relays.length) {
params.oneose?.();
handleEose = () => {
};
}
};
const closesReceived = [];
let handleClose = (i2, reason) => {
handleEose(i2);
closesReceived[i2] = reason;
if (closesReceived.filter((a) => a).length === relays.length) {
params.onclose?.(closesReceived);
handleClose = () => {
};
}
};
const localAlreadyHaveEventHandler = (id) => {
if (params.alreadyHaveEvent?.(id)) {
return true;
}
const have = _knownIds.has(id);
_knownIds.add(id);
return have;
};
const allOpened = Promise.all(
relays.map(normalizeURL).map(async (url, i2, arr) => {
if (arr.indexOf(url) !== i2) {
handleClose(i2, "duplicate url");
return;
}
let relay;
try {
relay = await this.ensureRelay(url, {
connectionTimeout: params.maxWait ? Math.max(params.maxWait * 0.8, params.maxWait - 1e3) : void 0
});
} catch (err) {
handleClose(i2, err?.message || String(err));
return;
}
let subscription = relay.subscribe(filters, {
...params,
oneose: () => handleEose(i2),
onclose: (reason) => handleClose(i2, reason),
alreadyHaveEvent: localAlreadyHaveEventHandler,
eoseTimeout: params.maxWait
});
subs.push(subscription);
})
);
return {
async close() {
await allOpened;
subs.forEach((sub) => {
sub.close();
});
}
};
}
subscribeManyEose(relays, filters, params) {
const subcloser = this.subscribeMany(relays, filters, {
...params,
oneose() {
subcloser.close();
}
});
return subcloser;
}
async querySync(relays, filter, params) {
return new Promise(async (resolve) => {
const events = [];
this.subscribeManyEose(relays, [filter], {
...params,
onevent(event) {
events.push(event);
},
onclose(_) {
resolve(events);
}
});
});
}
async get(relays, filter, params) {
filter.limit = 1;
const events = await this.querySync(relays, filter, params);
events.sort((a, b) => b.created_at - a.created_at);
return events[0] || null;
}
publish(relays, event) {
return relays.map(normalizeURL).map(async (url, i2, arr) => {
if (arr.indexOf(url) !== i2) {
return Promise.reject("duplicate url");
}
let r = await this.ensureRelay(url);
return r.publish(event);
});
}
};
// pool.ts
var SimplePool = class extends AbstractSimplePool {
constructor() {
super({ verifyEvent });
}
};
// nip46.ts
var _fetch2;
try {
_fetch2 = fetch;
} catch {
}
function useFetchImplementation(fetchImplementation) {
_fetch2 = fetchImplementation;
}
var BUNKER_REGEX = /^bunker:\/\/([0-9a-f]{64})\??([?\/\w:.=&%]*)$/;
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
async function parseBunkerInput(input) {
let match = input.match(BUNKER_REGEX);
if (match) {
try {
const pubkey = match[1];
const qs = new URLSearchParams(match[2]);
return {
pubkey,
relays: qs.getAll("relay"),
secret: qs.get("secret")
};
} catch (_err) {
}
}
return queryBunkerProfile(input);
}
async function queryBunkerProfile(nip05) {
const match = nip05.match(NIP05_REGEX);
if (!match)
return null;
const [_, name = "_", domain] = match;
try {
const url = `https://${domain}/.well-known/nostr.json?name=${name}`;
const res = await (await _fetch2(url, { redirect: "error" })).json();
let pubkey = res.names[name];
let relays = res.nip46[pubkey] || [];
return { pubkey, relays, secret: null };
} catch (_err) {
return null;
}
}
var BunkerSigner = class {
pool;
subCloser;
relays;
isOpen;
serial;
idPrefix;
listeners;
secretKey;
connectionSecret;
remotePubkey;
constructor(clientSecretKey, bp, params = {}) {
if (bp.relays.length === 0) {
throw new Error("no relays are specified for this bunker");
}
this.pool = params.pool || new SimplePool();
this.secretKey = clientSecretKey;
this.relays = bp.relays;
this.remotePubkey = bp.pubkey;
this.connectionSecret = bp.secret || "";
this.isOpen = false;
this.idPrefix = Math.random().toString(36).substring(7);
this.serial = 0;
this.listeners = {};
const listeners = this.listeners;
this.subCloser = this.pool.subscribeMany(
this.relays,
[{ kinds: [NostrConnect, NostrConnectAdmin], "#p": [getPublicKey(this.secretKey)] }],
{
async onevent(event) {
const { id, result, error } = JSON.parse(await decrypt(clientSecretKey, event.pubkey, event.content));
if (result === "auth_url") {
if (params.onauth) {
params.onauth(error);
} else {
console.warn(
`nostr-tools/nip46: remote signer ${bp.pubkey} tried to send an "auth_url"='${error}' but there was no onauth() callback configured.`
);
}
return;
}
let handler = listeners[id];
if (handler) {
if (error)
handler.reject(error);
else if (result)
handler.resolve(result);
delete listeners[id];
}
}
}
);
this.isOpen = true;
}
async close() {
this.isOpen = false;
this.subCloser.close();
}
async sendRequest(method, params) {
return new Promise(async (resolve, reject) => {
try {
if (!this.isOpen)
throw new Error("this signer is not open anymore, create a new one");
this.serial++;
const id = `${this.idPrefix}-${this.serial}`;
const encryptedContent = await encrypt(
this.secretKey,
this.remotePubkey,
JSON.stringify({ id, method, params })
);
const verifiedEvent = finalizeEvent(
{
kind: method === "create_account" ? NostrConnectAdmin : NostrConnect,
tags: [["p", this.remotePubkey]],
content: encryptedContent,
created_at: Math.floor(Date.now() / 1e3)
},
this.secretKey
);
this.listeners[id] = { resolve, reject };
await Promise.any(this.pool.publish(this.relays, verifiedEvent));
} catch (err) {
reject(err);
}
});
}
async ping() {
let resp = await this.sendRequest("ping", []);
if (resp !== "pong")
throw new Error(`result is not pong: ${resp}`);
}
async connect() {
await this.sendRequest("connect", [getPublicKey(this.secretKey), this.connectionSecret]);
}
async signEvent(event) {
let resp = await this.sendRequest("sign_event", [JSON.stringify(event)]);
let signed = JSON.parse(resp);
if (signed.pubkey === this.remotePubkey && verifyEvent(signed)) {
return signed;
} else {
throw new Error(`event returned from bunker is improperly signed: ${JSON.stringify(signed)}`);
}
}
};
async function createAccount(bunker, params, username, domain, email) {
if (email && !EMAIL_REGEX.test(email))
throw new Error("Invalid email");
let sk = generateSecretKey();
let rpc = new BunkerSigner(sk, bunker.bunkerPointer, params);
let pubkey = await rpc.sendRequest("create_account", [username, domain, email || ""]);
rpc.remotePubkey = pubkey;
await rpc.connect();
return rpc;
}
async function fetchCustodialbunkers(pool, relays) {
const events = await pool.querySync(relays, {
kinds: [Handlerinformation],
"#k": [NostrConnect.toString()]
});
const validatedBunkers = await Promise.all(
events.map(async (event) => {
try {
const content = JSON.parse(event.content);
const bp = await queryBunkerProfile(content.nip05);
if (bp && bp.pubkey === event.pubkey && bp.relays.length) {
return {
bunkerPointer: bp,
nip05: content.nip05,
domain: content.nip05.split("@")[1],
name: content.name || content.display_name,
picture: content.picture,
about: content.about,
website: content.website,
local: false
};
}
} catch (err) {
return void 0;
}
})
);
return validatedBunkers.filter((b) => b !== void 0);
}

@@ -147,2 +147,3 @@ "use strict";

}
throw new Error("Unknown error in uploading file!");
}

@@ -149,0 +150,0 @@ try {

@@ -266,2 +266,3 @@ var __defProp = Object.defineProperty;

NostrConnect: () => NostrConnect,
NostrConnectAdmin: () => NostrConnectAdmin,
OpenTimestamps: () => OpenTimestamps,

@@ -364,2 +365,3 @@ Pinlist: () => Pinlist,

var NostrConnect = 24133;
var NostrConnectAdmin = 24134;
var HTTPAuth = 27235;

@@ -996,3 +998,5 @@ var Followsets = 3e4;

BECH32_REGEX: () => BECH32_REGEX,
Bech32MaxSize: () => Bech32MaxSize,
decode: () => decode,
encodeBytes: () => encodeBytes,
naddrEncode: () => naddrEncode,

@@ -1311,2 +1315,3 @@ neventEncode: () => neventEncode,

NIP05_REGEX: () => NIP05_REGEX,
isValid: () => isValid,
queryProfile: () => queryProfile,

@@ -1316,3 +1321,3 @@ searchDomain: () => searchDomain,

});
var NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w.-]+)$/;
var NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$/;
var _fetch;

@@ -1328,4 +1333,6 @@ try {

try {
let res = await (await _fetch(`https://${domain}/.well-known/nostr.json?name=${query}`)).json();
return res.names;
const url = `https://${domain}/.well-known/nostr.json?name=${query}`;
const res = await _fetch(url, { redirect: "error" });
const json = await res.json();
return json.names;
} catch (_) {

@@ -1341,6 +1348,6 @@ return {};

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;
const url = `https://${domain}/.well-known/nostr.json?name=${name}`;
const res = await (await _fetch(url, { redirect: "error" })).json();
let pubkey = res.names[name];
return pubkey ? { pubkey, relays: res.relays?.[pubkey] } : null;
} catch (_e) {

@@ -1350,20 +1357,5 @@ 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;
async function isValid(pubkey, nip05) {
let res = await queryProfile(nip05);
return res ? res.pubkey === pubkey : false;
}

@@ -1370,0 +1362,0 @@

@@ -73,2 +73,3 @@ // kinds.ts

var NostrConnect = 24133;
var NostrConnectAdmin = 24134;
var HTTPAuth = 27235;

@@ -155,2 +156,3 @@ var Followsets = 3e4;

NostrConnect,
NostrConnectAdmin,
OpenTimestamps,

@@ -157,0 +159,0 @@ Pinlist,

// nip05.ts
var NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w.-]+)$/;
var NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$/;
var _fetch;

@@ -13,4 +13,6 @@ try {

try {
let res = await (await _fetch(`https://${domain}/.well-known/nostr.json?name=${query}`)).json();
return res.names;
const url = `https://${domain}/.well-known/nostr.json?name=${query}`;
const res = await _fetch(url, { redirect: "error" });
const json = await res.json();
return json.names;
} catch (_) {

@@ -26,6 +28,6 @@ return {};

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;
const url = `https://${domain}/.well-known/nostr.json?name=${name}`;
const res = await (await _fetch(url, { redirect: "error" })).json();
let pubkey = res.names[name];
return pubkey ? { pubkey, relays: res.relays?.[pubkey] } : null;
} catch (_e) {

@@ -35,23 +37,9 @@ 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;
async function isValid(pubkey, nip05) {
let res = await queryProfile(nip05);
return res ? res.pubkey === pubkey : false;
}
export {
NIP05_REGEX,
isValid,
queryProfile,

@@ -58,0 +46,0 @@ searchDomain,

@@ -181,3 +181,5 @@ // nip19.ts

BECH32_REGEX,
Bech32MaxSize,
decode,
encodeBytes,
naddrEncode,

@@ -184,0 +186,0 @@ neventEncode,

@@ -0,9 +1,910 @@

// pure.ts
import { schnorr } from "@noble/curves/secp256k1";
import { bytesToHex } from "@noble/hashes/utils";
// core.ts
var verifiedSymbol = Symbol("verified");
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 i2 = 0; i2 < event.tags.length; i2++) {
let tag = event.tags[i2];
if (!Array.isArray(tag))
return false;
for (let j = 0; j < tag.length; j++) {
if (typeof tag[j] === "object")
return false;
}
}
return true;
}
// pure.ts
import { sha256 } from "@noble/hashes/sha256";
// utils.ts
var utf8Decoder = new TextDecoder("utf-8");
var utf8Encoder = new TextEncoder();
function normalizeURL(url) {
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();
}
var QueueNode = class {
value;
next = null;
prev = null;
constructor(message) {
this.value = message;
}
};
var Queue = class {
first;
last;
constructor() {
this.first = null;
this.last = null;
}
enqueue(value) {
const newNode = new QueueNode(value);
if (!this.last) {
this.first = newNode;
this.last = newNode;
} else if (this.last === this.first) {
this.last = newNode;
this.last.prev = this.first;
this.first.next = newNode;
} else {
newNode.prev = this.last;
this.last.next = newNode;
this.last = newNode;
}
return true;
}
dequeue() {
if (!this.first)
return null;
if (this.first === this.last) {
const target2 = this.first;
this.first = null;
this.last = null;
return target2.value;
}
const target = this.first;
this.first = target.next;
return target.value;
}
};
// pure.ts
var JS = class {
generateSecretKey() {
return schnorr.utils.randomPrivateKey();
}
getPublicKey(secretKey) {
return bytesToHex(schnorr.getPublicKey(secretKey));
}
finalizeEvent(t, secretKey) {
const event = t;
event.pubkey = bytesToHex(schnorr.getPublicKey(secretKey));
event.id = getEventHash(event);
event.sig = bytesToHex(schnorr.sign(getEventHash(event), secretKey));
event[verifiedSymbol] = true;
return event;
}
verifyEvent(event) {
if (typeof event[verifiedSymbol] === "boolean")
return event[verifiedSymbol];
const hash = getEventHash(event);
if (hash !== event.id) {
event[verifiedSymbol] = false;
return false;
}
try {
const valid = schnorr.verify(event.sig, hash, event.pubkey);
event[verifiedSymbol] = valid;
return valid;
} catch (err) {
event[verifiedSymbol] = false;
return false;
}
}
};
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 bytesToHex(eventHash);
}
var i = new JS();
var generateSecretKey = i.generateSecretKey;
var getPublicKey = i.getPublicKey;
var finalizeEvent = i.finalizeEvent;
var verifyEvent = i.verifyEvent;
// nip04.ts
import { bytesToHex as bytesToHex2, 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(secretKey, pubkey, text) {
const privkey = secretKey instanceof Uint8Array ? bytesToHex2(secretKey) : secretKey;
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(secretKey, pubkey, data) {
const privkey = secretKey instanceof Uint8Array ? bytesToHex2(secretKey) : secretKey;
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_REGEX = /^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$/;
var _fetch;
try {
_fetch = fetch;
} catch {
}
// kinds.ts
var ClientAuth = 22242;
var NostrConnect = 24133;
var NostrConnectAdmin = 24134;
var Handlerinformation = 31990;
// 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 i2 = 0; i2 < filters.length; i2++) {
if (matchFilter(filters[i2], event))
return true;
}
return false;
}
// fakejson.ts
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 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);
}
// nip42.ts
function makeAuthEvent(relayURL, challenge) {
return {
kind: ClientAuth,
created_at: Math.floor(Date.now() / 1e3),
tags: [
["relay", relayURL],
["challenge", challenge]
],
content: ""
};
}
// helpers.ts
async function yieldThread() {
return new Promise((resolve) => {
const ch = new MessageChannel();
const handler = () => {
ch.port1.removeEventListener("message", handler);
resolve();
};
ch.port1.addEventListener("message", handler);
ch.port2.postMessage(0);
ch.port1.start();
});
}
var alwaysTrue = (t) => {
t[verifiedSymbol] = true;
return true;
};
// abstract-relay.ts
var AbstractRelay = class {
url;
_connected = false;
onclose = null;
onnotice = (msg) => console.debug(`NOTICE from ${this.url}: ${msg}`);
baseEoseTimeout = 4400;
connectionTimeout = 4400;
openSubs = /* @__PURE__ */ new Map();
connectionTimeoutHandle;
connectionPromise;
openCountRequests = /* @__PURE__ */ new Map();
openEventPublishes = /* @__PURE__ */ new Map();
ws;
incomingMessageQueue = new Queue();
queueRunning = false;
challenge;
serial = 0;
verifyEvent;
constructor(url, opts) {
this.url = normalizeURL(url);
this.verifyEvent = opts.verifyEvent;
}
static async connect(url, opts) {
const relay = new AbstractRelay(url, opts);
await relay.connect();
return relay;
}
closeAllSubscriptions(reason) {
for (let [_, sub] of this.openSubs) {
sub.close(reason);
}
this.openSubs.clear();
for (let [_, ep] of this.openEventPublishes) {
ep.reject(new Error(reason));
}
this.openEventPublishes.clear();
for (let [_, cr] of this.openCountRequests) {
cr.reject(new Error(reason));
}
this.openCountRequests.clear();
}
get connected() {
return this._connected;
}
async connect() {
if (this.connectionPromise)
return this.connectionPromise;
this.challenge = void 0;
this.connectionPromise = new Promise((resolve, reject) => {
this.connectionTimeoutHandle = setTimeout(() => {
reject("connection timed out");
this.connectionPromise = void 0;
this.onclose?.();
this.closeAllSubscriptions("relay connection timed out");
}, this.connectionTimeout);
try {
this.ws = new WebSocket(this.url);
} catch (err) {
reject(err);
return;
}
this.ws.onopen = () => {
clearTimeout(this.connectionTimeoutHandle);
this._connected = true;
resolve();
};
this.ws.onerror = (ev) => {
reject(ev.message);
if (this._connected) {
this.onclose?.();
this.closeAllSubscriptions("relay connection errored");
this._connected = false;
}
};
this.ws.onclose = async () => {
this.connectionPromise = void 0;
this.onclose?.();
this.closeAllSubscriptions("relay connection closed");
this._connected = false;
};
this.ws.onmessage = this._onmessage.bind(this);
});
return this.connectionPromise;
}
async runQueue() {
this.queueRunning = true;
while (true) {
if (false === this.handleNext()) {
break;
}
await yieldThread();
}
this.queueRunning = false;
}
handleNext() {
const json = this.incomingMessageQueue.dequeue();
if (!json) {
return false;
}
const subid = getSubscriptionId(json);
if (subid) {
const so = this.openSubs.get(subid);
if (!so) {
return;
}
const id = getHex64(json, "id");
const alreadyHave = so.alreadyHaveEvent?.(id);
so.receivedEvent?.(this, id);
if (alreadyHave) {
return;
}
}
try {
let data = JSON.parse(json);
switch (data[0]) {
case "EVENT": {
const so = this.openSubs.get(data[1]);
const event = data[2];
if (this.verifyEvent(event) && matchFilters(so.filters, event)) {
so.onevent(event);
}
return;
}
case "COUNT": {
const id = data[1];
const payload = data[2];
const cr = this.openCountRequests.get(id);
if (cr) {
cr.resolve(payload.count);
this.openCountRequests.delete(id);
}
return;
}
case "EOSE": {
const so = this.openSubs.get(data[1]);
if (!so)
return;
so.receivedEose();
return;
}
case "OK": {
const id = data[1];
const ok = data[2];
const reason = data[3];
const ep = this.openEventPublishes.get(id);
if (ok)
ep.resolve(reason);
else
ep.reject(new Error(reason));
this.openEventPublishes.delete(id);
return;
}
case "CLOSED": {
const id = data[1];
const so = this.openSubs.get(id);
if (!so)
return;
so.closed = true;
so.close(data[2]);
return;
}
case "NOTICE":
this.onnotice(data[1]);
return;
case "AUTH": {
this.challenge = data[1];
return;
}
}
} catch (err) {
return;
}
}
async send(message) {
if (!this.connectionPromise)
throw new Error("sending on closed connection");
this.connectionPromise.then(() => {
this.ws?.send(message);
});
}
async auth(signAuthEvent) {
if (!this.challenge)
throw new Error("can't perform auth, no challenge was received");
const evt = await signAuthEvent(makeAuthEvent(this.url, this.challenge));
const ret = new Promise((resolve, reject) => {
this.openEventPublishes.set(evt.id, { resolve, reject });
});
this.send('["AUTH",' + JSON.stringify(evt) + "]");
return ret;
}
async publish(event) {
const ret = new Promise((resolve, reject) => {
this.openEventPublishes.set(event.id, { resolve, reject });
});
this.send('["EVENT",' + JSON.stringify(event) + "]");
return ret;
}
async count(filters, params) {
this.serial++;
const id = params?.id || "count:" + this.serial;
const ret = new Promise((resolve, reject) => {
this.openCountRequests.set(id, { resolve, reject });
});
this.send('["COUNT","' + id + '",' + JSON.stringify(filters) + "]");
return ret;
}
subscribe(filters, params) {
const subscription = this.prepareSubscription(filters, params);
subscription.fire();
return subscription;
}
prepareSubscription(filters, params) {
this.serial++;
const id = params.id || "sub:" + this.serial;
const subscription = new Subscription(this, id, filters, params);
this.openSubs.set(id, subscription);
return subscription;
}
close() {
this.closeAllSubscriptions("relay connection closed by us");
this._connected = false;
this.ws?.close();
}
_onmessage(ev) {
this.incomingMessageQueue.enqueue(ev.data);
if (!this.queueRunning) {
this.runQueue();
}
}
};
var Subscription = class {
relay;
id;
closed = false;
eosed = false;
filters;
alreadyHaveEvent;
receivedEvent;
onevent;
oneose;
onclose;
eoseTimeout;
eoseTimeoutHandle;
constructor(relay, id, filters, params) {
this.relay = relay;
this.filters = filters;
this.id = id;
this.alreadyHaveEvent = params.alreadyHaveEvent;
this.receivedEvent = params.receivedEvent;
this.eoseTimeout = params.eoseTimeout || relay.baseEoseTimeout;
this.oneose = params.oneose;
this.onclose = params.onclose;
this.onevent = params.onevent || ((event) => {
console.warn(
`onevent() callback not defined for subscription '${this.id}' in relay ${this.relay.url}. event received:`,
event
);
});
}
fire() {
this.relay.send('["REQ","' + this.id + '",' + JSON.stringify(this.filters).substring(1));
this.eoseTimeoutHandle = setTimeout(this.receivedEose.bind(this), this.eoseTimeout);
}
receivedEose() {
if (this.eosed)
return;
clearTimeout(this.eoseTimeoutHandle);
this.eosed = true;
this.oneose?.();
}
close(reason = "closed by caller") {
if (!this.closed) {
this.relay.send('["CLOSE",' + JSON.stringify(this.id) + "]");
this.closed = true;
}
this.relay.openSubs.delete(this.id);
this.onclose?.(reason);
}
};
// abstract-pool.ts
var AbstractSimplePool = class {
relays = /* @__PURE__ */ new Map();
seenOn = /* @__PURE__ */ new Map();
trackRelays = false;
verifyEvent;
trustedRelayURLs = /* @__PURE__ */ new Set();
constructor(opts) {
this.verifyEvent = opts.verifyEvent;
}
async ensureRelay(url, params) {
url = normalizeURL(url);
let relay = this.relays.get(url);
if (!relay) {
relay = new AbstractRelay(url, {
verifyEvent: this.trustedRelayURLs.has(url) ? alwaysTrue : this.verifyEvent
});
if (params?.connectionTimeout)
relay.connectionTimeout = params.connectionTimeout;
this.relays.set(url, relay);
}
await relay.connect();
return relay;
}
close(relays) {
relays.map(normalizeURL).forEach((url) => {
this.relays.get(url)?.close();
});
}
subscribeMany(relays, filters, params) {
if (this.trackRelays) {
params.receivedEvent = (relay, id) => {
let set = this.seenOn.get(id);
if (!set) {
set = /* @__PURE__ */ new Set();
this.seenOn.set(id, set);
}
set.add(relay);
};
}
const _knownIds = /* @__PURE__ */ new Set();
const subs = [];
const eosesReceived = [];
let handleEose = (i2) => {
eosesReceived[i2] = true;
if (eosesReceived.filter((a) => a).length === relays.length) {
params.oneose?.();
handleEose = () => {
};
}
};
const closesReceived = [];
let handleClose = (i2, reason) => {
handleEose(i2);
closesReceived[i2] = reason;
if (closesReceived.filter((a) => a).length === relays.length) {
params.onclose?.(closesReceived);
handleClose = () => {
};
}
};
const localAlreadyHaveEventHandler = (id) => {
if (params.alreadyHaveEvent?.(id)) {
return true;
}
const have = _knownIds.has(id);
_knownIds.add(id);
return have;
};
const allOpened = Promise.all(
relays.map(normalizeURL).map(async (url, i2, arr) => {
if (arr.indexOf(url) !== i2) {
handleClose(i2, "duplicate url");
return;
}
let relay;
try {
relay = await this.ensureRelay(url, {
connectionTimeout: params.maxWait ? Math.max(params.maxWait * 0.8, params.maxWait - 1e3) : void 0
});
} catch (err) {
handleClose(i2, err?.message || String(err));
return;
}
let subscription = relay.subscribe(filters, {
...params,
oneose: () => handleEose(i2),
onclose: (reason) => handleClose(i2, reason),
alreadyHaveEvent: localAlreadyHaveEventHandler,
eoseTimeout: params.maxWait
});
subs.push(subscription);
})
);
return {
async close() {
await allOpened;
subs.forEach((sub) => {
sub.close();
});
}
};
}
subscribeManyEose(relays, filters, params) {
const subcloser = this.subscribeMany(relays, filters, {
...params,
oneose() {
subcloser.close();
}
});
return subcloser;
}
async querySync(relays, filter, params) {
return new Promise(async (resolve) => {
const events = [];
this.subscribeManyEose(relays, [filter], {
...params,
onevent(event) {
events.push(event);
},
onclose(_) {
resolve(events);
}
});
});
}
async get(relays, filter, params) {
filter.limit = 1;
const events = await this.querySync(relays, filter, params);
events.sort((a, b) => b.created_at - a.created_at);
return events[0] || null;
}
publish(relays, event) {
return relays.map(normalizeURL).map(async (url, i2, arr) => {
if (arr.indexOf(url) !== i2) {
return Promise.reject("duplicate url");
}
let r = await this.ensureRelay(url);
return r.publish(event);
});
}
};
// pool.ts
var SimplePool = class extends AbstractSimplePool {
constructor() {
super({ verifyEvent });
}
};
// nip46.ts
function parseBunkerInput() {
var _fetch2;
try {
_fetch2 = fetch;
} catch {
}
function requestSignedEvent() {
function useFetchImplementation(fetchImplementation) {
_fetch2 = fetchImplementation;
}
var BUNKER_REGEX = /^bunker:\/\/([0-9a-f]{64})\??([?\/\w:.=&%]*)$/;
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
async function parseBunkerInput(input) {
let match = input.match(BUNKER_REGEX);
if (match) {
try {
const pubkey = match[1];
const qs = new URLSearchParams(match[2]);
return {
pubkey,
relays: qs.getAll("relay"),
secret: qs.get("secret")
};
} catch (_err) {
}
}
return queryBunkerProfile(input);
}
async function queryBunkerProfile(nip05) {
const match = nip05.match(NIP05_REGEX);
if (!match)
return null;
const [_, name = "_", domain] = match;
try {
const url = `https://${domain}/.well-known/nostr.json?name=${name}`;
const res = await (await _fetch2(url, { redirect: "error" })).json();
let pubkey = res.names[name];
let relays = res.nip46[pubkey] || [];
return { pubkey, relays, secret: null };
} catch (_err) {
return null;
}
}
var BunkerSigner = class {
pool;
subCloser;
relays;
isOpen;
serial;
idPrefix;
listeners;
secretKey;
connectionSecret;
remotePubkey;
constructor(clientSecretKey, bp, params = {}) {
if (bp.relays.length === 0) {
throw new Error("no relays are specified for this bunker");
}
this.pool = params.pool || new SimplePool();
this.secretKey = clientSecretKey;
this.relays = bp.relays;
this.remotePubkey = bp.pubkey;
this.connectionSecret = bp.secret || "";
this.isOpen = false;
this.idPrefix = Math.random().toString(36).substring(7);
this.serial = 0;
this.listeners = {};
const listeners = this.listeners;
this.subCloser = this.pool.subscribeMany(
this.relays,
[{ kinds: [NostrConnect, NostrConnectAdmin], "#p": [getPublicKey(this.secretKey)] }],
{
async onevent(event) {
const { id, result, error } = JSON.parse(await decrypt(clientSecretKey, event.pubkey, event.content));
if (result === "auth_url") {
if (params.onauth) {
params.onauth(error);
} else {
console.warn(
`nostr-tools/nip46: remote signer ${bp.pubkey} tried to send an "auth_url"='${error}' but there was no onauth() callback configured.`
);
}
return;
}
let handler = listeners[id];
if (handler) {
if (error)
handler.reject(error);
else if (result)
handler.resolve(result);
delete listeners[id];
}
}
}
);
this.isOpen = true;
}
async close() {
this.isOpen = false;
this.subCloser.close();
}
async sendRequest(method, params) {
return new Promise(async (resolve, reject) => {
try {
if (!this.isOpen)
throw new Error("this signer is not open anymore, create a new one");
this.serial++;
const id = `${this.idPrefix}-${this.serial}`;
const encryptedContent = await encrypt(
this.secretKey,
this.remotePubkey,
JSON.stringify({ id, method, params })
);
const verifiedEvent = finalizeEvent(
{
kind: method === "create_account" ? NostrConnectAdmin : NostrConnect,
tags: [["p", this.remotePubkey]],
content: encryptedContent,
created_at: Math.floor(Date.now() / 1e3)
},
this.secretKey
);
this.listeners[id] = { resolve, reject };
await Promise.any(this.pool.publish(this.relays, verifiedEvent));
} catch (err) {
reject(err);
}
});
}
async ping() {
let resp = await this.sendRequest("ping", []);
if (resp !== "pong")
throw new Error(`result is not pong: ${resp}`);
}
async connect() {
await this.sendRequest("connect", [getPublicKey(this.secretKey), this.connectionSecret]);
}
async signEvent(event) {
let resp = await this.sendRequest("sign_event", [JSON.stringify(event)]);
let signed = JSON.parse(resp);
if (signed.pubkey === this.remotePubkey && verifyEvent(signed)) {
return signed;
} else {
throw new Error(`event returned from bunker is improperly signed: ${JSON.stringify(signed)}`);
}
}
};
async function createAccount(bunker, params, username, domain, email) {
if (email && !EMAIL_REGEX.test(email))
throw new Error("Invalid email");
let sk = generateSecretKey();
let rpc = new BunkerSigner(sk, bunker.bunkerPointer, params);
let pubkey = await rpc.sendRequest("create_account", [username, domain, email || ""]);
rpc.remotePubkey = pubkey;
await rpc.connect();
return rpc;
}
async function fetchCustodialbunkers(pool, relays) {
const events = await pool.querySync(relays, {
kinds: [Handlerinformation],
"#k": [NostrConnect.toString()]
});
const validatedBunkers = await Promise.all(
events.map(async (event) => {
try {
const content = JSON.parse(event.content);
const bp = await queryBunkerProfile(content.nip05);
if (bp && bp.pubkey === event.pubkey && bp.relays.length) {
return {
bunkerPointer: bp,
nip05: content.nip05,
domain: content.nip05.split("@")[1],
name: content.name || content.display_name,
picture: content.picture,
about: content.about,
website: content.website,
local: false
};
}
} catch (err) {
return void 0;
}
})
);
return validatedBunkers.filter((b) => b !== void 0);
}
export {
BUNKER_REGEX,
BunkerSigner,
createAccount,
fetchCustodialbunkers,
parseBunkerInput,
requestSignedEvent
useFetchImplementation
};

@@ -112,2 +112,3 @@ // kinds.ts

}
throw new Error("Unknown error in uploading file!");
}

@@ -114,0 +115,0 @@ try {

@@ -61,2 +61,3 @@ /** Events are **regular**, which means they're all expected to be stored by relays. */

export declare const NostrConnect = 24133;
export declare const NostrConnectAdmin = 24134;
export declare const HTTPAuth = 27235;

@@ -63,0 +64,0 @@ export declare const Followsets = 30000;

@@ -15,10 +15,2 @@ import { ProfilePointer } from './nip19.ts';

export declare function queryProfile(fullname: string): Promise<ProfilePointer | null>;
/** nostr.json result. */
export interface NIP05Result {
names: {
[name: string]: string;
};
relays?: {
[pubkey: string]: string[];
};
}
export declare function isValid(pubkey: string, nip05: string): Promise<boolean>;

@@ -0,1 +1,2 @@

export declare const Bech32MaxSize = 5000;
/**

@@ -43,2 +44,3 @@ * Bech32 regex.

export declare function noteEncode(hex: string): `note1${string}`;
export declare function encodeBytes<Prefix extends string>(prefix: Prefix, bytes: Uint8Array): `${Prefix}1${string}`;
export declare function nprofileEncode(profile: ProfilePointer): `nprofile1${string}`;

@@ -45,0 +47,0 @@ export declare function neventEncode(event: EventPointer): `nevent1${string}`;

@@ -1,2 +0,83 @@

export declare function parseBunkerInput(): void;
export declare function requestSignedEvent(): void;
import { UnsignedEvent, VerifiedEvent } from './core.ts';
import { AbstractSimplePool } from './abstract-pool.ts';
export declare function useFetchImplementation(fetchImplementation: any): void;
export declare const BUNKER_REGEX: RegExp;
export type BunkerPointer = {
relays: string[];
pubkey: string;
secret: null | string;
};
/** This takes either a bunker:// URL or a name@domain.com NIP-05 identifier
and returns a BunkerPointer -- or null in case of error */
export declare function parseBunkerInput(input: string): Promise<BunkerPointer | null>;
export type BunkerSignerParams = {
pool?: AbstractSimplePool;
onauth?: (url: string) => void;
};
export declare class BunkerSigner {
private pool;
private subCloser;
private relays;
private isOpen;
private serial;
private idPrefix;
private listeners;
private secretKey;
private connectionSecret;
remotePubkey: string;
/**
* Creates a new instance of the Nip46 class.
* @param relays - An array of relay addresses.
* @param remotePubkey - An optional remote public key. This is the key you want to sign as.
* @param secretKey - An optional key pair.
*/
constructor(clientSecretKey: Uint8Array, bp: BunkerPointer, params?: BunkerSignerParams);
close(): Promise<void>;
sendRequest(method: string, params: string[]): Promise<string>;
/**
* Sends a ping request to the remote server.
* Requires permission/access rights to bunker.
* @returns "Pong" if successful. The promise will reject if the response is not "pong".
*/
ping(): Promise<void>;
/**
* Connects to a remote server using the provided keys and remote public key.
* Optionally, a secret can be provided for additional authentication.
*
* @param remotePubkey - Optional the remote public key to connect to.
* @param secret - Optional secret for additional authentication.
* @returns "ack" if successful. The promise will reject if the response is not "ack".
*/
connect(): Promise<void>;
/**
* Signs an event using the remote private key.
* @param event - The event to sign.
* @returns A Promise that resolves to the signed event.
*/
signEvent(event: UnsignedEvent): Promise<VerifiedEvent>;
}
/**
* Creates an account with the specified username, domain, and optional email.
* @param bunkerPubkey - The public key of the bunker to use for the create_account call.
* @param username - The username for the account.
* @param domain - The domain for the account.
* @param email - The optional email for the account.
* @throws Error if the email is present but invalid.
* @returns A Promise that resolves to the auth_url that the client should follow to create an account.
*/
export declare function createAccount(bunker: BunkerProfile, params: BunkerSignerParams, username: string, domain: string, email?: string): Promise<BunkerSigner>;
/**
* Fetches info on available providers that announce themselves using NIP-89 events.
* @returns A promise that resolves to an array of available bunker objects.
*/
export declare function fetchCustodialbunkers(pool: AbstractSimplePool, relays: string[]): Promise<BunkerProfile[]>;
export type BunkerProfile = {
bunkerPointer: BunkerPointer;
domain: string;
nip05: string;
name: string;
picture: string;
about: string;
website: string;
local: boolean;
};

@@ -1,2 +0,2 @@

import { Event, EventTemplate, UnsignedEvent, VerifiedEvent } from './core';
import { Event, EventTemplate, UnsignedEvent, VerifiedEvent } from './core.ts';
export declare function serializeEvent(evt: UnsignedEvent): string;

@@ -3,0 +3,0 @@ export declare function getEventHash(event: UnsignedEvent): string;

{
"type": "module",
"name": "nostr-tools",
"version": "2.1.5",
"version": "2.1.7",
"description": "Tools for making a Nostr client.",

@@ -23,2 +23,7 @@ "repository": {

},
"./core": {
"import": "./lib/esm/core.js",
"require": "./lib/cjs/core.js",
"types": "./lib/types/core.d.ts"
},
"./pure": {

@@ -74,7 +79,2 @@ "import": "./lib/esm/pure.js",

},
"./nip44": {
"import": "./lib/esm/nip44.js",
"require": "./lib/cjs/nip44.js",
"types": "./lib/types/nip44.d.ts"
},
"./nip05": {

@@ -155,2 +155,17 @@ "import": "./lib/esm/nip05.js",

},
"./nip44": {
"import": "./lib/esm/nip44.js",
"require": "./lib/cjs/nip44.js",
"types": "./lib/types/nip44.d.ts"
},
"./nip46": {
"import": "./lib/esm/nip46.js",
"require": "./lib/cjs/nip46.js",
"types": "./lib/types/nip46.d.ts"
},
"./nip49": {
"import": "./lib/esm/nip49.js",
"require": "./lib/cjs/nip49.js",
"types": "./lib/types/nip49.d.ts"
},
"./nip57": {

@@ -161,2 +176,12 @@ "import": "./lib/esm/nip57.js",

},
"./nip94": {
"import": "./lib/esm/nip94.js",
"require": "./lib/cjs/nip94.js",
"types": "./lib/types/nip94.d.ts"
},
"./nip96": {
"import": "./lib/esm/nip96.js",
"require": "./lib/cjs/nip96.js",
"types": "./lib/types/nip96.d.ts"
},
"./nip98": {

@@ -167,2 +192,7 @@ "import": "./lib/esm/nip98.js",

},
"./nip99": {
"import": "./lib/esm/nip99.js",
"require": "./lib/cjs/nip99.js",
"types": "./lib/types/nip99.d.ts"
},
"./fakejson": {

@@ -224,3 +254,2 @@ "import": "./lib/esm/fakejson.js",

"prettier": "^3.0.3",
"tsd": "^0.22.0",
"typescript": "^5.0.4"

@@ -227,0 +256,0 @@ },

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc