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
pull/72/head
OFF0 2 years ago
parent 57be701ef9
commit 87cd5f21b3
Signed by: offbyn
GPG Key ID: 94A2F643C51F37FA

1267
package-lock.json generated

File diff suppressed because it is too large Load Diff

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

@ -1,22 +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 {bounce} from './utils.js';
import {zeroLeadingBitsCount} from './cryptoutils.js'; import {zeroLeadingBitsCount} from './cryptoutils.js';
import {elem, parseTextContent} from './domutil.js'; import {elem, parseTextContent} from './domutil.js';
import {dateTime, formatTime} from './timeutil.js'; import {dateTime, formatTime} from './timeutil.js';
// curl -H 'accept: application/nostr+json' https://relay.nostr.ch/ // 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://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});
function onEvent(evt, relay) { function onEvent(evt, relay) {
switch (evt.kind) { switch (evt.kind) {
case 0: case 0:
@ -46,14 +35,8 @@ let pubkey = localStorage.getItem('pub_key') || (() => {
return pubkey; return pubkey;
})(); })();
const subList = [];
const unSubAll = () => {
subList.forEach(sub => sub.unsub());
subList.length = 0;
};
function sub24hFeed() { function sub24hFeed() {
subList.push(pool.sub({ sub({
cb: onEvent, cb: onEvent,
filter: { filter: {
kinds: [0, 1, 2, 7], kinds: [0, 1, 2, 7],
@ -61,7 +44,7 @@ function sub24hFeed() {
since: Math.floor((Date.now() * 0.001) - (24 * 60 * 60)), since: Math.floor((Date.now() * 0.001) - (24 * 60 * 60)),
limit: 50, limit: 50,
} }
})); });
} }
function subNoteAndProfile(id) { function subNoteAndProfile(id) {
@ -71,18 +54,18 @@ function subNoteAndProfile(id) {
} }
function subTextNote(eventId) { function subTextNote(eventId) {
subList.push(pool.sub({ sub({
cb: onEvent, cb: onEvent,
filter: { filter: {
ids: [eventId], ids: [eventId],
kinds: [1], kinds: [1],
limit: 1, limit: 1,
} }
})); });
} }
function subProfile(pubkey) { function subProfile(pubkey) {
subList.push(pool.sub({ sub({
cb: (evt, relay) => { cb: (evt, relay) => {
console.log('found profile, unsub subTextNote somehow') console.log('found profile, unsub subTextNote somehow')
// renderProfile(evt, relay); // renderProfile(evt, relay);
@ -93,16 +76,16 @@ function subProfile(pubkey) {
kinds: [0], kinds: [0],
limit: 1, limit: 1,
} }
})); });
// get notes for profile // get notes for profile
subList.push(pool.sub({ sub({
cb: onEvent, cb: onEvent,
filter: { filter: {
authors: [pubkey], authors: [pubkey],
kinds: [1], kinds: [1],
limit: 50, limit: 50,
} }
})); });
} }
const containers = [ const containers = [
@ -115,39 +98,6 @@ const containers = [
]; ];
let activeContainerIndex = null; 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 textNoteList = []; // could use indexDB
const eventRelayMap = {}; // eventId: [relay1, relay2] const eventRelayMap = {}; // eventId: [relay1, relay2]
@ -356,6 +306,8 @@ function linkPreview(href, id, relay) {
}); });
} }
const writeInput = document.querySelector('textarea[name="message"]');
function createTextNote(evt, relay) { function createTextNote(evt, relay) {
const {host, img, name, time, userName} = getMetadata(evt, relay); const {host, img, name, time, userName} = getMetadata(evt, relay);
const replies = replyList.filter(({replyTo}) => replyTo === evt.id); const replies = replyList.filter(({replyTo}) => replyTo === evt.id);
@ -625,7 +577,6 @@ function getReplyTo(evt) {
} }
const writeForm = document.querySelector('#writeForm'); const writeForm = document.querySelector('#writeForm');
const writeInput = document.querySelector('textarea[name="message"]');
const elemShrink = () => { const elemShrink = () => {
const height = writeInput.style.height || writeInput.getBoundingClientRect().height; const height = writeInput.style.height || writeInput.getBoundingClientRect().height;
@ -718,17 +669,16 @@ async function upvote(eventId, eventPubkey) {
return; return;
} }
const privatekey = localStorage.getItem('private_key'); const privatekey = localStorage.getItem('private_key');
const sig = await signEvent(newReaction, privatekey).catch(console.error); const sig = signEvent(newReaction, privatekey);
// TODO: validateEvent
if (sig) { if (sig) {
statusElem.textContent = 'publishing…'; statusElem.textContent = 'publishing…';
const ev = await pool.publish({...newReaction, sig}, (status, url) => { publish({...newReaction, sig}, (relay, error) => {
if (status === 0) { if (error) {
console.info(`publish request sent to ${url}`); return console.error(error, relay);
}
if (status === 1) {
console.info(`event published by ${url}`);
} }
}).catch(console.error); console.info(`event published by ${relay}`);
});
reactionBtn.disabled = false; reactionBtn.disabled = false;
} }
} }
@ -736,7 +686,7 @@ async function upvote(eventId, eventPubkey) {
// send // send
const sendStatus = document.querySelector('#sendstatus'); const sendStatus = document.querySelector('#sendstatus');
const onSendError = err => sendStatus.textContent = err.message; const onSendError = err => sendStatus.textContent = err.message;
const publish = document.querySelector('#publish'); const publishBtn = document.querySelector('#publish');
writeForm.addEventListener('submit', async (e) => { writeForm.addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
// const pubkey = localStorage.getItem('pub_key'); // const pubkey = localStorage.getItem('pub_key');
@ -753,7 +703,7 @@ writeForm.addEventListener('submit', async (e) => {
sendStatus.textContent = ''; sendStatus.textContent = '';
writeInput.value = ''; writeInput.value = '';
writeInput.style.removeProperty('height'); writeInput.style.removeProperty('height');
publish.disabled = true; publishBtn.disabled = true;
if (replyTo) { if (replyTo) {
localStorage.removeItem('reply_to'); localStorage.removeItem('reply_to');
publishView.append(writeForm); publishView.append(writeForm);
@ -772,23 +722,22 @@ writeForm.addEventListener('submit', async (e) => {
close(); close();
return; return;
} }
const sig = await signEvent(newEvent, privatekey).catch(onSendError); const sig = signEvent(newEvent, privatekey);
// TODO validateEvent
if (sig) { if (sig) {
sendStatus.textContent = 'publishing…'; sendStatus.textContent = 'publishing…';
const ev = await pool.publish({...newEvent, sig}, (status, url) => { publish({...newEvent, sig}, (relay, error) => {
if (status === 0) { if (error) {
console.info(`publish request sent to ${url}`); return console.log(error, relay);
} }
if (status === 1) { console.info(`publish request sent to ${relay}`);
close(); close();
// console.info(`event published by ${url}`, ev);
}
}); });
} }
}); });
writeInput.addEventListener('input', () => { writeInput.addEventListener('input', () => {
publish.disabled = !writeInput.value.trimRight(); publishBtn.disabled = !writeInput.value.trimRight();
updateElemHeight(writeInput); updateElemHeight(writeInput);
}); });
writeInput.addEventListener('blur', () => sendStatus.textContent = ''); writeInput.addEventListener('blur', () => sendStatus.textContent = '');
@ -918,7 +867,7 @@ switch(location.pathname) {
window.addEventListener('popstate', (event) => { window.addEventListener('popstate', (event) => {
// console.log(`popstate: ${location.pathname}, state: ${JSON.stringify(event.state)}`); // console.log(`popstate: ${location.pathname}, state: ${JSON.stringify(event.state)}`);
unSubAll(); unsubAll();
if (event.state?.pubkey) { if (event.state?.pubkey) {
subProfile(event.state.pubkey); subProfile(event.state.pubkey);
view(`/${event.state.pubkey}`); view(`/${event.state.pubkey}`);
@ -961,18 +910,18 @@ document.body.addEventListener('click', (e) => {
switch(href) { switch(href) {
case '/': case '/':
navigate('/'); navigate('/');
unSubAll(); unsubAll();
sub24hFeed(); sub24hFeed();
break; break;
default: default:
switch(a.dataset.nav) { switch(a.dataset.nav) {
case '/[profile]': case '/[profile]':
unSubAll(); unsubAll();
subProfile(pubkey); subProfile(pubkey);
navigate({pubkey}); navigate({pubkey});
break; break;
case '/[note]': case '/[note]':
unSubAll(); unsubAll();
subTextNote(id) subTextNote(id)
navigate({note: id}); navigate({note: id});
break; break;
@ -1144,18 +1093,18 @@ profileForm.addEventListener('submit', async (e) => {
return; return;
} }
const privatekey = localStorage.getItem('private_key'); const privatekey = localStorage.getItem('private_key');
const sig = await signEvent(newProfile, privatekey).catch(console.error); const sig = signEvent(newProfile, privatekey);
// TODO: validateEvent
if (sig) { if (sig) {
const ev = await pool.publish({...newProfile, sig}, (status, url) => { publish({...newProfile, sig}, (relay, error) => {
if (status === 0) { if (error) {
console.info(`publish request sent to ${url}`); return console.error(error, relay);
} }
if (status === 1) { console.info(`publish request sent to ${relay}`);
profileStatus.textContent = 'profile metadata successfully published'; profileStatus.textContent = 'profile metadata successfully published';
profileStatus.hidden = false; profileStatus.hidden = false;
profileSubmit.disabled = true; profileSubmit.disabled = true;
} });
}).catch(console.error);
} }
}); });
@ -1223,7 +1172,10 @@ function validatePow(evt) {
function powEvent(evt, options) { function powEvent(evt, options) {
const {difficulty, statusElem, timeout} = options; const {difficulty, statusElem, timeout} = options;
if (difficulty === 0) { if (difficulty === 0) {
return Promise.resolve(evt); return Promise.resolve({
id: getEventHash(evt),
...evt,
});
} }
const cancelBtn = elem('button', {className: 'btn-inline'}, [elem('small', {}, 'cancel')]); const cancelBtn = elem('button', {className: 'btn-inline'}, [elem('small', {}, 'cancel')]);
statusElem.replaceChildren('working…', cancelBtn); 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); const id = getEventHash(event);
if (zeroLeadingBitsCount(id) === difficulty) { if (zeroLeadingBitsCount(id) === difficulty) {
console.timeEnd('pow'); console.timeEnd('pow');
return event; return {id, ...event};
} }
} }
} }

Loading…
Cancel
Save