improve startup time

This commit is contained in:
Ren Amamiya 2023-09-27 14:53:01 +07:00
parent b339e842ca
commit 2b50fc438f
13 changed files with 244 additions and 142 deletions

View File

@ -16,7 +16,7 @@ export function ImportStep3Screen() {
const setStep = useOnboarding((state) => state.setStep); const setStep = useOnboarding((state) => state.setStep);
const { db } = useStorage(); const { db } = useStorage();
const { fetchUserData, prefetchEvents } = useNostr(); const { fetchUserData } = useNostr();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -27,16 +27,14 @@ export function ImportStep3Screen() {
// prefetch data // prefetch data
const user = await fetchUserData(); const user = await fetchUserData();
const data = await prefetchEvents();
// create default widget // create default widget
await db.createWidget(WidgetKinds.other.learnNostr, 'Learn Nostr', ''); await db.createWidget(WidgetKinds.other.learnNostr, 'Learn Nostr', '');
// redirect to next step // redirect to next step
if (user.status === 'ok' && data.status === 'ok') { if (user.status === 'ok') {
navigate('/auth/onboarding/step-2', { replace: true }); navigate('/auth/onboarding/step-2', { replace: true });
} else { } else {
console.log('error: ', data.message);
setLoading(false); setLoading(false);
} }
} catch (e) { } catch (e) {
@ -82,8 +80,8 @@ export function ImportStep3Screen() {
)} )}
</button> </button>
<span className="text-center text-sm text-white/50"> <span className="text-center text-sm text-white/50">
By clicking &apos;Continue&apos;, Lume will download your old relay list and By clicking &apos;Continue&apos;, Lume will sync your old relay list and
all events from the last 24 hours. It may take a bit metadata. It may take a bit
</span> </span>
</div> </div>
</div> </div>

View File

@ -16,7 +16,7 @@ export function OnboardStep1Screen() {
const navigate = useNavigate(); const navigate = useNavigate();
const setStep = useOnboarding((state) => state.setStep); const setStep = useOnboarding((state) => state.setStep);
const { publish, fetchUserData, prefetchEvents } = useNostr(); const { publish, fetchUserData } = useNostr();
const { db } = useStorage(); const { db } = useStorage();
const { status, data } = useQuery(['trending-profiles-widget'], async () => { const { status, data } = useQuery(['trending-profiles-widget'], async () => {
const res = await fetch('https://api.nostr.band/v0/trending/profiles'); const res = await fetch('https://api.nostr.band/v0/trending/profiles');
@ -46,14 +46,12 @@ export function OnboardStep1Screen() {
// prefetch data // prefetch data
const user = await fetchUserData(follows); const user = await fetchUserData(follows);
const data = await prefetchEvents();
// redirect to next step // redirect to next step
if (event && user.status === 'ok' && data.status === 'ok') { if (event && user.status === 'ok') {
navigate('/auth/onboarding/step-2', { replace: true }); navigate('/auth/onboarding/step-2', { replace: true });
} else { } else {
setLoading(false); setLoading(false);
console.log('error: ', data.message);
} }
} catch (e) { } catch (e) {
setLoading(false); setLoading(false);
@ -70,7 +68,7 @@ export function OnboardStep1Screen() {
<div className="flex h-full w-full flex-col justify-center"> <div className="flex h-full w-full flex-col justify-center">
<div className="mx-auto mb-4 w-full max-w-md border-b border-white/10 pb-4"> <div className="mx-auto mb-4 w-full max-w-md border-b border-white/10 pb-4">
<h1 className="mb-2 text-center text-2xl font-semibold text-white"> <h1 className="mb-2 text-center text-2xl font-semibold text-white">
{loading ? 'Prefetching data...' : 'Enrich your network'} {loading ? 'Loading...' : 'Enrich your network'}
</h1> </h1>
<p className="text-white/70"> <p className="text-white/70">
Choose the account you want to follow. These accounts are trending in the last Choose the account you want to follow. These accounts are trending in the last
@ -127,19 +125,12 @@ export function OnboardStep1Screen() {
</> </>
)} )}
</button> </button>
{!loading ? ( <Link
<Link to="/auth/onboarding/step-2"
to="/auth/onboarding/step-2" className="inline-flex h-12 w-full items-center justify-center rounded-lg border-t border-white/10 bg-white/20 px-6 font-medium leading-none text-white backdrop-blur-xl hover:bg-white/30 focus:outline-none"
className="inline-flex h-12 w-full items-center justify-center rounded-lg border-t border-white/10 bg-white/20 px-6 font-medium leading-none text-white backdrop-blur-xl hover:bg-white/30 focus:outline-none" >
> Skip, you can add later
Skip, you can add later </Link>
</Link>
) : (
<span className="text-center text-sm text-white/50">
By clicking &apos;Continue&apos;, Lume will download all events related to
your follows from the last 24 hours. It may take a bit
</span>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -65,9 +65,9 @@ export function SpaceScreen() {
case WidgetKinds.nostrBand.trendingNotes: case WidgetKinds.nostrBand.trendingNotes:
return <TrendingNotesWidget key={widget.id} params={widget} />; return <TrendingNotesWidget key={widget.id} params={widget} />;
case WidgetKinds.tmp.xfeed: case WidgetKinds.tmp.xfeed:
return <XhashtagWidget key={widget.id} params={widget} />;
case WidgetKinds.tmp.xhashtag:
return <XfeedsWidget key={widget.id} params={widget} />; return <XfeedsWidget key={widget.id} params={widget} />;
case WidgetKinds.tmp.xhashtag:
return <XhashtagWidget key={widget.id} params={widget} />;
case WidgetKinds.tmp.list: case WidgetKinds.tmp.list:
return <WidgetList key={widget.id} params={widget} />; return <WidgetList key={widget.id} params={widget} />;
case WidgetKinds.other.learnNostr: case WidgetKinds.other.learnNostr:

View File

@ -12,7 +12,7 @@ import { useNostr } from '@utils/hooks/useNostr';
export function SplashScreen() { export function SplashScreen() {
const { db } = useStorage(); const { db } = useStorage();
const { ndk } = useNDK(); const { ndk } = useNDK();
const { fetchUserData, prefetchEvents } = useNostr(); const { fetchUserData } = useNostr();
const [isLoading, setIsLoading] = useState<boolean>(true); const [isLoading, setIsLoading] = useState<boolean>(true);
@ -20,27 +20,8 @@ export function SplashScreen() {
await invoke('close_splashscreen'); await invoke('close_splashscreen');
}; };
const prefetch = async () => {
try {
const [user, events] = await Promise.all([fetchUserData(), prefetchEvents()]);
if (user.status === 'ok' && events.status === 'ok') {
// update last login = current time
await db.updateLastLogin();
// close splash screen and open main app screen
await invoke('close_splashscreen');
}
} catch (e) {
setIsLoading(false);
await message(e, {
title: 'An unexpected error has occurred',
type: 'error',
});
}
};
useEffect(() => { useEffect(() => {
async function initial() { async function syncUserData() {
if (!db.account) { if (!db.account) {
await invoke('close_splashscreen'); await invoke('close_splashscreen');
} else { } else {
@ -50,31 +31,47 @@ export function SplashScreen() {
if (step) { if (step) {
await invoke('close_splashscreen'); await invoke('close_splashscreen');
} else { } else {
console.log('prefetching...'); try {
prefetch(); const userData = await fetchUserData();
if (userData.status === 'ok') {
// update last login = current time
await db.updateLastLogin();
// close splash screen and open main app screen
await invoke('close_splashscreen');
}
} catch (e) {
setIsLoading(false);
await message(e, {
title: 'An unexpected error has occurred',
type: 'error',
});
}
} }
} }
} }
if (ndk) { if (ndk) {
initial(); syncUserData();
} }
}, [ndk, db.account]); }, [ndk, db.account]);
return ( return (
<div className="relative flex h-screen w-screen items-center justify-center bg-black"> <div className="relative flex h-screen w-screen items-center justify-center bg-black">
<div data-tauri-drag-region className="absolute left-0 top-0 z-10 h-11 w-full" /> <div data-tauri-drag-region className="absolute left-0 top-0 z-10 h-11 w-full" />
<div className="flex min-h-0 w-full flex-1 items-center justify-center"> <div className="flex min-h-0 w-full flex-1 items-center justify-center px-8">
<div className="flex flex-col items-center justify-center gap-4"> <div className="flex flex-col items-center justify-center gap-4">
<LoaderIcon className="h-6 w-6 animate-spin text-white" /> <LoaderIcon className="h-6 w-6 animate-spin text-white" />
{isLoading ? ( {isLoading ? (
<div className="flex flex-col gap-1 text-center"> <div className="flex flex-col gap-2 text-center">
<h3 className="text-lg font-semibold leading-none text-white"> <h3 className="text-lg font-semibold leading-none text-white">
{!ndk ? 'Connecting to relay...' : 'Fetching events from the last login.'} {!ndk ? 'Connecting to relay...' : 'Syncing user data...'}
</h3> </h3>
<p className="text-sm text-white/50"> {ndk ? (
This may take a few seconds, please don&apos;t close app. <p className="text-sm text-white/50">
</p> Ensure all your data is sync across all Nostr clients, it may take a few
seconds, please don&apos;t close app.
</p>
) : null}
</div> </div>
) : ( ) : (
<div className="mt-2 flex flex-col gap-1 text-center"> <div className="mt-2 flex flex-col gap-1 text-center">

View File

@ -21,49 +21,42 @@ export const NDKInstance = () => {
); );
// TODO: fully support NIP-11 // TODO: fully support NIP-11
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function getExplicitRelays() { async function getExplicitRelays() {
try { try {
// get relays // get relays
const relays = await db.getExplicitRelayUrls(); const relays = await db.getExplicitRelayUrls();
const requests = relays.map((relay) => { const onlineRelays = new Set(relays);
for (const relay of relays) {
const url = new URL(relay); const url = new URL(relay);
return fetch(`https://${url.hostname + url.pathname}`, { try {
method: 'GET', const res = await fetch(`https://${url.hostname}`, {
timeout: 10, method: 'GET',
headers: { timeout: { secs: 5, nanos: 0 },
Accept: 'application/nostr+json', headers: {
}, Accept: 'application/nostr+json',
}); },
}); });
const responses = await Promise.all(requests); if (!res.ok) {
const successes = responses.filter((res) => res.ok); console.info(`${relay} is not working, skipping...`);
onlineRelays.delete(relay);
const verifiedRelays: string[] = successes.map((res) => { }
const url = new URL(res.url); } catch {
console.warn(`${relay} is not working, skipping...`);
// @ts-expect-error, not have type yet onlineRelays.delete(relay);
if (res.data?.limitation?.payment_required) {
if (url.protocol === 'http:')
return `ws://${url.hostname + url.pathname + db.account.npub}`;
if (url.protocol === 'https:')
return `wss://${url.hostname + url.pathname + db.account.npub}`;
} }
}
if (url.protocol === 'http:') return `ws://${url.hostname + url.pathname}`; // return all online relays
if (url.protocol === 'https:') return `wss://${url.hostname + url.pathname}`; return [...onlineRelays];
});
// return all validated relays
return verifiedRelays;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
} }
async function initNDK() { async function initNDK() {
const explicitRelayUrls = await db.getExplicitRelayUrls(); const explicitRelayUrls = await getExplicitRelays();
const instance = new NDK({ const instance = new NDK({
explicitRelayUrls, explicitRelayUrls,
cacheAdapter, cacheAdapter,

View File

@ -1,5 +1,6 @@
import { NDKEvent, NDKUserProfile } from '@nostr-dev-kit/ndk'; import { NDKEvent, NDKUserProfile } from '@nostr-dev-kit/ndk';
import { BaseDirectory, removeFile } from '@tauri-apps/api/fs'; import { BaseDirectory, removeFile } from '@tauri-apps/api/fs';
import { Platform } from '@tauri-apps/api/os';
import Database from 'tauri-plugin-sql-api'; import Database from 'tauri-plugin-sql-api';
import { Stronghold } from 'tauri-plugin-stronghold-api'; import { Stronghold } from 'tauri-plugin-stronghold-api';
@ -11,12 +12,14 @@ import { Account, DBEvent, Relays, Widget } from '@utils/types';
export class LumeStorage { export class LumeStorage {
public db: Database; public db: Database;
public secureDB: Stronghold; public secureDB: Stronghold;
public account: Account | null = null; public account: Account | null;
public platform: Platform | null;
constructor(sqlite: Database, stronghold?: Stronghold) { constructor(sqlite: Database, platform?: Platform, stronghold?: Stronghold) {
this.db = sqlite; this.db = sqlite;
this.secureDB = stronghold ?? undefined; this.secureDB = stronghold ?? undefined;
this.account = null; this.account = null;
this.platform = platform ?? undefined;
} }
private async getSecureClient(key?: string) { private async getSecureClient(key?: string) {

View File

@ -1,4 +1,5 @@
import { message } from '@tauri-apps/api/dialog'; import { message } from '@tauri-apps/api/dialog';
import { platform } from '@tauri-apps/api/os';
import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react'; import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react';
import Database from 'tauri-plugin-sql-api'; import Database from 'tauri-plugin-sql-api';
@ -18,7 +19,8 @@ const StorageProvider = ({ children }: PropsWithChildren<object>) => {
const initLumeStorage = async () => { const initLumeStorage = async () => {
try { try {
const sqlite = await Database.load('sqlite:lume.db'); const sqlite = await Database.load('sqlite:lume.db');
const lumeStorage = new LumeStorage(sqlite); const platformName = await platform();
const lumeStorage = new LumeStorage(sqlite, platformName);
if (!lumeStorage.account) await lumeStorage.getActiveAccount(); if (!lumeStorage.account) await lumeStorage.getActiveAccount();
setDB(lumeStorage); setDB(lumeStorage);

View File

@ -0,0 +1,61 @@
import { useQueryClient } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import { useStorage } from '@libs/storage/provider';
import { useNostr } from '@utils/hooks/useNostr';
export function EmptyList() {
const { db } = useStorage();
const { getAllEventsSinceLastLogin } = useNostr();
const queryClient = useQueryClient();
const [progress, setProgress] = useState(0);
useEffect(() => {
async function getEvents() {
const events = await getAllEventsSinceLastLogin();
const promises = await Promise.all(
events.data.map(async (event) => await db.createEvent(event))
);
if (promises) {
setProgress(100);
// invalidate queries
queryClient.invalidateQueries(['local-network-widget']);
}
}
// only start download if progress === 0
if (progress === 0) getEvents();
// auto increase progress after 2 secs
setInterval(() => setProgress((prev) => (prev += 8)), 2000);
}, []);
return (
<div className="px-3">
<div className="h-max w-full rounded-lg border-t border-white/10 bg-white/20 p-3">
<div className="flex flex-col items-center gap-5">
<div>
<span className="text-4xl">👋</span>
<h3 className="mt-2 font-semibold leading-tight">
Hello, this is the first time you&apos;re using Lume
</h3>
<p className="text-sm text-white/70">
Lume is downloading all events since the last 24 hours. It will auto refresh
when it done, please be patient
</p>
</div>
<div className="flex h-1.5 w-full overflow-hidden rounded-full bg-white/20">
<div
className="flex flex-col justify-center overflow-hidden bg-fuchsia-500 transition-all duration-1000 ease-smooth"
role="progressbar"
style={{ width: `${progress}%` }}
/>
</div>
</div>
</div>
</div>
);
}

View File

@ -14,3 +14,5 @@ export * from './nostrBand/trendingAccounts';
export * from './tmp/feeds'; export * from './tmp/feeds';
export * from './tmp/hashtag'; export * from './tmp/hashtag';
export * from './other/learnNostr'; export * from './other/learnNostr';
export * from './emptyList';
export * from './loadLatestEvents';

View File

@ -0,0 +1,61 @@
import { useQueryClient } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import { useStorage } from '@libs/storage/provider';
import { useStronghold } from '@stores/stronghold';
import { useNostr } from '@utils/hooks/useNostr';
export function LoadLatestEvents() {
const { db } = useStorage();
const { getAllEventsSinceLastLogin } = useNostr();
const setIsFetched = useStronghold((state) => state.setIsFetched);
const queryClient = useQueryClient();
const [progress, setProgress] = useState(0);
useEffect(() => {
async function getEvents() {
const events = await getAllEventsSinceLastLogin();
const promises = await Promise.all(
events.data.map(async (event) => await db.createEvent(event))
);
if (promises) {
setProgress(100);
setIsFetched();
// invalidate queries
queryClient.invalidateQueries(['local-network-widget']);
}
}
// only start download if progress === 0
if (progress === 0) getEvents();
// auto increase progress after 2 secs
setInterval(() => setProgress((prev) => (prev += 8)), 2000);
}, []);
return (
<div className="mb-3 px-3">
<div className="h-max w-full rounded-lg border-t border-white/10 bg-white/20 p-3">
<div className="flex flex-col items-center gap-3">
<div className="text-center">
<h3 className="font-semibold leading-tight">
Downloading all events from your last login...
</h3>
</div>
<div className="flex h-1.5 w-full overflow-hidden rounded-full bg-white/20">
<div
className="flex flex-col justify-center overflow-hidden bg-fuchsia-500 transition-all duration-1000 ease-smooth"
role="progressbar"
style={{ width: `${progress}%` }}
/>
</div>
</div>
</div>
</div>
);
}

View File

@ -16,7 +16,9 @@ import {
} from '@shared/notes'; } from '@shared/notes';
import { NoteSkeleton } from '@shared/notes/skeleton'; import { NoteSkeleton } from '@shared/notes/skeleton';
import { TitleBar } from '@shared/titleBar'; import { TitleBar } from '@shared/titleBar';
import { WidgetWrapper } from '@shared/widgets'; import { EmptyList, LoadLatestEvents, WidgetWrapper } from '@shared/widgets';
import { useStronghold } from '@stores/stronghold';
import { useNostr } from '@utils/hooks/useNostr'; import { useNostr } from '@utils/hooks/useNostr';
import { toRawEvent } from '@utils/rawEvent'; import { toRawEvent } from '@utils/rawEvent';
@ -34,6 +36,7 @@ export function LocalNetworkWidget() {
getNextPageParam: (lastPage) => lastPage.nextCursor, getNextPageParam: (lastPage) => lastPage.nextCursor,
}); });
const isFetched = useStronghold((state) => state.isFetched);
const dbEvents = useMemo( const dbEvents = useMemo(
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []), () => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
[data] [data]
@ -83,7 +86,7 @@ export function LocalNetworkWidget() {
// subscribe for new event // subscribe for new event
// sub will be managed by lru-cache // sub will be managed by lru-cache
useEffect(() => { useEffect(() => {
if (db.account && db.account.network) { if (db.account && db.account.network && dbEvents.length > 0) {
const filter: NDKFilter = { const filter: NDKFilter = {
kinds: [NDKKind.Text, NDKKind.Repost], kinds: [NDKKind.Text, NDKKind.Repost],
authors: db.account.network, authors: db.account.network,
@ -95,11 +98,11 @@ export function LocalNetworkWidget() {
await db.createEvent(rawEvent); await db.createEvent(rawEvent);
}); });
} }
}, []); }, [data]);
return ( return (
<WidgetWrapper> <WidgetWrapper>
<TitleBar title="👋 Network" /> <TitleBar title="Network" />
<div className="h-full"> <div className="h-full">
{status === 'loading' ? ( {status === 'loading' ? (
<div className="px-3 py-1.5"> <div className="px-3 py-1.5">
@ -108,21 +111,10 @@ export function LocalNetworkWidget() {
</div> </div>
</div> </div>
) : dbEvents.length === 0 ? ( ) : dbEvents.length === 0 ? (
<div className="flex h-full w-full flex-col items-center justify-center px-3"> <EmptyList />
<div className="flex flex-col items-center gap-4">
<img src="/ghost.png" alt="empty feeds" className="h-16 w-16" />
<div className="text-center">
<h3 className="text-xl font-semibold leading-tight">
Your newsfeed is empty
</h3>
<p className="text-center text-white/50">
Connect more people to explore more content
</p>
</div>
</div>
</div>
) : ( ) : (
<VList className="scrollbar-hide h-full"> <VList className="scrollbar-hide h-full">
{!isFetched ? <LoadLatestEvents /> : null}
{dbEvents.map((item) => renderItem(item))} {dbEvents.map((item) => renderItem(item))}
<div className="flex items-center justify-center px-3 py-1.5"> <div className="flex items-center justify-center px-3 py-1.5">
{dbEvents.length > 0 ? ( {dbEvents.length > 0 ? (

View File

@ -4,9 +4,11 @@ import { createJSONStorage, persist } from 'zustand/middleware';
interface StrongholdState { interface StrongholdState {
privkey: null | string; privkey: null | string;
walletConnectURL: null | string; walletConnectURL: null | string;
isFetched: null | boolean;
setPrivkey: (privkey: string) => void; setPrivkey: (privkey: string) => void;
setWalletConnectURL: (uri: string) => void; setWalletConnectURL: (uri: string) => void;
clearPrivkey: () => void; clearPrivkey: () => void;
setIsFetched: () => void;
} }
export const useStronghold = create<StrongholdState>()( export const useStronghold = create<StrongholdState>()(
@ -14,6 +16,7 @@ export const useStronghold = create<StrongholdState>()(
(set) => ({ (set) => ({
privkey: null, privkey: null,
walletConnectURL: null, walletConnectURL: null,
isFetched: false,
setPrivkey: (privkey: string) => { setPrivkey: (privkey: string) => {
set({ privkey: privkey }); set({ privkey: privkey });
}, },
@ -23,6 +26,9 @@ export const useStronghold = create<StrongholdState>()(
clearPrivkey: () => { clearPrivkey: () => {
set({ privkey: null }); set({ privkey: null });
}, },
setIsFetched: () => {
set({ isFetched: true });
},
}), }),
{ {
name: 'stronghold', name: 'stronghold',

View File

@ -54,7 +54,7 @@ export function useNostr() {
}); });
subManager.set(JSON.stringify(filter), subEvent); subManager.set(JSON.stringify(filter), subEvent);
console.log(subManager.keys()); console.log('current active sub: ', subManager.size);
}; };
const fetchUserData = async (preFollows?: string[]) => { const fetchUserData = async (preFollows?: string[]) => {
@ -144,41 +144,6 @@ export function useNostr() {
publish({ content: '', kind: NDKKind.Contacts, tags: tags }); publish({ content: '', kind: NDKKind.Contacts, tags: tags });
}; };
const prefetchEvents = async () => {
try {
const dbEventsEmpty = await db.isEventsEmpty();
let since: number;
if (dbEventsEmpty || db.account.last_login_at === 0) {
since = db.account.network.length > 400 ? nHoursAgo(12) : nHoursAgo(24);
} else {
since = db.account.last_login_at;
}
console.log("prefetching events with user's network: ", db.account.network.length);
console.log('prefetching events since: ', since);
const events = (await fetcher.fetchAllEvents(
relayUrls,
{
kinds: [NDKKind.Text, NDKKind.Repost, 1063, NDKKind.Article],
authors: db.account.network,
},
{ since: since }
)) as unknown as NDKEvent[];
// save all events to database
const promises = await Promise.all(
events.map(async (event) => await db.createEvent(event))
);
if (promises) return { status: 'ok', message: 'prefetch completed' };
} catch (e) {
console.error('prefetch events failed, error: ', e);
return { status: 'failed', message: e };
}
};
const fetchActivities = async () => { const fetchActivities = async () => {
try { try {
const events = await fetcher.fetchAllEvents( const events = await fetcher.fetchAllEvents(
@ -293,6 +258,37 @@ export function useNostr() {
return events; return events;
}; };
const getAllEventsSinceLastLogin = async (customSince?: number) => {
try {
let since: number;
const dbEventsEmpty = await db.isEventsEmpty();
if (!customSince) {
if (dbEventsEmpty || db.account.last_login_at === 0) {
since = db.account.network.length > 400 ? nHoursAgo(12) : nHoursAgo(24);
} else {
since = db.account.last_login_at;
}
} else {
since = customSince;
}
const events = (await fetcher.fetchAllEvents(
relayUrls,
{
kinds: [NDKKind.Text, NDKKind.Repost, 1063, NDKKind.Article],
authors: db.account.network,
},
{ since: since }
)) as unknown as NDKEvent[];
return { status: 'ok', message: 'fetch completed', data: events };
} catch (e) {
console.error('prefetch events failed, error: ', e);
return { status: 'failed', message: e };
}
};
const getContactsByPubkey = async (pubkey: string) => { const getContactsByPubkey = async (pubkey: string) => {
const user = ndk.getUser({ hexpubkey: pubkey }); const user = ndk.getUser({ hexpubkey: pubkey });
const follows = [...(await user.follows())].map((user) => user.hexpubkey); const follows = [...(await user.follows())].map((user) => user.hexpubkey);
@ -444,7 +440,7 @@ export function useNostr() {
fetchUserData, fetchUserData,
addContact, addContact,
removeContact, removeContact,
prefetchEvents, getAllEventsSinceLastLogin,
fetchActivities, fetchActivities,
fetchNIP04Chats, fetchNIP04Chats,
fetchNIP04Messages, fetchNIP04Messages,