From 6e28bcdb962410052782bd1727ad9fb2e3e805e4 Mon Sep 17 00:00:00 2001 From: Ren Amamiya <123083837+reyamir@users.noreply.github.com> Date: Tue, 15 Aug 2023 08:29:04 +0700 Subject: [PATCH] wip: use new storage layer --- src/app/auth/create/step-1.tsx | 10 ++++--- src/app/auth/create/step-2.tsx | 15 +++++++--- src/app/auth/create/step-3.tsx | 4 +-- src/app/auth/import/step-1.tsx | 10 ++++--- src/app/auth/import/step-2.tsx | 15 +++++++--- src/app/auth/import/step-3.tsx | 21 ++++++++------ src/app/auth/migrate.tsx | 15 +++++++--- src/app/auth/onboarding/step-1.tsx | 25 +++++++++------- src/app/auth/onboarding/step-2.tsx | 8 ++++-- src/app/auth/onboarding/step-3.tsx | 13 ++++----- src/app/auth/reset.tsx | 20 ++++++++++--- src/app/auth/unlock.tsx | 2 +- src/app/splash.tsx | 5 ++-- src/libs/ndk/cache.ts | 41 ++++++++++++-------------- src/libs/ndk/instance.ts | 13 ++++----- src/libs/storage/instance.ts | 46 ++++++++++++++++++++++++++++-- src/libs/storage/provider.tsx | 12 ++------ src/utils/hooks/useAccount.tsx | 2 +- src/utils/hooks/useNostr.tsx | 9 +++--- 19 files changed, 182 insertions(+), 104 deletions(-) diff --git a/src/app/auth/create/step-1.tsx b/src/app/auth/create/step-1.tsx index 78578d6f..a75a52e4 100644 --- a/src/app/auth/create/step-1.tsx +++ b/src/app/auth/create/step-1.tsx @@ -4,7 +4,7 @@ import { generatePrivateKey, getPublicKey, nip19 } from 'nostr-tools'; import { useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { createAccount } from '@libs/storage'; +import { useStorage } from '@libs/storage/provider'; import { Button } from '@shared/button'; import { EyeOffIcon, EyeOnIcon, LoaderIcon } from '@shared/icons'; @@ -14,6 +14,8 @@ import { useOnboarding } from '@stores/onboarding'; import { useStronghold } from '@stores/stronghold'; export function CreateStep1Screen() { + const { db } = useStorage(); + const queryClient = useQueryClient(); const navigate = useNavigate(); const setPrivkey = useStronghold((state) => state.setPrivkey); @@ -53,10 +55,10 @@ export function CreateStep1Screen() { follows: null | string[][]; is_active: number; }) => { - return createAccount(data.npub, data.pubkey, null, 1); + return db.createAccount(data.npub, data.pubkey); }, onSuccess: (data) => { - queryClient.setQueryData(['currentAccount'], data); + queryClient.setQueryData(['account'], data); }, }); @@ -75,7 +77,7 @@ export function CreateStep1Screen() { }); // redirect to next step - setTimeout(() => navigate('/auth/create/step-2', { replace: true }), 1200); + navigate('/auth/create/step-2', { replace: true }); }; useEffect(() => { diff --git a/src/app/auth/create/step-2.tsx b/src/app/auth/create/step-2.tsx index 9a7dde4a..70419881 100644 --- a/src/app/auth/create/step-2.tsx +++ b/src/app/auth/create/step-2.tsx @@ -1,15 +1,17 @@ +import { appConfigDir } from '@tauri-apps/api/path'; +import { Stronghold } from '@tauri-apps/plugin-stronghold'; import { useEffect, useState } from 'react'; import { Resolver, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; +import { useStorage } from '@libs/storage/provider'; + import { EyeOffIcon, EyeOnIcon, LoaderIcon } from '@shared/icons'; import { ArrowRightCircleIcon } from '@shared/icons/arrowRightCircle'; import { useOnboarding } from '@stores/onboarding'; import { useStronghold } from '@stores/stronghold'; -import { useSecureStorage } from '@utils/hooks/useSecureStorage'; - type FormValues = { password: string; }; @@ -37,7 +39,7 @@ export function CreateStep2Screen() { const [passwordInput, setPasswordInput] = useState('password'); const [loading, setLoading] = useState(false); - const { save } = useSecureStorage(); + const { db } = useStorage(); // toggle private key const showPassword = () => { @@ -58,8 +60,13 @@ export function CreateStep2Screen() { const onSubmit = async (data: { [x: string]: string }) => { setLoading(true); if (data.password.length > 3) { + const dir = await appConfigDir(); + const stronghold = await Stronghold.load(`${dir}/lume.stronghold`, data.password); + + db.secureDB = stronghold; + // save privkey to secure storage - await save(pubkey, privkey, data.password); + await db.secureSave(pubkey, privkey); // redirect to next step navigate('/auth/create/step-3', { replace: true }); diff --git a/src/app/auth/create/step-3.tsx b/src/app/auth/create/step-3.tsx index 64c91921..dc85bfcd 100644 --- a/src/app/auth/create/step-3.tsx +++ b/src/app/auth/create/step-3.tsx @@ -47,10 +47,10 @@ export function CreateStep3Screen() { tags: [], }); - queryClient.invalidateQueries(['currentAccount']); + queryClient.invalidateQueries(['account']); if (event) { - setTimeout(() => navigate('/auth/onboarding', { replace: true }), 1000); + navigate('/auth/onboarding', { replace: true }); } } catch (e) { console.log('error: ', e); diff --git a/src/app/auth/import/step-1.tsx b/src/app/auth/import/step-1.tsx index 33565e4e..94282b1c 100644 --- a/src/app/auth/import/step-1.tsx +++ b/src/app/auth/import/step-1.tsx @@ -4,7 +4,7 @@ import { useEffect, useState } from 'react'; import { Resolver, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; -import { createAccount } from '@libs/storage'; +import { useStorage } from '@libs/storage/provider'; import { LoaderIcon } from '@shared/icons'; import { ArrowRightCircleIcon } from '@shared/icons/arrowRightCircle'; @@ -31,6 +31,8 @@ const resolver: Resolver = async (values) => { }; export function ImportStep1Screen() { + const { db } = useStorage(); + const queryClient = useQueryClient(); const navigate = useNavigate(); const setPrivkey = useStronghold((state) => state.setPrivkey); @@ -47,10 +49,10 @@ export function ImportStep1Screen() { follows: null | string[]; is_active: number | boolean; }) => { - return createAccount(data.npub, data.pubkey, null, 1); + return db.createAccount(data.npub, data.pubkey); }, onSuccess: (data) => { - queryClient.setQueryData(['currentAccount'], data); + queryClient.setQueryData(['account'], data); }, }); @@ -87,7 +89,7 @@ export function ImportStep1Screen() { }); // redirect to step 2 - setTimeout(() => navigate('/auth/import/step-2', { replace: true }), 1200); + navigate('/auth/import/step-2', { replace: true }); } } catch (error) { setError('privkey', { diff --git a/src/app/auth/import/step-2.tsx b/src/app/auth/import/step-2.tsx index ee00aa0b..afe372ff 100644 --- a/src/app/auth/import/step-2.tsx +++ b/src/app/auth/import/step-2.tsx @@ -1,15 +1,17 @@ +import { appConfigDir } from '@tauri-apps/api/path'; +import { Stronghold } from '@tauri-apps/plugin-stronghold'; import { useEffect, useState } from 'react'; import { Resolver, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; +import { useStorage } from '@libs/storage/provider'; + import { EyeOffIcon, EyeOnIcon, LoaderIcon } from '@shared/icons'; import { ArrowRightCircleIcon } from '@shared/icons/arrowRightCircle'; import { useOnboarding } from '@stores/onboarding'; import { useStronghold } from '@stores/stronghold'; -import { useSecureStorage } from '@utils/hooks/useSecureStorage'; - type FormValues = { password: string; }; @@ -37,7 +39,7 @@ export function ImportStep2Screen() { const [passwordInput, setPasswordInput] = useState('password'); const [loading, setLoading] = useState(false); - const { save } = useSecureStorage(); + const { db } = useStorage(); // toggle private key const showPassword = () => { @@ -58,8 +60,13 @@ export function ImportStep2Screen() { const onSubmit = async (data: { [x: string]: string }) => { setLoading(true); if (data.password.length > 3) { + const dir = await appConfigDir(); + const stronghold = await Stronghold.load(`${dir}/lume.stronghold`, data.password); + + db.secureDB = stronghold; + // save privkey to secure storage - await save(pubkey, privkey, data.password); + await db.secureSave(pubkey, privkey); // redirect to next step navigate('/auth/import/step-3', { replace: true }); diff --git a/src/app/auth/import/step-3.tsx b/src/app/auth/import/step-3.tsx index 93513a58..a5d2f793 100644 --- a/src/app/auth/import/step-3.tsx +++ b/src/app/auth/import/step-3.tsx @@ -4,7 +4,7 @@ import { useNavigate } from 'react-router-dom'; import { User } from '@app/auth/components/user'; -import { updateLastLogin } from '@libs/storage'; +import { useStorage } from '@libs/storage/provider'; import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons'; @@ -20,22 +20,27 @@ export function ImportStep3Screen() { const [loading, setLoading] = useState(false); + const { db } = useStorage(); const { status, account } = useAccount(); - const { fetchNotes, fetchChats } = useNostr(); + const { fetchUserData } = useNostr(); const submit = async () => { try { // show loading indicator setLoading(true); - const now = Math.floor(Date.now() / 1000); - await fetchNotes(); - await fetchChats(); - await updateLastLogin(now); + const data = await fetchUserData(); - queryClient.invalidateQueries(['currentAccount']); + if (data.status === 'ok') { + // update last login + await db.updateLastLogin(Math.floor(Date.now() / 1000)); - navigate('/auth/onboarding/step-2', { replace: true }); + queryClient.invalidateQueries(['account']); + navigate('/auth/onboarding/step-2', { replace: true }); + } else { + console.log('error: ', data.message); + setLoading(false); + } } catch (e) { console.log('error: ', e); setLoading(false); diff --git a/src/app/auth/migrate.tsx b/src/app/auth/migrate.tsx index a2928b1a..89eab1e6 100644 --- a/src/app/auth/migrate.tsx +++ b/src/app/auth/migrate.tsx @@ -1,16 +1,18 @@ import { useQueryClient } from '@tanstack/react-query'; +import { appConfigDir } from '@tauri-apps/api/path'; +import { Stronghold } from '@tauri-apps/plugin-stronghold'; import { useState } from 'react'; import { Resolver, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; import { removePrivkey } from '@libs/storage'; +import { useStorage } from '@libs/storage/provider'; import { EyeOffIcon, EyeOnIcon, LoaderIcon } from '@shared/icons'; import { useStronghold } from '@stores/stronghold'; import { useAccount } from '@utils/hooks/useAccount'; -import { useSecureStorage } from '@utils/hooks/useSecureStorage'; type FormValues = { password: string; @@ -39,7 +41,7 @@ export function MigrateScreen() { const [loading, setLoading] = useState(false); const { account } = useAccount(); - const { save } = useSecureStorage(); + const { db } = useStorage(); // toggle private key const showPassword = () => { @@ -63,13 +65,18 @@ export function MigrateScreen() { // load private in secure storage try { // save privkey to secure storage - await save(account.pubkey, account.privkey, data.password); + const dir = await appConfigDir(); + const stronghold = await Stronghold.load(`${dir}/lume.stronghold`, data.password); + + if (!db.secureDB) db.secureDB = stronghold; + await db.secureSave(account.pubkey, account.privkey); + // add privkey to state setPrivkey(account.privkey); // remove privkey in db await removePrivkey(); // clear cache - await queryClient.invalidateQueries(['currentAccount']); + await queryClient.invalidateQueries(['account']); // redirect to home navigate('/', { replace: true }); } catch { diff --git a/src/app/auth/onboarding/step-1.tsx b/src/app/auth/onboarding/step-1.tsx index 79e64a00..c591b88c 100644 --- a/src/app/auth/onboarding/step-1.tsx +++ b/src/app/auth/onboarding/step-1.tsx @@ -4,7 +4,7 @@ import { Link, useNavigate } from 'react-router-dom'; import { User } from '@app/auth/components/user'; -import { updateAccount } from '@libs/storage'; +import { useStorage } from '@libs/storage/provider'; import { ArrowRightCircleIcon, CheckCircleIcon, LoaderIcon } from '@shared/icons'; @@ -19,7 +19,8 @@ export function OnboardStep1Screen() { const navigate = useNavigate(); const setStep = useOnboarding((state) => state.setStep); - const { publish, fetchNotes } = useNostr(); + const { db } = useStorage(); + const { publish, fetchUserData } = useNostr(); const { account } = useAccount(); const { status, data } = useQuery(['trending-profiles'], async () => { const res = await fetch('https://api.nostr.band/v0/trending/profiles'); @@ -46,20 +47,22 @@ export function OnboardStep1Screen() { const tags = arrayToNIP02([...follows, account.pubkey]); const event = await publish({ content: '', kind: 3, tags: tags }); - await updateAccount('follows', follows); + await db.updateAccount('follows', follows); // prefetch notes with current follows - const notes = await fetchNotes(follows); + const data = await fetchUserData(follows); // redirect to next step - if (event && notes) { - setTimeout(() => { - queryClient.invalidateQueries(['currentAccount']); - navigate('/auth/onboarding/step-2', { replace: true }); - }, 1000); + if (event && data.status === 'ok') { + queryClient.invalidateQueries(['account']); + navigate('/auth/onboarding/step-2', { replace: true }); + } else { + setLoading(false); + console.log('error: ', data.message); } - } catch { - console.log('error'); + } catch (e) { + setLoading(false); + console.log('error: ', e); } }; diff --git a/src/app/auth/onboarding/step-2.tsx b/src/app/auth/onboarding/step-2.tsx index 6ef68d62..5f9ebd51 100644 --- a/src/app/auth/onboarding/step-2.tsx +++ b/src/app/auth/onboarding/step-2.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; -import { createWidget } from '@libs/storage'; +import { useStorage } from '@libs/storage/provider'; import { ArrowRightCircleIcon, CheckCircleIcon, LoaderIcon } from '@shared/icons'; @@ -33,6 +33,8 @@ export function OnboardStep2Screen() { const [loading, setLoading] = useState(false); const [tags, setTags] = useState(new Set()); + const { db } = useStorage(); + const toggleTag = (tag: string) => { if (tags.has(tag)) { setTags((prev) => { @@ -50,10 +52,10 @@ export function OnboardStep2Screen() { setLoading(true); for (const tag of tags) { - await createWidget(BLOCK_KINDS.hashtag, tag, tag.replace('#', '')); + await db.createWidget(BLOCK_KINDS.hashtag, tag, tag.replace('#', '')); } - setTimeout(() => navigate('/auth/onboarding/step-3', { replace: true }), 1000); + navigate('/auth/onboarding/step-3', { replace: true }); } catch { console.log('error'); } diff --git a/src/app/auth/onboarding/step-3.tsx b/src/app/auth/onboarding/step-3.tsx index dea51bcc..a9dc537b 100644 --- a/src/app/auth/onboarding/step-3.tsx +++ b/src/app/auth/onboarding/step-3.tsx @@ -5,7 +5,7 @@ import { useNavigate } from 'react-router-dom'; import { UserRelay } from '@app/auth/components/userRelay'; import { useNDK } from '@libs/ndk/provider'; -import { createRelay } from '@libs/storage'; +import { useStorage } from '@libs/storage/provider'; import { ArrowRightCircleIcon, CheckCircleIcon, LoaderIcon } from '@shared/icons'; @@ -22,6 +22,7 @@ export function OnboardStep3Screen() { const [loading, setLoading] = useState(false); const [relays, setRelays] = useState(new Set()); + const { db } = useStorage(); const { publish } = useNostr(); const { account } = useAccount(); const { ndk } = useNDK(); @@ -62,21 +63,19 @@ export function OnboardStep3Screen() { try { if (!skip) { for (const relay of relays) { - await createRelay(relay); + await db.createRelay(relay); } const tags = Array.from(relays).map((relay) => ['r', relay.replace(/\/+$/, '')]); await publish({ content: '', kind: 10002, tags: tags }); } else { for (const relay of FULL_RELAYS) { - await createRelay(relay); + await db.createRelay(relay); } } - setTimeout(() => { - clearStep(); - navigate('/', { replace: true }); - }, 1000); + clearStep(); + navigate('/', { replace: true }); } catch (e) { setLoading(false); console.log('error: ', e); diff --git a/src/app/auth/reset.tsx b/src/app/auth/reset.tsx index 36691c13..61db6331 100644 --- a/src/app/auth/reset.tsx +++ b/src/app/auth/reset.tsx @@ -1,14 +1,17 @@ +import { appConfigDir } from '@tauri-apps/api/path'; +import { Stronghold } from '@tauri-apps/plugin-stronghold'; import { getPublicKey, nip19 } from 'nostr-tools'; import { useState } from 'react'; import { Resolver, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; +import { useStorage } from '@libs/storage/provider'; + import { EyeOffIcon, EyeOnIcon, LoaderIcon } from '@shared/icons'; import { useStronghold } from '@stores/stronghold'; import { useAccount } from '@utils/hooks/useAccount'; -import { useSecureStorage } from '@utils/hooks/useSecureStorage'; type FormValues = { password: string; @@ -37,7 +40,7 @@ export function ResetScreen() { const [loading, setLoading] = useState(false); const { account } = useAccount(); - const { save, reset } = useSecureStorage(); + const { db } = useStorage(); // toggle private key const showPassword = () => { @@ -75,9 +78,18 @@ export function ResetScreen() { }); } else { // remove old stronghold - await reset(); + await db.secureReset(); + // save privkey to secure storage - await save(account.pubkey, account.privkey, data.password); + const dir = await appConfigDir(); + const stronghold = await Stronghold.load( + `${dir}/lume.stronghold`, + data.password + ); + + if (!db.secureDB) db.secureDB = stronghold; + await db.secureSave(account.pubkey, account.privkey); + // add privkey to state setPrivkey(account.privkey); // redirect to home diff --git a/src/app/auth/unlock.tsx b/src/app/auth/unlock.tsx index 471744e9..7025cb36 100644 --- a/src/app/auth/unlock.tsx +++ b/src/app/auth/unlock.tsx @@ -54,7 +54,7 @@ export function UnlockScreen() { const dir = await appConfigDir(); const stronghold = await Stronghold.load(`${dir}/lume.stronghold`, data.password); - db.secureDB = stronghold; + if (!db.secureDB) db.secureDB = stronghold; const privkey = await db.secureLoad(account.pubkey); diff --git a/src/app/splash.tsx b/src/app/splash.tsx index f0579c0b..47911768 100644 --- a/src/app/splash.tsx +++ b/src/app/splash.tsx @@ -2,7 +2,7 @@ import { invoke } from '@tauri-apps/api/tauri'; import { useEffect, useState } from 'react'; import { useNDK } from '@libs/ndk/provider'; -import { updateLastLogin } from '@libs/storage'; +import { useStorage } from '@libs/storage/provider'; import { LoaderIcon } from '@shared/icons'; @@ -10,6 +10,7 @@ import { useAccount } from '@utils/hooks/useAccount'; import { useNostr } from '@utils/hooks/useNostr'; export function SplashScreen() { + const { db } = useStorage(); const { ndk, relayUrls } = useNDK(); const { status, account } = useAccount(); const { fetchUserData } = useNostr(); @@ -30,7 +31,7 @@ export function SplashScreen() { const user = await fetchUserData(); if (user.status === 'ok') { const now = Math.floor(Date.now() / 1000); - await updateLastLogin(now); + await db.updateLastLogin(now); invoke('close_splashscreen'); } else { setIsLoading(false); diff --git a/src/libs/ndk/cache.ts b/src/libs/ndk/cache.ts index fed7815a..b011bd22 100644 --- a/src/libs/ndk/cache.ts +++ b/src/libs/ndk/cache.ts @@ -1,13 +1,14 @@ import { NDKCacheAdapter } from '@nostr-dev-kit/ndk'; import { NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk'; -import { Store } from '@tauri-apps/plugin-store'; + +import { LumeStorage } from '@libs/storage/instance'; export default class TauriAdapter implements NDKCacheAdapter { - public store: Store; + public store: LumeStorage; readonly locking: boolean; - constructor() { - this.store = new Store('.ndkcache.dat'); + constructor(db: LumeStorage) { + this.store = db; this.locking = true; } @@ -20,7 +21,7 @@ export default class TauriAdapter implements NDKCacheAdapter { for (const author of filter.authors) { for (const kind of filter.kinds) { const key = `${author}:${kind}`; - promises.push(this.store.get(key)); + promises.push(this.store.getEventByKey(key)); } } @@ -28,25 +29,26 @@ export default class TauriAdapter implements NDKCacheAdapter { for (const result of results) { if (result) { - const event = await this.store.get(result as string); - - if (event) { - console.log('cache hit: ', result); - const ndkEvent = new NDKEvent(subscription.ndk, JSON.parse(event as string)); - subscription.eventReceived(ndkEvent, undefined, true); - } + console.log('cache hit: ', result); + const ndkEvent = new NDKEvent( + subscription.ndk, + JSON.parse(result.event as string) + ); + subscription.eventReceived(ndkEvent, undefined, true); } } } if (filter.ids) { for (const id of filter.ids) { - const key = id; - const event = await this.store.get(key); + const cacheEvent = await this.store.getEventByID(id); - if (event) { + if (cacheEvent) { console.log('cache hit: ', id); - const ndkEvent = new NDKEvent(subscription.ndk, JSON.parse(event as string)); + const ndkEvent = new NDKEvent( + subscription.ndk, + JSON.parse(cacheEvent.event as string) + ); subscription.eventReceived(ndkEvent, undefined, true); } } @@ -59,13 +61,8 @@ export default class TauriAdapter implements NDKCacheAdapter { return new Promise((resolve) => { Promise.all([ - this.store.set(event.id, JSON.stringify(nostrEvent)), - this.store.set(key, event.id), + this.store.createEvent(key, event.id, event.kind, JSON.stringify(nostrEvent)), ]).then(() => resolve()); }); } - - public save() { - return this.store.save(); - } } diff --git a/src/libs/ndk/instance.ts b/src/libs/ndk/instance.ts index 7b5a334b..6bfaeba8 100644 --- a/src/libs/ndk/instance.ts +++ b/src/libs/ndk/instance.ts @@ -1,19 +1,20 @@ // inspire by: https://github.com/nostr-dev-kit/ndk-react/ import NDK from '@nostr-dev-kit/ndk'; import { fetch } from '@tauri-apps/plugin-http'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useState } from 'react'; import TauriAdapter from '@libs/ndk/cache'; import { getExplicitRelayUrls } from '@libs/storage'; +import { useStorage } from '@libs/storage/provider'; import { FULL_RELAYS } from '@stores/constants'; export const NDKInstance = () => { + const { db } = useStorage(); + const [ndk, setNDK] = useState(undefined); const [relayUrls, setRelayUrls] = useState([]); - const cacheAdapter = useMemo(() => new TauriAdapter(), []); - // TODO: fully support NIP-11 async function verifyRelays(relays: string[]) { const verifiedRelays: string[] = []; @@ -57,6 +58,8 @@ export const NDKInstance = () => { explicitRelayUrls = await verifyRelays(FULL_RELAYS); } + const cacheAdapter = new TauriAdapter(db); + console.log('ndk cache adapter: ', cacheAdapter); const instance = new NDK({ explicitRelayUrls, cacheAdapter }); try { @@ -71,10 +74,6 @@ export const NDKInstance = () => { useEffect(() => { if (!ndk) initNDK(); - - return () => { - cacheAdapter.save(); - }; }, []); return { diff --git a/src/libs/storage/instance.ts b/src/libs/storage/instance.ts index 15100eeb..74c3d3e3 100644 --- a/src/libs/storage/instance.ts +++ b/src/libs/storage/instance.ts @@ -1,7 +1,8 @@ +import { BaseDirectory, removeFile } from '@tauri-apps/plugin-fs'; import Database from '@tauri-apps/plugin-sql'; import { Stronghold } from '@tauri-apps/plugin-stronghold'; -import { Account, Widget } from '@utils/types'; +import { Account, Relays, Widget } from '@utils/types'; export class LumeStorage { public db: Database; @@ -39,6 +40,10 @@ export class LumeStorage { return decoded; } + public async secureReset() { + return await removeFile('lume.stronghold', { dir: BaseDirectory.AppConfig }); + } + public async getActiveAccount() { const account: Account = await this.db.select( 'SELECT * FROM accounts WHERE is_active = 1;' @@ -123,7 +128,10 @@ export class LumeStorage { 'SELECT * FROM events WHERE cache_key = $1 ORDER BY id DESC LIMIT 1;', [cacheKey] )?.[0]; - if (!event) console.error('failed to get event by cache_key: ', cacheKey); + if (!event) { + console.error('failed to get event by cache_key: ', cacheKey); + return null; + } return event; } @@ -132,10 +140,42 @@ export class LumeStorage { 'SELECT * FROM events WHERE event_id = $1 ORDER BY id DESC LIMIT 1;', [id] )?.[0]; - if (!event) console.error('failed to get event by id: ', id); + if (!event) { + console.error('failed to get event by id: ', id); + return null; + } return event; } + public async getExplicitRelayUrls() { + const account = await this.getActiveAccount(); + const result: Relays[] = await this.db.select( + `SELECT * FROM relays WHERE account_id = "${account.id}";` + ); + + if (result.length > 0) return result.map((el) => el.relay); + return null; + } + + public async createRelay(relay: string, purpose?: string) { + const account = await this.getActiveAccount(); + return await this.db.execute( + 'INSERT OR IGNORE INTO relays (account_id, relay, purpose) VALUES ($1, $2, $3);', + [account.id, relay, purpose || ''] + ); + } + + public async removeRelay(relay: string) { + return await this.db.execute(`DELETE FROM relays WHERE relay = "${relay}";`); + } + + public async updateLastLogin(time: number) { + return await this.db.execute( + 'UPDATE settings SET value = $1 WHERE key = "last_login";', + [time] + ); + } + public async close() { return this.db.close(); } diff --git a/src/libs/storage/provider.tsx b/src/libs/storage/provider.tsx index 4adc2571..8489124c 100644 --- a/src/libs/storage/provider.tsx +++ b/src/libs/storage/provider.tsx @@ -28,15 +28,9 @@ const StorageProvider = ({ children }: PropsWithChildren) => { }; }, []); - return ( - - {children} - - ); + if (db) { + return {children}; + } }; const useStorage = () => { diff --git a/src/utils/hooks/useAccount.tsx b/src/utils/hooks/useAccount.tsx index eb25eefe..ba3db3db 100644 --- a/src/utils/hooks/useAccount.tsx +++ b/src/utils/hooks/useAccount.tsx @@ -6,7 +6,7 @@ import { getActiveAccount } from '@libs/storage'; export function useAccount() { const { ndk } = useNDK(); const { status, data: account } = useQuery( - ['currentAccount'], + ['account'], async () => { const account = await getActiveAccount(); console.log('account: ', account); diff --git a/src/utils/hooks/useNostr.tsx b/src/utils/hooks/useNostr.tsx index d2b06f1a..cd95104e 100644 --- a/src/utils/hooks/useNostr.tsx +++ b/src/utils/hooks/useNostr.tsx @@ -15,7 +15,7 @@ import { nip19 } from 'nostr-tools'; import { useMemo } from 'react'; import { useNDK } from '@libs/ndk/provider'; -import { updateAccount } from '@libs/storage'; +import { useStorage } from '@libs/storage/provider'; import { useStronghold } from '@stores/stronghold'; @@ -24,6 +24,7 @@ import { useAccount } from '@utils/hooks/useAccount'; export function useNostr() { const { ndk, relayUrls } = useNDK(); const { account } = useAccount(); + const { db } = useStorage(); const queryClient = useQueryClient(); const privkey = useStronghold((state) => state.privkey); @@ -74,10 +75,10 @@ export function useNostr() { const network = [...lruNetwork.values()] as string[]; - await updateAccount('follows', [...follows]); - await updateAccount('network', [...new Set([...follows, ...network])]); + await db.updateAccount('follows', [...follows]); + await db.updateAccount('network', [...new Set([...follows, ...network])]); - queryClient.invalidateQueries(['currentAccount']); + queryClient.invalidateQueries(['account']); return { status: 'ok' }; } catch (e) {