From 52fe523196986d651c7fa14224e2b4324683a6f4 Mon Sep 17 00:00:00 2001 From: Eugene Date: Wed, 7 Aug 2024 15:52:14 +0300 Subject: [PATCH] fix: amends the relay look up method to return default relay set --- src/controllers/MetadataController.ts | 119 ++++++-------------------- src/utils/const.ts | 8 +- src/utils/relays.ts | 108 +++++++++++++++++++++++ 3 files changed, 141 insertions(+), 94 deletions(-) create mode 100644 src/utils/relays.ts diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts index 88f8a75..e2f6852 100644 --- a/src/controllers/MetadataController.ts +++ b/src/controllers/MetadataController.ts @@ -16,6 +16,12 @@ import { queryNip05 } from '../utils' import NDK, { NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk' import { EventEmitter } from 'tseep' import { localCache } from '../services' +import { + findRelayListAndUpdateCache, + findRelayListInCache, + getDefaultRelaySet, + getUserRelaySet +} from '../utils/relays.ts' export class MetadataController extends EventEmitter { private nostrController: NostrController @@ -144,100 +150,27 @@ export class MetadataController extends EventEmitter { return this.checkForMoreRecentMetadata(hexKey, null) } - public findRelayListMetadata = async (hexKey: string) => { - let relayEvent: Event | null = null + /** + * Based on the hexKey of the current user, this method attempts to retrieve a relay set. + * @func findRelayListInCache first checks if there is already an up-to-date + * relay list available in cache; if not - + * @func findRelayListAndUpdateCache checks if the relevant relay event is available from + * the purple pages relay; + * @func findRelayListAndUpdateCache will run again if the previous two calls return null and + * check if the relevant relay event can be obtained from 'most popular relays' + * If relay event is found, it will be saved in cache for future use + * @param hexKey of the current user + * @return RelaySet which will contain either relays extracted from the user Relay Event + * or a fallback RelaySet with Sigit's Relay + */ + public findRelayListMetadata = async (hexKey: string): Promise => { + const relayEvent = await findRelayListInCache(hexKey) + || await findRelayListAndUpdateCache([this.specialMetadataRelay], hexKey) + || await findRelayListAndUpdateCache(await this.nostrController.getMostPopularRelays(), hexKey) - // Attempt to retrieve the metadata event from the local cache - const cachedRelayListMetadataEvent = - await localCache.getUserRelayListMetadata(hexKey) - - if (cachedRelayListMetadataEvent) { - const oneWeekInMS = 7 * 24 * 60 * 60 * 1000 // Number of milliseconds in one week - - // Check if the cached event is not older than one week - if (Date.now() - cachedRelayListMetadataEvent.cachedAt < oneWeekInMS) { - relayEvent = cachedRelayListMetadataEvent.event - } - } - - // define filter for relay list - const eventFilter: Filter = { - kinds: [kinds.RelayList], - authors: [hexKey] - } - - const pool = new SimplePool() - - // Try to get the relayList event from a special relay (wss://purplepag.es) - if (!relayEvent) { - relayEvent = await pool - .get([this.specialMetadataRelay], eventFilter) - .then((event) => { - if (event) { - // update the event in local cache - localCache.addUserRelayListMetadata(event) - } - return event - }) - .catch((err) => { - console.error(err) - return null - }) - } - - if (!relayEvent) { - // If no valid relayList event is found from the special relay, get the most popular relays - const mostPopularRelays = - await this.nostrController.getMostPopularRelays() - - // Query the most popular relays for relayList event - relayEvent = await pool - .get(mostPopularRelays, eventFilter) - .then((event) => { - if (event) { - // update the event in local cache - localCache.addUserRelayListMetadata(event) - } - return event - }) - .catch((err) => { - console.error(err) - return null - }) - } - - if (relayEvent) { - const relaySet: RelaySet = { - read: [], - write: [] - } - - // a list of r tags with relay URIs and a read or write marker. - const relayTags = relayEvent.tags.filter((tag) => tag[0] === 'r') - - // Relays marked as read / write are called READ / WRITE relays, respectively - relayTags.forEach((tag) => { - if (tag.length >= 3) { - const marker = tag[2] - - if (marker === 'read') { - relaySet.read.push(tag[1]) - } else if (marker === 'write') { - relaySet.write.push(tag[1]) - } - } - - // If the marker is omitted, the relay is used for both purposes - if (tag.length === 2) { - relaySet.read.push(tag[1]) - relaySet.write.push(tag[1]) - } - }) - - return relaySet - } - - throw new Error('No relay list metadata found.') + return relayEvent + ? getUserRelaySet(relayEvent.tags) + : getDefaultRelaySet() } public extractProfileMetadataContent = (event: Event) => { diff --git a/src/utils/const.ts b/src/utils/const.ts index 4f8c233..aed1807 100644 --- a/src/utils/const.ts +++ b/src/utils/const.ts @@ -3,4 +3,10 @@ import { MarkType } from '../types/drawing.ts' export const EMPTY: string = '' export const MARK_TYPE_TRANSLATION: { [key: string]: string } = { [MarkType.FULLNAME.valueOf()]: 'Full Name' -} \ No newline at end of file +} +/** + * Number of milliseconds in one week. + * Calc based on: 7 * 24 * 60 * 60 * 1000 + */ +export const ONE_WEEK_IN_MS: number = 604800000 +export const SIGIT_RELAY: string = 'wss://relay.sigit.io' \ No newline at end of file diff --git a/src/utils/relays.ts b/src/utils/relays.ts new file mode 100644 index 0000000..676a59a --- /dev/null +++ b/src/utils/relays.ts @@ -0,0 +1,108 @@ +import { Filter, SimplePool } from 'nostr-tools' +import { RelayList } from 'nostr-tools/kinds' +import { Event } from 'nostr-tools' +import { localCache } from '../services' +import { ONE_WEEK_IN_MS, SIGIT_RELAY } from './const.ts' +import { RelayMap, RelaySet } from '../types' + +const READ_MARKER = "read" +const WRITE_MARKET = "write" + +/** + * Attempts to find a relay list from the provided lookUpRelays. + * If the relay list is found, it will be added to the user relay list metadata. + * @param lookUpRelays + * @param hexKey + * @return found relay list or null + */ +const findRelayListAndUpdateCache = async (lookUpRelays: string[], hexKey: string): Promise => { + try { + const eventFilter: Filter = { + kinds: [RelayList], + authors: [hexKey] + } + const pool = new SimplePool() + const event = await pool.get(lookUpRelays, eventFilter) + if (event) { + await localCache.addUserRelayListMetadata(event) + } + return event + } catch (error) { + console.error(error) + return null + } +} + +/** + * Attempts to find a relay list in cache. If it is present, it will check that the cached event is not + * older than one week. + * @param hexKey + * @return RelayList event if it's not older than a week; otherwise null + */ +const findRelayListInCache = async (hexKey: string): Promise => { + try { + // Attempt to retrieve the metadata event from the local cache + const cachedRelayListMetadataEvent = await localCache.getUserRelayListMetadata(hexKey) + + // Check if the cached event is not older than one week + if (cachedRelayListMetadataEvent && isOlderThanOneWeek(cachedRelayListMetadataEvent.cachedAt)) { + return cachedRelayListMetadataEvent.event + } + + return null + } catch (error) { + console.error(error) + return null + } +} + +/** + * Transforms a list of relay tags from a Nostr Event to a RelaySet. + * @param tags + */ +const getUserRelaySet = (tags: string[][]): RelaySet => { + return tags + .filter(isRelayTag) + .reduce(toRelaySet, getDefaultRelaySet()) +} + +const getDefaultRelaySet = (): RelaySet => ({ + read: [SIGIT_RELAY], + write: [SIGIT_RELAY] +}) + +const getDefaultRelayMap = (): RelayMap => ({ + [SIGIT_RELAY]: { write: true, read: true } +}) + +const isOlderThanOneWeek = (cachedAt: number) => { + return Date.now() - cachedAt < ONE_WEEK_IN_MS +} + +const isRelayTag = (tag: string[]): boolean => tag[0] === 'r' + +const toRelaySet = (obj: RelaySet, tag: string[]): RelaySet => { + if (tag.length >= 3) { + const marker = tag[2] + + if (marker === READ_MARKER) { + obj.read.push(tag[1]) + } else if (marker === WRITE_MARKET) { + obj.write.push(tag[1]) + } + } + if (tag.length === 2) { + obj.read.push(tag[1]) + obj.write.push(tag[1]) + } + + return obj +} + +export { + findRelayListAndUpdateCache, + findRelayListInCache, + getUserRelaySet, + getDefaultRelaySet, + getDefaultRelayMap, +} \ No newline at end of file