130-fix-empty-relay-issue #131

Merged
eugene merged 7 commits from 130-fix-empty-relay-issue into staging 2024-08-07 15:27:20 +00:00
5 changed files with 159 additions and 99 deletions

View File

@ -16,6 +16,13 @@ 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,
isOlderThanOneWeek
} from '../utils/relays.ts'
export class MetadataController extends EventEmitter {
private nostrController: NostrController
@ -127,10 +134,8 @@ export class MetadataController extends EventEmitter {
// If cached metadata is found, check its validity
if (cachedMetadataEvent) {
const oneWeekInMS = 7 * 24 * 60 * 60 * 1000 // Number of milliseconds in one week
// Check if the cached metadata is older than one week
if (Date.now() - cachedMetadataEvent.cachedAt > oneWeekInMS) {
if (isOlderThanOneWeek(cachedMetadataEvent.cachedAt)) {
// If older than one week, find the metadata from relays in background
this.checkForMoreRecentMetadata(hexKey, cachedMetadataEvent.event)
@ -144,100 +149,32 @@ 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<RelaySet> => {
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) => {

View File

@ -44,6 +44,7 @@ import {
getNsecBunkerDelegatedKey,
verifySignedEvent
} from '../utils'
import { getDefaultRelayMap } from '../utils/relays.ts'
export class NostrController extends EventEmitter {
private static instance: NostrController
@ -650,7 +651,7 @@ export class NostrController extends EventEmitter {
*/
getRelayMap = async (
npub: string
): Promise<{ map: RelayMap; mapUpdated: number }> => {
): Promise<{ map: RelayMap; mapUpdated?: number }> => {
const mostPopularRelays = await this.getMostPopularRelays()
const pool = new SimplePool()
@ -691,7 +692,7 @@ export class NostrController extends EventEmitter {
return Promise.resolve({ map: relaysMap, mapUpdated: event.created_at })
} else {
return Promise.reject('User relays were not found.')
return Promise.resolve({ map: getDefaultRelayMap() })
}
}

View File

@ -91,7 +91,7 @@ export const RelaysPage = () => {
if (isMounted) {
if (
!relaysState?.mapUpdated ||
newRelayMap.mapUpdated > relaysState?.mapUpdated
newRelayMap?.mapUpdated !== undefined && (newRelayMap?.mapUpdated > relaysState?.mapUpdated)
) {
if (
!relaysState?.map ||

View File

@ -4,3 +4,9 @@ export const EMPTY: string = ''
export const MARK_TYPE_TRANSLATION: { [key: string]: string } = {
[MarkType.FULLNAME.valueOf()]: 'Full Name'
}
/**
* Number of milliseconds in one week.
* Calc based on: 7 * 24 * 60 * 60 * 1000
*/
export const ONE_WEEK_IN_MS: number = 604800000
eugene marked this conversation as resolved
Review

we should use this const instead of const oneWeekInMS = 7 * 24 * 60 * 60 * 1000 // Number of milliseconds in one week at

const oneWeekInMS = 7 * 24 * 60 * 60 * 1000 // Number of milliseconds in one week

we should use this const instead of `const oneWeekInMS = 7 * 24 * 60 * 60 * 1000 // Number of milliseconds in one week` at https://git.nostrdev.com/sigit/sigit.io/src/commit/c1a6ac246b719739ee7e0eb4d7e09ea10bc64ce1/src/controllers/MetadataController.ts#L130
export const SIGIT_RELAY: string = 'wss://relay.sigit.io'

116
src/utils/relays.ts Normal file
View File

@ -0,0 +1,116 @@
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_MARKER = 'write'
eugene marked this conversation as resolved Outdated
Outdated
Review

small typo

small typo
/**
* 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<Event | null> => {
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<Event | null> => {
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<RelaySet>(toRelaySet, getDefaultRelaySet())
}
const getDefaultRelaySet = (): RelaySet => ({
read: [SIGIT_RELAY],
write: [SIGIT_RELAY]
eugene marked this conversation as resolved
Review

Maybe we can use this function as an util, it's used in MetadataContoller.ts too?

Maybe we can use this function as an util, it's used in `MetadataContoller.ts` too?
})
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_MARKER) {
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,
isOlderThanOneWeek
}