import NDK, { NDKNip46Signer, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk'; import { ndkAdapter } from '@nostr-fetch/adapter-ndk'; import { message } from '@tauri-apps/plugin-dialog'; import { fetch } from '@tauri-apps/plugin-http'; import { NostrFetcher } from 'nostr-fetch'; import { useEffect, useMemo, useState } from 'react'; import NDKCacheAdapterTauri from '@libs/ndk/cache'; import { useStorage } from '@libs/storage/provider'; export const NDKInstance = () => { const [ndk, setNDK] = useState(undefined); const [relayUrls, setRelayUrls] = useState([]); const { db } = useStorage(); const fetcher = useMemo( () => (ndk ? NostrFetcher.withCustomPool(ndkAdapter(ndk)) : null), [ndk] ); // TODO: fully support NIP-11 async function getExplicitRelays() { try { // get relays const relays = await db.getExplicitRelayUrls(); const onlineRelays = new Set(relays); for (const relay of relays) { try { const url = new URL(relay); const res = await fetch(`https://${url.hostname}`, { method: 'GET', headers: { Accept: 'application/nostr+json', }, }); if (!res.ok) { console.info(`${relay} is not working, skipping...`); onlineRelays.delete(relay); } } catch { console.warn(`${relay} is not working, skipping...`); onlineRelays.delete(relay); } } // return all online relays return [...onlineRelays]; } catch (e) { console.error(e); } } async function getSigner(instance: NDK) { if (!db.account) return null; const localSignerPrivkey = await db.secureLoad(db.account.pubkey + '-bunker'); const userPrivkey = await db.secureLoad(db.account.pubkey); // NIP-46 Signer if (localSignerPrivkey) { const localSigner = new NDKPrivateKeySigner(localSignerPrivkey); const remoteSigner = new NDKNip46Signer(instance, db.account.id, localSigner); await remoteSigner.blockUntilReady(); return remoteSigner; } // Privkey Signer if (userPrivkey) { return new NDKPrivateKeySigner(userPrivkey); } } async function initNDK() { const outboxSetting = await db.getSettingValue('outbox'); const explicitRelayUrls = await getExplicitRelays(); const dexieAdapter = new NDKCacheAdapterTauri(db); const instance = new NDK({ explicitRelayUrls, cacheAdapter: dexieAdapter, outboxRelayUrls: ['wss://purplepag.es'], enableOutboxModel: outboxSetting === '1', }); try { // connect await instance.connect(2000); // add signer const signer = await getSigner(instance); instance.signer = signer; // update account's metadata if (db.account) { const circleSetting = await db.getSettingValue('circles'); const user = instance.getUser({ hexpubkey: db.account.pubkey }); const follows = await user.follows(); const relayList = await user.relayList(); const followsAsArr = []; follows.forEach((user) => { followsAsArr.push(user.pubkey); }); // update user's follows await db.updateAccount('follows', JSON.stringify(followsAsArr)); if (circleSetting !== '1') await db.updateAccount('circles', JSON.stringify(followsAsArr)); // update user's relay list if (relayList) { for (const relay of relayList.relays) { await db.createRelay(relay); } } } } catch (error) { await message(`NDK instance init failed: ${error}`, { title: 'Lume', type: 'error', }); } setNDK(instance); setRelayUrls(explicitRelayUrls); } useEffect(() => { if (!ndk) initNDK(); }, []); return { ndk, relayUrls, fetcher, }; };