From 349e26b62888e72d7987ee6c1baab761b998ed22 Mon Sep 17 00:00:00 2001 From: SwiftHawk Date: Tue, 16 Apr 2024 11:12:29 +0500 Subject: [PATCH] fix: use relays from nip65 for broadcasting DMs --- src/controllers/MetadataController.ts | 68 +++++++++++++++++++++++++-- src/controllers/NostrController.ts | 31 ++++++++---- src/pages/home/index.tsx | 41 ++++++++++++---- src/types/nostr.ts | 5 ++ 4 files changed, 124 insertions(+), 21 deletions(-) diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts index 2dabaa9..b2c116d 100644 --- a/src/controllers/MetadataController.ts +++ b/src/controllers/MetadataController.ts @@ -7,7 +7,7 @@ import { verifyEvent, Event } from 'nostr-tools' -import { ProfileMetadata } from '../types' +import { ProfileMetadata, RelaySet } from '../types' import { NostrController } from '.' import { toast } from 'react-toastify' @@ -64,6 +64,66 @@ export class MetadataController { throw new Error('Mo metadata found.') } + public findRelayListMetadata = async (hexKey: string) => { + const eventFilter: Filter = { + kinds: [kinds.RelayList], + authors: [hexKey] + } + + const pool = new SimplePool() + + let relayEvent = await pool + .get([this.specialMetadataRelay], eventFilter) + .catch((err) => { + console.error(err) + return null + }) + + if (!relayEvent) { + const mostPopularRelays = import.meta.env.VITE_MOST_POPULAR_RELAYS + const hardcodedPopularRelays = (mostPopularRelays || '').split(' ') + const relays = [...hardcodedPopularRelays] + + relayEvent = await pool.get(relays, eventFilter).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.') + } + public extractProfileMetadataContent = (event: VerifiedEvent) => { try { return JSON.parse(event.content) as ProfileMetadata @@ -94,9 +154,9 @@ export class MetadataController { } await this.nostrController - .publishEvent(signedMetadataEvent, this.specialMetadataRelay) - .then((res) => { - toast.success(`Metadata: ${res}`) + .publishEvent(signedMetadataEvent, [this.specialMetadataRelay]) + .then((relays) => { + toast.success(`Metadata event published on: ${relays.join('\n')}`) }) .catch((err) => { toast.error(err.message) diff --git a/src/controllers/NostrController.ts b/src/controllers/NostrController.ts index e9e34d4..a2b0f1e 100644 --- a/src/controllers/NostrController.ts +++ b/src/controllers/NostrController.ts @@ -1,4 +1,3 @@ -import { EventEmitter } from 'tseep' import NDK, { NDKEvent, NDKNip46Signer, @@ -9,12 +8,13 @@ import NDK, { import { Event, EventTemplate, - Relay, + SimplePool, UnsignedEvent, finalizeEvent, nip04, nip19 } from 'nostr-tools' +import { EventEmitter } from 'tseep' import { updateNsecbunkerPubkey } from '../store/actions' import { AuthState, LoginMethods } from '../store/auth/types' import store from '../store/store' @@ -195,14 +195,29 @@ export class NostrController extends EventEmitter { } /** - * Function will publish provided event to the provided relay + * Function will publish provided event to the provided relays */ - publishEvent = async (event: Event, relayUrl: string) => { - const relay = await Relay.connect(relayUrl) - await relay.publish(event) - relay.close() + publishEvent = async (event: Event, relays: string[]) => { + const simplePool = new SimplePool() - return `event published to relay: ${relayUrl}` + const promises = simplePool.publish(relays, event) + + const results = await Promise.allSettled(promises) + + const publishedRelays: string[] = [] + + console.log('results of publish event :>> ', results) + + results.forEach((result, index) => { + if (result.status === 'fulfilled') { + publishedRelays.push(relays[index]) + } + }) + + if (publishedRelays.length === 0) + throw new Error(`Couldn't publish to any relay`) + + return publishedRelays } /** diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index cf0bad2..2381ec9 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -278,6 +278,7 @@ export const HomePage = () => { setLoadingSpinnerDesc('Signing auth event for uploading zip') const authEvent = await nostrController.signEvent(event) + // todo: use env variable const FILE_STORAGE_URL = 'https://blossom.sigit.io' const response = await axios.put(`${FILE_STORAGE_URL}/upload`, file, { @@ -304,9 +305,16 @@ export const HomePage = () => { setLoadingSpinnerDesc('encrypting content for DM') - // todo: add timeout - const encrypted = await nostrController - .nip04Encrypt(pubkey, content) + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => { + reject(new Error('Timeout occurred')) + }, 15000) // timeout duration = 15 sec + }) + + const encrypted = await Promise.race([ + nostrController.nip04Encrypt(pubkey, content), + timeoutPromise + ]) .then((res) => { return res }) @@ -339,15 +347,30 @@ export const HomePage = () => { if (!signedEvent) return - // const metadata = metadataMap[pubkey] - setLoadingSpinnerDesc('Publishing encrypted DM') - // todo: do not use hardcoded relay + const metadataController = new MetadataController() + const relaySet = await metadataController + .findRelayListMetadata(pubkey) + .catch((err) => { + toast.error( + err.message || 'An error occurred while finding relay list metadata' + ) + return null + }) + + if (!relaySet) return + + // NOTE: according to Nip65 + // DMs SHOULD only be broadcasted to the author's WRITE relays and to the receiver's READ relays to keep maximum privacy. + if (relaySet.read.length === 0) { + toast.error('No relay found for publishing encrypted DM') + } + await nostrController - .publishEvent(signedEvent, 'wss://relay.damus.io') - .then(() => { - toast.success('DM sent to first signer') + .publishEvent(signedEvent, relaySet.read) + .then((relays) => { + toast.success(`Encrypted DM sent on: ${relays.join('\n')}`) }) .catch((err) => { console.log('err :>> ', err) diff --git a/src/types/nostr.ts b/src/types/nostr.ts index 00e5ade..87a9b27 100644 --- a/src/types/nostr.ts +++ b/src/types/nostr.ts @@ -7,3 +7,8 @@ export interface SignedEvent { id: string sig: string } + +export interface RelaySet { + read: string[] + write: string[] +}