From 30809ccbce8f54b469604ba303b74951dbf37bdf Mon Sep 17 00:00:00 2001 From: OFF0 Date: Mon, 1 May 2023 15:04:37 +0200 Subject: [PATCH] feed: update subscriptions playing around with subscriptions, feed now loads profile names, profile views also show replied to notes and notes recursively search for replies or replies. this commit does too many subscriptions, ideally a max of three subscriptions are done at once. in future commit it would be nice to subscribe modularly, have done callbacks or push into subscribe next queues. --- src/main.ts | 8 +-- src/relays.ts | 48 ++++++++++------ src/subscriptions.ts | 130 +++++++++++++++++++++++++++++++++++++++---- src/utils/dom.ts | 4 +- 4 files changed, 155 insertions(+), 35 deletions(-) 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); };