sigit.io/src/utils/relays.ts

246 lines
6.3 KiB
TypeScript
Raw Normal View History

import { Event, Filter, kinds, UnsignedEvent } from 'nostr-tools'
import { RelayList } from 'nostr-tools/kinds'
import { getRelayInfo, unixNow } from '.'
import { NostrController, relayController } from '../controllers'
import { localCache } from '../services'
import { RelayMap, RelaySet } from '../types'
import {
DEFAULT_LOOK_UP_RELAY_LIST,
ONE_WEEK_IN_MS,
SIGIT_RELAY
} from './const'
2024-08-07 18:24:12 +03:00
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
*/
2024-08-07 18:24:12 +03:00
const findRelayListAndUpdateCache = async (
lookUpRelays: string[],
hexKey: string
): Promise<Event | null> => {
try {
const eventFilter: Filter = {
kinds: [RelayList],
authors: [hexKey]
}
console.count('findRelayListAndUpdateCache')
const event = await relayController.fetchEvent(eventFilter, lookUpRelays)
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)
// Check if the cached event is not older than one week
2024-08-07 18:24:12 +03:00
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 isOlderThanOneDay = (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])
2024-08-07 18:24:12 +03:00
} 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
}
/**
* 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]
}
console.count('getRelayMap')
const event = await relayController
.fetchEvent(eventFilter, DEFAULT_LOOK_UP_RELAY_LIST)
.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) => {
console.log('being called from getRelayMap')
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) {
relaysToPublish = DEFAULT_LOOK_UP_RELAY_LIST
}
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.')
}
export {
findRelayListAndUpdateCache,
findRelayListInCache,
getDefaultRelayMap,
getDefaultRelaySet,
getRelayMap,
getUserRelaySet,
isOlderThanOneDay,
isOlderThanOneWeek,
publishRelayMap
2024-08-07 18:24:12 +03:00
}