Files
lume/src/libs/ndk/instance.ts
2023-11-28 09:00:46 +07:00

147 lines
4.2 KiB
TypeScript

import NDK, { NDKNip46Signer, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import { ndkAdapter } from '@nostr-fetch/adapter-ndk';
import { ask } from '@tauri-apps/plugin-dialog';
import { fetch } from '@tauri-apps/plugin-http';
import { relaunch } from '@tauri-apps/plugin-process';
import { NostrFetcher } from 'nostr-fetch';
import { useEffect, useMemo, useState } from 'react';
import { toast } from 'sonner';
import NDKCacheAdapterTauri from '@libs/ndk/cache';
import { useStorage } from '@libs/storage/provider';
export const NDKInstance = () => {
const [ndk, setNDK] = useState<NDK | undefined>(undefined);
const [relayUrls, setRelayUrls] = useState<string[]>([]);
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);
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 8000);
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',
},
signal: controller.signal,
});
if (!res.ok) {
toast.warning(`${relay} is not working, skipping...`);
onlineRelays.delete(relay);
}
toast.success(`Connected to ${relay}`);
} catch {
toast.warning(`${relay} is not working, skipping...`);
onlineRelays.delete(relay);
} finally {
clearTimeout(timeoutId);
}
}
// return all online relays
return [...onlineRelays];
} catch (e) {
console.error(e);
}
}
async function getSigner(nsecbunker?: boolean) {
if (!db.account) return;
// NIP-46 Signer
if (nsecbunker) {
const localSignerPrivkey = await db.secureLoad(db.account.pubkey + '-nsecbunker');
const localSigner = new NDKPrivateKeySigner(localSignerPrivkey);
if (!localSigner) return null;
// await remoteSigner.blockUntilReady();
return new NDKNip46Signer(ndk, db.account.id, localSigner);
}
// Private key Signer
const userPrivkey = await db.secureLoad(db.account.pubkey);
if (!userPrivkey) return null;
return new NDKPrivateKeySigner(userPrivkey);
}
async function initNDK() {
try {
const outboxSetting = await db.getSettingValue('outbox');
const bunkerSetting = await db.getSettingValue('nsecbunker');
const signer = await getSigner(!!parseInt(bunkerSetting));
const explicitRelayUrls = await getExplicitRelays();
const tauriAdapter = new NDKCacheAdapterTauri(db);
const instance = new NDK({
explicitRelayUrls,
cacheAdapter: tauriAdapter,
outboxRelayUrls: ['wss://purplepag.es'],
blacklistRelayUrls: [],
enableOutboxModel: !!parseInt(outboxSetting),
});
instance.signer = signer;
// connect
await instance.connect();
// update account's metadata
if (db.account) {
const user = instance.getUser({ pubkey: db.account.pubkey });
if (user) {
const follows = [...(await user.follows())].map((user) => user.pubkey);
const relayList = await user.relayList();
// update user's follows
await db.updateAccount('follows', JSON.stringify(follows));
if (relayList)
// update user's relays
for (const relay of relayList.relays) {
await db.createRelay(relay);
}
}
}
setNDK(instance);
setRelayUrls(explicitRelayUrls);
} catch (e) {
const yes = await ask(
`Something wrong, Lume is not working as expected, do you want to relaunch app?`,
{
title: 'Lume',
type: 'error',
okLabel: 'Yes',
}
);
if (yes) relaunch();
}
}
useEffect(() => {
if (!ndk) initNDK();
}, []);
return {
ndk,
relayUrls,
fetcher,
};
};