Merge pull request '130-fix-empty-relay-issue' (#131) from 130-fix-empty-relay-issue into staging
Some checks failed
Release to Staging / build_and_release (push) Failing after 33s

Reviewed-on: #131
This commit is contained in:
eugene 2024-08-07 15:27:19 +00:00
commit f49a9e0b40
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
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'
/**
* 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]
})
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
}