relays: type and upgrade to nostr-tools@1.6.0

move and typed relay related code to relays.ts

upgrade nostr-tools to latest greatest, major version with
breaking changes:

- relayPool is gone in favor of SimplePool, but this commit just
  used relayInit directly as relays should become configurable at
  some point
OFF0 2 years ago
parent ad81d892e1
commit 589e65ae40
Signed by: offbyn
GPG Key ID: 94A2F643C51F37FA

1080
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -8,7 +8,10 @@
"esbuild": "^0.14.54",
"esbuild-plugin-alias": "^0.2.1",
"events": "^3.3.0",
"nostr-tools": "0.24.1"
"readable-stream": "4.3.0"
},
"dependencies": {
"nostr-tools": "1.6.0"
},
"scripts": {
"build": "node tools/build.js",

@ -1,24 +1,11 @@
import {relayPool, generatePrivateKey, getPublicKey, signEvent} from 'nostr-tools';
import {generatePrivateKey, getEventHash, getPublicKey, signEvent} from 'nostr-tools';
import {publish, sub, unsubAll} from './relays';
import {bounce} from './utils.js';
import {zeroLeadingBitsCount} from './cryptoutils.js';
import {elem, parseTextContent} from './domutil.js';
import {dateTime, formatTime} from './timeutil.js';
// curl -H 'accept: application/nostr+json' https://relay.nostr.ch/
const pool = relayPool();
// pool.addRelay('wss://relay.nostr.info', {read: true, write: true});
// pool.addRelay('wss://nostr.openchain.fr', {read: true, write: true});
// pool.addRelay('wss://relay.damus.io', {read: true, write: true});
// pool.addRelay('wss://relay.snort.social', {read: true, write: true});
// pool.addRelay('wss://relay.nostr.ch', {read: true, write: true});
// pool.addRelay('wss://nostr.openchain.fr', {read: true, write: true});
// pool.addRelay('wss://eden.nostr.land', {read: true, write: true});
// pool.addRelay('wss://nostr.einundzwanzig.space', {read: true, write: true});
// pool.addRelay('wss://relay.nostrich.de', {read: true, write: true});
// pool.addRelay('wss://nostr.cercatrova.me', {read: true, write: true});
pool.addRelay('wss://nostr.x1ddos.ch', {read: true, write: true});
function onEvent(evt, relay) {
switch (evt.kind) {
case 0:
@ -48,14 +35,8 @@ let pubkey = localStorage.getItem('pub_key') || (() => {
return pubkey;
})();
const subList = [];
const unSubAll = () => {
subList.forEach(sub => sub.unsub());
subList.length = 0;
};
function sub24hFeed() {
subList.push(pool.sub({
sub({
cb: onEvent,
filter: {
kinds: [0, 1, 2, 7],
@ -63,7 +44,7 @@ function sub24hFeed() {
since: Math.floor((Date.now() * 0.001) - (24 * 60 * 60)),
limit: 50,
}
}));
});
}
function subNoteAndProfile(id) {
@ -73,18 +54,18 @@ function subNoteAndProfile(id) {
}
function subTextNote(eventId) {
subList.push(pool.sub({
sub({
cb: onEvent,
filter: {
ids: [eventId],
kinds: [1],
limit: 1,
}
}));
});
}
function subProfile(pubkey) {
subList.push(pool.sub({
sub({
cb: (evt, relay) => {
console.log('found profile, unsub subTextNote somehow')
// renderProfile(evt, relay);
@ -95,16 +76,16 @@ function subProfile(pubkey) {
kinds: [0],
limit: 1,
}
}));
});
// get notes for profile
subList.push(pool.sub({
sub({
cb: onEvent,
filter: {
authors: [pubkey],
kinds: [1],
limit: 50,
}
}));
});
}
const containers = [
@ -117,39 +98,6 @@ const containers = [
];
let activeContainerIndex = null;
// const profileContainer = document.querySelector('#profile');
// const profileAbout = profileContainer.querySelector('.profile-about');
// const profileName = profileContainer.querySelector('.profile-name');
// const profilePubkey = profileContainer.querySelector('.profile-pubkey');
// const profilePubkeyLabel = profileContainer.querySelector('.profile-pubkey-label');
// const profileImage = profileContainer.querySelector('.profile-image');
// const textNoteContainer = document.querySelector('#textnote');
// function clearProfile() {
// profileAbout.textContent = '';
// profileName.textContent = '';
// profilePubkey.textContent = '';
// profilePubkeyLabel.hidden = true;
// profileImage.removeAttribute('src');
// profileImage.hidden = true;
// }
// function renderProfile(evt, relay) {
// profileContainer.dataset.pubkey = evt.pubkey;
// profilePubkey.textContent = evt.pubkey;
// profilePubkeyLabel.hidden = false;
// const content = parseContent(evt.content);
// if (content) {
// profileAbout.textContent = content.about;
// profileName.textContent = content.name;
// const noxyImg = validatePow(evt) && getNoxyUrl('data', content.picture, evt.id, relay);
// if (noxyImg) {
// profileImage.setAttribute('src', noxyImg);
// profileImage.hidden = false;
// }
// }
// }
const textNoteList = []; // could use indexDB
const eventRelayMap = {}; // eventId: [relay1, relay2]
@ -358,6 +306,8 @@ function linkPreview(href, id, relay) {
});
}
const writeInput = document.querySelector('textarea[name="message"]');
function createTextNote(evt, relay) {
const {host, img, name, time, userName} = getMetadata(evt, relay);
const replies = replyList.filter(({replyTo}) => replyTo === evt.id);
@ -627,7 +577,6 @@ function getReplyTo(evt) {
}
const writeForm = document.querySelector('#writeForm');
const writeInput = document.querySelector('textarea[name="message"]');
const elemShrink = () => {
const height = writeInput.style.height || writeInput.getBoundingClientRect().height;
@ -720,17 +669,16 @@ async function upvote(eventId, eventPubkey) {
return;
}
const privatekey = localStorage.getItem('private_key');
const sig = await signEvent(newReaction, privatekey).catch(console.error);
const sig = signEvent(newReaction, privatekey);
// TODO: validateEvent
if (sig) {
statusElem.textContent = 'publishing…';
const ev = await pool.publish({...newReaction, sig}, (status, url) => {
if (status === 0) {
console.info(`publish request sent to ${url}`);
}
if (status === 1) {
console.info(`event published by ${url}`);
publish({...newReaction, sig}, (relay, error) => {
if (error) {
return console.error(error, relay);
}
}).catch(console.error);
console.info(`event published by ${relay}`);
});
reactionBtn.disabled = false;
}
}
@ -738,7 +686,7 @@ async function upvote(eventId, eventPubkey) {
// send
const sendStatus = document.querySelector('#sendstatus');
const onSendError = err => sendStatus.textContent = err.message;
const publish = document.querySelector('#publish');
const publishBtn = document.querySelector('#publish');
writeForm.addEventListener('submit', async (e) => {
e.preventDefault();
// const pubkey = localStorage.getItem('pub_key');
@ -755,7 +703,7 @@ writeForm.addEventListener('submit', async (e) => {
sendStatus.textContent = '';
writeInput.value = '';
writeInput.style.removeProperty('height');
publish.disabled = true;
publishBtn.disabled = true;
if (replyTo) {
localStorage.removeItem('reply_to');
publishView.append(writeForm);
@ -774,23 +722,22 @@ writeForm.addEventListener('submit', async (e) => {
close();
return;
}
const sig = await signEvent(newEvent, privatekey).catch(onSendError);
const sig = signEvent(newEvent, privatekey);
// TODO validateEvent
if (sig) {
sendStatus.textContent = 'publishing…';
const ev = await pool.publish({...newEvent, sig}, (status, url) => {
if (status === 0) {
console.info(`publish request sent to ${url}`);
publish({...newEvent, sig}, (relay, error) => {
if (error) {
return console.log(error, relay);
}
if (status === 1) {
console.info(`publish request sent to ${relay}`);
close();
// console.info(`event published by ${url}`, ev);
}
});
}
});
writeInput.addEventListener('input', () => {
publish.disabled = !writeInput.value.trimRight();
publishBtn.disabled = !writeInput.value.trimRight();
updateElemHeight(writeInput);
});
writeInput.addEventListener('blur', () => sendStatus.textContent = '');
@ -920,7 +867,7 @@ switch(location.pathname) {
window.addEventListener('popstate', (event) => {
// console.log(`popstate: ${location.pathname}, state: ${JSON.stringify(event.state)}`);
unSubAll();
unsubAll();
if (event.state?.pubkey) {
subProfile(event.state.pubkey);
view(`/${event.state.pubkey}`);
@ -963,18 +910,18 @@ document.body.addEventListener('click', (e) => {
switch(href) {
case '/':
navigate('/');
unSubAll();
unsubAll();
sub24hFeed();
break;
default:
switch(a.dataset.nav) {
case '/[profile]':
unSubAll();
unsubAll();
subProfile(pubkey);
navigate({pubkey});
break;
case '/[note]':
unSubAll();
unsubAll();
subTextNote(id)
navigate({note: id});
break;
@ -1146,18 +1093,18 @@ profileForm.addEventListener('submit', async (e) => {
return;
}
const privatekey = localStorage.getItem('private_key');
const sig = await signEvent(newProfile, privatekey).catch(console.error);
const sig = signEvent(newProfile, privatekey);
// TODO: validateEvent
if (sig) {
const ev = await pool.publish({...newProfile, sig}, (status, url) => {
if (status === 0) {
console.info(`publish request sent to ${url}`);
publish({...newProfile, sig}, (relay, error) => {
if (error) {
return console.error(error, relay);
}
if (status === 1) {
console.info(`publish request sent to ${relay}`);
profileStatus.textContent = 'profile metadata successfully published';
profileStatus.hidden = false;
profileSubmit.disabled = true;
}
}).catch(console.error);
});
}
});
@ -1225,7 +1172,10 @@ function validatePow(evt) {
function powEvent(evt, options) {
const {difficulty, statusElem, timeout} = options;
if (difficulty === 0) {
return Promise.resolve(evt);
return Promise.resolve({
id: getEventHash(evt),
...evt,
});
}
const cancelBtn = elem('button', {className: 'btn-inline'}, [elem('small', {}, 'cancel')]);
statusElem.replaceChildren('working…', cancelBtn);

@ -0,0 +1,99 @@
import {Event, Filter, relayInit, Relay, Sub} from 'nostr-tools';
type SubCallback = (
event: Readonly<Event>,
relay: Readonly<string>,
) => void;
type Subscribe = {
cb: SubCallback;
filter: Filter;
};
const relayList: Array<Relay> = [];
const subList: Array<Sub> = [];
const currentSubList: Array<Subscribe> = [];
export const addRelay = async (url: string) => {
const relay = relayInit(url);
relay.on('connect', () => {
console.info(`connected to ${relay.url}`);
});
relay.on('error', () => {
console.warn(`failed to connect to ${relay.url}`);
});
try {
await relay.connect();
currentSubList.forEach(({cb, filter}) => subscribe(cb, filter, relay));
relayList.push(relay);
} catch {
console.warn(`could not connect to ${url}`);
}
};
const unsubscribe = (sub: Sub) => {
sub.unsub();
subList.splice(subList.indexOf(sub), 1);
};
const subscribe = (
cb: SubCallback,
filter: Filter,
relay: Relay,
) => {
const sub = relay.sub([filter]);
subList.push(sub);
sub.on('event', (event: Event) => {
cb(event, relay.url);
});
sub.on('eose', () => {
// console.log('eose', relay.url);
// unsubscribe(sub);
});
};
const subscribeAll = (
cb: SubCallback,
filter: Filter,
) => {
relayList.forEach(relay => subscribe(cb, filter, relay));
};
export const sub = (obj: Subscribe) => {
currentSubList.push(obj);
subscribeAll(obj.cb, obj.filter);
};
export const unsubAll = () => {
subList.forEach(unsubscribe);
currentSubList.length = 0;
};
type PublishCallback = (
relay: string,
errorMessage?: string,
) => void;
export const publish = (
event: Event,
cb: PublishCallback,
) => {
relayList.forEach(relay => {
const pub = relay.publish(event);
pub.on('ok', () => {
console.info(`${relay.url} has accepted our event`);
cb(relay.url);
});
pub.on('failed', (reason: any) => {
console.error(`failed to publish to ${relay.url}: ${reason}`);
cb(relay.url, reason);
});
});
};
addRelay('wss://relay.snort.social'); // good one
addRelay('wss://nostr.bitcoiner.social');
addRelay('wss://nostr.mom');
addRelay('wss://relay.nostr.bg');
addRelay('wss://nos.lol');
addRelay('wss://relay.nostr.ch');

@ -26,7 +26,7 @@ function mine(event, difficulty, timeout = 5) {
const id = getEventHash(event);
if (zeroLeadingBitsCount(id) === difficulty) {
console.timeEnd('pow');
return event;
return {id, ...event};
}
}
}

Loading…
Cancel
Save