2024-08-18 22:48:48 +05:00
|
|
|
import { Event, Filter, kinds, UnsignedEvent } from 'nostr-tools'
|
2024-08-07 15:52:14 +03:00
|
|
|
import { RelayList } from 'nostr-tools/kinds'
|
2024-08-18 22:48:48 +05:00
|
|
|
import { getRelayInfo, unixNow } from '.'
|
|
|
|
import { NostrController, relayController } from '../controllers'
|
2024-08-07 15:52:14 +03:00
|
|
|
import { localCache } from '../services'
|
2024-08-21 00:16:21 +05:00
|
|
|
import { RelayMap, RelaySet } from '../types'
|
|
|
|
import {
|
|
|
|
DEFAULT_LOOK_UP_RELAY_LIST,
|
|
|
|
ONE_WEEK_IN_MS,
|
|
|
|
SIGIT_RELAY
|
|
|
|
} from './const'
|
2024-08-07 15:52:14 +03:00
|
|
|
|
2024-08-07 18:24:12 +03:00
|
|
|
const READ_MARKER = 'read'
|
|
|
|
const WRITE_MARKER = 'write'
|
2024-08-07 15:52:14 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2024-08-07 18:24:12 +03:00
|
|
|
const findRelayListAndUpdateCache = async (
|
|
|
|
lookUpRelays: string[],
|
|
|
|
hexKey: string
|
|
|
|
): Promise<Event | null> => {
|
2024-08-07 15:52:14 +03:00
|
|
|
try {
|
|
|
|
const eventFilter: Filter = {
|
|
|
|
kinds: [RelayList],
|
|
|
|
authors: [hexKey]
|
|
|
|
}
|
2024-08-15 22:13:39 +05:00
|
|
|
|
2024-08-21 00:16:21 +05:00
|
|
|
console.count('findRelayListAndUpdateCache')
|
2024-08-15 22:13:39 +05:00
|
|
|
const event = await relayController.fetchEvent(eventFilter, lookUpRelays)
|
2024-08-07 15:52:14 +03:00
|
|
|
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
|
2024-08-07 18:24:12 +03:00
|
|
|
const cachedRelayListMetadataEvent =
|
|
|
|
await localCache.getUserRelayListMetadata(hexKey)
|
2024-08-07 15:52:14 +03:00
|
|
|
|
|
|
|
// Check if the cached event is not older than one week
|
2024-08-07 18:24:12 +03:00
|
|
|
if (
|
|
|
|
cachedRelayListMetadataEvent &&
|
|
|
|
isOlderThanOneWeek(cachedRelayListMetadataEvent.cachedAt)
|
|
|
|
) {
|
2024-08-07 15:52:14 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-08-21 00:16:21 +05:00
|
|
|
const isOlderThanOneDay = (cachedAt: number) => {
|
|
|
|
return Date.now() - cachedAt < ONE_WEEK_IN_MS
|
|
|
|
}
|
|
|
|
|
2024-08-07 15:52:14 +03:00
|
|
|
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])
|
2024-08-07 18:24:12 +03:00
|
|
|
} else if (marker === WRITE_MARKER) {
|
2024-08-07 15:52:14 +03:00
|
|
|
obj.write.push(tag[1])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (tag.length === 2) {
|
|
|
|
obj.read.push(tag[1])
|
|
|
|
obj.write.push(tag[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
return obj
|
|
|
|
}
|
|
|
|
|
2024-08-18 22:48:48 +05:00
|
|
|
/**
|
|
|
|
* Provides relay map.
|
|
|
|
* @param npub - user's npub
|
|
|
|
* @returns - promise that resolves into relay map and a timestamp when it has been updated.
|
|
|
|
*/
|
|
|
|
const getRelayMap = async (
|
|
|
|
npub: string
|
|
|
|
): Promise<{ map: RelayMap; mapUpdated?: number }> => {
|
|
|
|
// More info about this kind of event available https://github.com/nostr-protocol/nips/blob/master/65.md
|
|
|
|
const eventFilter: Filter = {
|
|
|
|
kinds: [kinds.RelayList],
|
|
|
|
authors: [npub]
|
|
|
|
}
|
|
|
|
|
2024-08-21 00:16:21 +05:00
|
|
|
console.count('getRelayMap')
|
2024-08-18 22:48:48 +05:00
|
|
|
const event = await relayController
|
2024-08-21 00:16:21 +05:00
|
|
|
.fetchEvent(eventFilter, DEFAULT_LOOK_UP_RELAY_LIST)
|
2024-08-18 22:48:48 +05:00
|
|
|
.catch((err) => {
|
|
|
|
return Promise.reject(err)
|
|
|
|
})
|
|
|
|
|
|
|
|
if (event) {
|
|
|
|
// Handle founded 10002 event
|
|
|
|
const relaysMap: RelayMap = {}
|
|
|
|
|
|
|
|
// 'r' stands for 'relay'
|
|
|
|
const relayTags = event.tags.filter((tag) => tag[0] === 'r')
|
|
|
|
|
|
|
|
relayTags.forEach((tag) => {
|
|
|
|
const uri = tag[1]
|
|
|
|
const relayType = tag[2]
|
|
|
|
|
|
|
|
// if 3rd element of relay tag is undefined, relay is WRITE and READ
|
|
|
|
relaysMap[uri] = {
|
|
|
|
write: relayType ? relayType === 'write' : true,
|
|
|
|
read: relayType ? relayType === 'read' : true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
Object.keys(relaysMap).forEach((relayUrl) => {
|
2024-08-21 00:16:21 +05:00
|
|
|
console.log('being called from getRelayMap')
|
2024-08-18 22:48:48 +05:00
|
|
|
relayController.connectRelay(relayUrl)
|
|
|
|
})
|
|
|
|
|
|
|
|
getRelayInfo(Object.keys(relaysMap))
|
|
|
|
|
|
|
|
return Promise.resolve({ map: relaysMap, mapUpdated: event.created_at })
|
|
|
|
} else {
|
|
|
|
return Promise.resolve({ map: getDefaultRelayMap() })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Publishes relay map.
|
|
|
|
* @param relayMap - relay map.
|
|
|
|
* @param npub - user's npub.
|
|
|
|
* @param extraRelaysToPublish - optional relays to publish relay map.
|
|
|
|
* @returns - promise that resolves into a string representing publishing result.
|
|
|
|
*/
|
|
|
|
const publishRelayMap = async (
|
|
|
|
relayMap: RelayMap,
|
|
|
|
npub: string,
|
|
|
|
extraRelaysToPublish?: string[]
|
|
|
|
): Promise<string> => {
|
|
|
|
const timestamp = unixNow()
|
|
|
|
const relayURIs = Object.keys(relayMap)
|
|
|
|
|
|
|
|
// More info about this kind of event available https://github.com/nostr-protocol/nips/blob/master/65.md
|
|
|
|
const tags: string[][] = relayURIs.map((relayURI) =>
|
|
|
|
[
|
|
|
|
'r',
|
|
|
|
relayURI,
|
|
|
|
relayMap[relayURI].read && relayMap[relayURI].write
|
|
|
|
? ''
|
|
|
|
: relayMap[relayURI].write
|
|
|
|
? 'write'
|
|
|
|
: 'read'
|
|
|
|
].filter((value) => value !== '')
|
|
|
|
)
|
|
|
|
|
|
|
|
const newRelayMapEvent: UnsignedEvent = {
|
|
|
|
kind: kinds.RelayList,
|
|
|
|
tags,
|
|
|
|
content: '',
|
|
|
|
pubkey: npub,
|
|
|
|
created_at: timestamp
|
|
|
|
}
|
|
|
|
|
|
|
|
const nostrController = NostrController.getInstance()
|
|
|
|
const signedEvent = await nostrController.signEvent(newRelayMapEvent)
|
|
|
|
|
|
|
|
let relaysToPublish = relayURIs
|
|
|
|
|
|
|
|
// Add extra relays if provided
|
|
|
|
if (extraRelaysToPublish) {
|
|
|
|
relaysToPublish = [...relaysToPublish, ...extraRelaysToPublish]
|
|
|
|
}
|
|
|
|
|
|
|
|
// If relay map is empty, use most popular relay URIs
|
|
|
|
if (!relaysToPublish.length) {
|
2024-08-21 00:16:21 +05:00
|
|
|
relaysToPublish = DEFAULT_LOOK_UP_RELAY_LIST
|
2024-08-18 22:48:48 +05:00
|
|
|
}
|
|
|
|
const publishResult = await relayController.publish(
|
|
|
|
signedEvent,
|
|
|
|
relaysToPublish
|
|
|
|
)
|
|
|
|
|
|
|
|
if (publishResult && publishResult.length) {
|
|
|
|
return Promise.resolve(
|
|
|
|
`Relay Map published on: ${publishResult.join('\n')}`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.reject('Publishing updated relay map was unsuccessful.')
|
|
|
|
}
|
|
|
|
|
2024-08-07 15:52:14 +03:00
|
|
|
export {
|
|
|
|
findRelayListAndUpdateCache,
|
|
|
|
findRelayListInCache,
|
2024-08-07 17:54:45 +03:00
|
|
|
getDefaultRelayMap,
|
2024-08-15 22:13:39 +05:00
|
|
|
getDefaultRelaySet,
|
2024-08-18 22:48:48 +05:00
|
|
|
getRelayMap,
|
2024-08-15 22:13:39 +05:00
|
|
|
getUserRelaySet,
|
2024-08-21 00:16:21 +05:00
|
|
|
isOlderThanOneDay,
|
|
|
|
isOlderThanOneWeek,
|
|
|
|
publishRelayMap
|
2024-08-07 18:24:12 +03:00
|
|
|
}
|