diff --git a/src/main.ts b/src/main.ts index e06506e..51f16f0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -49,17 +49,17 @@ const renderFeed = bounce(() => { switch (view.type) { case 'note': textNoteList - .concat(replyList) + .concat(replyList) // search id in notes and replies .filter(note => note.id === view.id) .forEach(renderNote); break; case 'profile': const isEvent = (evt?: T): evt is T => evt !== undefined; [ - ...textNoteList + ...textNoteList // get notes .filter(note => note.pubkey === view.id), - ...replyList.filter(reply => reply.pubkey === view.id) - .map(reply => textNoteList.find(note => note.id === reply.replyTo) || replyList.find(note => note.id === reply.replyTo) ) + ...replyList.filter(reply => reply.pubkey === view.id) // and replies + .map(reply => textNoteList.find(note => note.id === reply.replyTo)) // and the replied to notes .filter(isEvent) ] .sort(sortByCreatedAt) diff --git a/src/relays.ts b/src/relays.ts index 66b7250..c17a9c6 100644 --- a/src/relays.ts +++ b/src/relays.ts @@ -8,11 +8,12 @@ type SubCallback = ( type Subscribe = { cb: SubCallback; filter: Filter; + unsub?: boolean; }; -const relayList: Array = []; const subList: Array = []; const currentSubList: Array = []; +const relayMap = new Map(); export const addRelay = async (url: string) => { const relay = relayInit(url); @@ -25,13 +26,13 @@ export const addRelay = async (url: string) => { try { await relay.connect(); currentSubList.forEach(({cb, filter}) => subscribe(cb, filter, relay)); - relayList.push(relay); + relayMap.set(url, relay); } catch { console.warn(`could not connect to ${url}`); } }; -const unsubscribe = (sub: Sub) => { +export const unsubscribe = (sub: Sub) => { sub.unsub(); subList.splice(subList.indexOf(sub), 1); }; @@ -40,28 +41,38 @@ const subscribe = ( cb: SubCallback, filter: Filter, relay: Relay, + unsub?: boolean ) => { 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)); + if (unsub) { + sub.on('eose', () => { + console.log('eose', relay.url); + unsubscribe(sub); + }); + } + return sub; }; export const sub = (obj: Subscribe) => { currentSubList.push(obj); - subscribeAll(obj.cb, obj.filter); + relayMap.forEach((relay) => subscribe(obj.cb, obj.filter, relay, obj.unsub)); +}; + +export const subOnce = ( + obj: Subscribe & {relay: string} +) => { + const relay = relayMap.get(obj.relay); + if (relay) { + const sub = subscribe(obj.cb, obj.filter, relay); + sub.on('eose', () => { + console.log('eose', obj.relay); + unsubscribe(sub); + }); + } }; export const unsubAll = () => { @@ -78,7 +89,7 @@ export const publish = ( event: Event, cb: PublishCallback, ) => { - relayList.forEach(relay => { + relayMap.forEach((relay, url) => { const pub = relay.publish(event); pub.on('ok', () => { console.info(`${relay.url} has accepted our event`); @@ -91,9 +102,10 @@ export const publish = ( }); }; -addRelay('wss://relay.snort.social'); // good one + +addRelay('wss://relay.snort.social'); addRelay('wss://nostr.bitcoiner.social'); addRelay('wss://nostr.mom'); addRelay('wss://relay.nostr.bg'); addRelay('wss://nos.lol'); -addRelay('wss://relay.nostr.ch'); +// addRelay('wss://relay.nostr.ch'); diff --git a/src/subscriptions.ts b/src/subscriptions.ts index 19e63c1..4dd408a 100644 --- a/src/subscriptions.ts +++ b/src/subscriptions.ts @@ -1,5 +1,6 @@ import {Event} from 'nostr-tools'; -import {sub, unsubAll} from './relays'; +import {getReplyTo, hasEventTag, isMention} from './events'; +import {sub, subOnce, unsubAll} from './relays'; type SubCallback = ( event: Event, @@ -9,17 +10,86 @@ type SubCallback = ( /** subscribe to global feed */ export const sub24hFeed = (onEvent: SubCallback) => { unsubAll(); + const now = Math.floor(Date.now() * 0.001); + const pubkeys = new Set(); + const notes = new Set(); + sub({ // get past events + cb: (evt, relay) => { + pubkeys.add(evt.pubkey); + notes.add(evt.id); + onEvent(evt, relay); + }, + filter: { + kinds: [1], + until: now, + since: Math.floor(now - (24 * 60 * 60)), + limit: 100, + }, + unsub: true + }); + + setTimeout(() => { + // get profile info + sub({ + cb: onEvent, + filter: { + authors: Array.from(pubkeys), + kinds: [0], + limit: pubkeys.size, + }, + unsub: true, + }); + pubkeys.clear(); + + // get reactions + sub({ + cb: onEvent, + filter: { + '#e': Array.from(notes), + kinds: [7], + until: now, + since: Math.floor(now - (24 * 60 * 60)), + }, + unsub: true, + }); + notes.clear(); + }, 2000); + + // subscribe to future notes, reactions and profile updates sub({ - cb: onEvent, + cb: (evt, relay) => { + onEvent(evt, relay); + subOnce({ // get profil data + relay, + cb: onEvent, + filter: { + authors: [evt.pubkey], + kinds: [0], + limit: 1, + } + }); + }, filter: { - kinds: [0, 1, 2, 7], - // until: Math.floor(Date.now() * 0.001), - since: Math.floor((Date.now() * 0.001) - (24 * 60 * 60)), - limit: 50, - } + kinds: [0, 1, 7], + since: now, + }, }); }; +/** subscribe to global feed */ +// export const simpleSub24hFeed = (onEvent: SubCallback) => { +// unsubAll(); +// sub({ +// cb: onEvent, +// filter: { +// kinds: [0, 1, 2, 7], +// // until: Math.floor(Date.now() * 0.001), +// since: Math.floor((Date.now() * 0.001) - (24 * 60 * 60)), +// limit: 250, +// } +// }); +// }; + /** subscribe to a note id (nip-19) */ export const subNote = ( eventId: string, @@ -32,14 +102,34 @@ export const subNote = ( ids: [eventId], kinds: [1], limit: 1, - } + }, + unsub: true, }); + + const replies = new Set(); + + const onReply = (evt: Event, relay: string) => { + replies.add(evt.id) + onEvent(evt, relay); + unsubAll(); + sub({ + cb: onEvent, + filter: { + '#e': Array.from(replies), + kinds: [1, 7], + }, + unsub: true, + }); + }; + + replies.add(eventId) sub({ - cb: onEvent, + cb: onReply, filter: { '#e': [eventId], kinds: [1, 7], - } + }, + unsub: true, }); }; @@ -59,7 +149,25 @@ export const subProfile = ( }); // get notes for profile sub({ - cb: onEvent, + cb: (evt, relay) => { + const repliesTo = new Set(); + if (evt.tags.some(hasEventTag) && !evt.tags.some(isMention)) { + const note = getReplyTo(evt); + if (note && !repliesTo.has(note)) { + repliesTo.add(note); + subOnce({ + relay, + cb: onEvent, + filter: { + ids: [note], + kinds: [1], + limit: 1, + } + }) + } + } + onEvent(evt, relay); + }, filter: { authors: [pubkey], kinds: [1], diff --git a/src/utils/dom.ts b/src/utils/dom.ts index e550577..864a6f4 100644 --- a/src/utils/dom.ts +++ b/src/utils/dom.ts @@ -184,8 +184,8 @@ export const updateElemHeight = ( export const elemArticle = ( content: Array, - attrs: Attributes = {} + attrs: Attributes = {}, ) => { - const className = attrs.className ? ['mbox', attrs?.className].join(' ') : 'mbox'; + const className = attrs.className ? `mbox ${attrs.className}` : 'mbox'; return elem('article', {...attrs, className}, content); };