130-fix-empty-relay-issue #131
@ -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) => {
|
||||
|
@ -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() })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 ||
|
||||
|
@ -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
|
||||
export const SIGIT_RELAY: string = 'wss://relay.sigit.io'
|
||||
|
116
src/utils/relays.ts
Normal file
116
src/utils/relays.ts
Normal 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]
|
||||
eugene marked this conversation as resolved
enes
commented
Maybe we can use this function as an util, it's used in 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user
we should use this const instead of
const oneWeekInMS = 7 * 24 * 60 * 60 * 1000 // Number of milliseconds in one week
at