From e6d35bc63519f89c9fca7af986cb00441fe2b2cd Mon Sep 17 00:00:00 2001 From: Ren Amamiya <123083837+reyamir@users.noreply.github.com> Date: Fri, 1 Sep 2023 15:57:31 +0700 Subject: [PATCH] fix mention in composer and improve error handling --- src/app/splash.tsx | 6 +- src/libs/ndk/instance.ts | 21 ++++- src/libs/storage/provider.tsx | 16 +++- src/shared/composer/mention/list.tsx | 131 ++++++++++++++------------- src/shared/nip05.tsx | 8 +- src/shared/notes/kinds/repost.tsx | 34 ++++--- src/shared/notes/kinds/unknown.tsx | 2 +- src/shared/user.tsx | 6 +- src/utils/hooks/useEvent.ts | 54 ++++++----- src/utils/parser.ts | 2 - 10 files changed, 156 insertions(+), 124 deletions(-) diff --git a/src/app/splash.tsx b/src/app/splash.tsx index 66a2c38d..046f2e40 100644 --- a/src/app/splash.tsx +++ b/src/app/splash.tsx @@ -1,4 +1,5 @@ import { invoke } from '@tauri-apps/api/tauri'; +import { message } from '@tauri-apps/plugin-dialog'; import { useEffect, useState } from 'react'; import { useNDK } from '@libs/ndk/provider'; @@ -40,7 +41,10 @@ export function SplashScreen() { } catch (e) { setIsLoading(false); setErrorMessage(e); - console.log('prefetch failed, error: ', e); + await message(`Something wrong: ${e}`, { + title: 'Lume', + type: 'error', + }); } }; diff --git a/src/libs/ndk/instance.ts b/src/libs/ndk/instance.ts index 16cf5d09..da97b430 100644 --- a/src/libs/ndk/instance.ts +++ b/src/libs/ndk/instance.ts @@ -1,5 +1,6 @@ // inspire by: https://github.com/nostr-dev-kit/ndk-react/ import NDK from '@nostr-dev-kit/ndk'; +import { message } from '@tauri-apps/plugin-dialog'; import { useEffect, useMemo, useState } from 'react'; import TauriAdapter from '@libs/ndk/cache'; @@ -28,7 +29,7 @@ export const NDKInstance = () => { }); const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort('timeout'), 5000); + const timeoutId = setTimeout(() => controller.abort('timeout'), 10000); const requests = urls.map((url) => fetch(url, { @@ -58,14 +59,16 @@ export const NDKInstance = () => { // return all validate relays return verifiedRelays; } catch (e) { - console.error('ndk instance error: ', e); + console.error('verify relay failed with error: ', e); } } async function initNDK() { let explicitRelayUrls: string[]; const explicitRelayUrlsFromDB = await db.getExplicitRelayUrls(); + console.log('relays in db: ', explicitRelayUrlsFromDB); + console.log('ndk cache adapter: ', cacheAdapter); if (explicitRelayUrlsFromDB) { explicitRelayUrls = await verifyRelays(explicitRelayUrlsFromDB); @@ -73,13 +76,21 @@ export const NDKInstance = () => { explicitRelayUrls = await verifyRelays(FULL_RELAYS); } - console.log('ndk cache adapter: ', cacheAdapter); - const instance = new NDK({ explicitRelayUrls, cacheAdapter }); + if (explicitRelayUrls.length < 1) { + await message('Something is wrong. No relays have been found.', { + title: 'Lume', + type: 'error', + }); + } + const instance = new NDK({ explicitRelayUrls, cacheAdapter }); try { await instance.connect(10000); } catch (error) { - throw new Error('NDK instance init failed: ', error); + await message(`NDK instance init failed: ${error}`, { + title: 'Lume', + type: 'error', + }); } setNDK(instance); diff --git a/src/libs/storage/provider.tsx b/src/libs/storage/provider.tsx index aeb712ae..4e326e0b 100644 --- a/src/libs/storage/provider.tsx +++ b/src/libs/storage/provider.tsx @@ -1,3 +1,4 @@ +import { message } from '@tauri-apps/plugin-dialog'; import Database from '@tauri-apps/plugin-sql'; import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react'; @@ -15,11 +16,18 @@ const StorageProvider = ({ children }: PropsWithChildren) => { const [db, setDB] = useState(undefined); async function initLumeStorage() { - const sqlite = await Database.load('sqlite:lume.db'); - const lumeStorage = new LumeStorage(sqlite); + try { + const sqlite = await Database.load('sqlite:lume.db'); + const lumeStorage = new LumeStorage(sqlite); - if (!lumeStorage.account) await lumeStorage.getActiveAccount(); - setDB(lumeStorage); + if (!lumeStorage.account) await lumeStorage.getActiveAccount(); + setDB(lumeStorage); + } catch (e) { + await message(`Cannot initialize database: ${e}`, { + title: 'Lume', + type: 'error', + }); + } } useEffect(() => { diff --git a/src/shared/composer/mention/list.tsx b/src/shared/composer/mention/list.tsx index 97b2c7bb..a34a2b3a 100644 --- a/src/shared/composer/mention/list.tsx +++ b/src/shared/composer/mention/list.tsx @@ -1,75 +1,84 @@ import { NDKUserProfile } from '@nostr-dev-kit/ndk'; -import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; +import { type SuggestionProps } from '@tiptap/suggestion'; +import { + ForwardedRef, + forwardRef, + useEffect, + useImperativeHandle, + useState, +} from 'react'; import { twMerge } from 'tailwind-merge'; import { MentionItem } from '@shared/composer'; -export const MentionList = forwardRef((props: any, ref: any) => { - const [selectedIndex, setSelectedIndex] = useState(0); +export const MentionList = forwardRef( + (props: SuggestionProps, ref: ForwardedRef) => { + const [selectedIndex, setSelectedIndex] = useState(0); - const selectItem = (index) => { - const item = props.items[index]; + const selectItem = (index) => { + const item = props.items[index]; - if (item) { - props.command({ id: item }); - } - }; - - const upHandler = () => { - setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length); - }; - - const downHandler = () => { - setSelectedIndex((selectedIndex + 1) % props.items.length); - }; - - const enterHandler = () => { - selectItem(selectedIndex); - }; - - useEffect(() => setSelectedIndex(0), [props.items]); - - useImperativeHandle(ref, () => ({ - onKeyDown: ({ event }) => { - if (event.key === 'ArrowUp') { - upHandler(); - return true; + if (item) { + props.command({ id: item }); } + }; - if (event.key === 'ArrowDown') { - downHandler(); - return true; - } + const upHandler = () => { + setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length); + }; - if (event.key === 'Enter') { - enterHandler(); - return true; - } + const downHandler = () => { + setSelectedIndex((selectedIndex + 1) % props.items.length); + }; - return false; - }, - })); + const enterHandler = () => { + selectItem(selectedIndex); + }; - return ( -
- {props.items.length ? ( - props.items.map((item: NDKUserProfile, index: number) => ( - - )) - ) : ( -
No result
- )} -
- ); -}); + useEffect(() => setSelectedIndex(0), [props.items]); + + useImperativeHandle(ref, () => ({ + onKeyDown: ({ event }) => { + if (event.key === 'ArrowUp') { + upHandler(); + return true; + } + + if (event.key === 'ArrowDown') { + downHandler(); + return true; + } + + if (event.key === 'Enter') { + enterHandler(); + return true; + } + + return false; + }, + })); + + return ( +
+ {props.items.length ? ( + props.items.map((item: NDKUserProfile, index: number) => ( + + )) + ) : ( +
No result
+ )} +
+ ); + } +); MentionList.displayName = 'MentionList'; diff --git a/src/shared/nip05.tsx b/src/shared/nip05.tsx index 1753badf..f7c140c7 100644 --- a/src/shared/nip05.tsx +++ b/src/shared/nip05.tsx @@ -19,13 +19,13 @@ export function NIP05({ className?: string; }) { const { status, data } = useQuery( - [nip05], + ['nip05', nip05], async () => { try { - const username = nip05.split('@')[0]; + const localPath = nip05.split('@')[0]; const service = nip05.split('@')[1]; // #TODO: use tauri native fetch to avoid CORS - const verifyURL = `https://${service}/.well-known/nostr.json?name=${username}`; + const verifyURL = `https://${service}/.well-known/nostr.json?name=${localPath}`; const res = await fetch(verifyURL, { method: 'GET', @@ -37,11 +37,11 @@ export function NIP05({ if (!res.ok) throw new Error(`Failed to fetch NIP-05 service: ${nip05}`); const data: NIP05 = await res.json(); - if (data.names) { if (data.names.username !== pubkey) return false; return true; } + return false; } catch (e) { throw new Error(`Failed to verify NIP-05, error: ${e}`); } diff --git a/src/shared/notes/kinds/repost.tsx b/src/shared/notes/kinds/repost.tsx index 3f5f5918..142c779c 100644 --- a/src/shared/notes/kinds/repost.tsx +++ b/src/shared/notes/kinds/repost.tsx @@ -1,4 +1,5 @@ import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; +import { useCallback } from 'react'; import { ArticleNote, @@ -15,9 +16,25 @@ import { User } from '@shared/user'; import { useEvent } from '@utils/hooks/useEvent'; export function Repost({ event }: { event: NDKEvent }) { - const repostID = event.tags.find((el) => el[0] === 'e')?.[1]; + const repostID = event.tags.find((el) => el[0] === 'e')[1] ?? ''; const { status, data } = useEvent(repostID, event.content as unknown as string); + const renderKind = useCallback( + (event: NDKEvent) => { + switch (event.kind) { + case NDKKind.Text: + return ; + case NDKKind.Article: + return ; + case 1063: + return ; + default: + return ; + } + }, + [event] + ); + if (status === 'loading') { return (
@@ -43,19 +60,6 @@ export function Repost({ event }: { event: NDKEvent }) { ); } - const renderKind = (event: NDKEvent) => { - switch (event.kind) { - case NDKKind.Text: - return ; - case NDKKind.Article: - return ; - case 1063: - return ; - default: - return ; - } - }; - return (
@@ -64,7 +68,7 @@ export function Repost({ event }: { event: NDKEvent }) {
-
+
{renderKind(data)} diff --git a/src/shared/notes/kinds/unknown.tsx b/src/shared/notes/kinds/unknown.tsx index 2570896a..243e244d 100644 --- a/src/shared/notes/kinds/unknown.tsx +++ b/src/shared/notes/kinds/unknown.tsx @@ -2,7 +2,7 @@ import { NDKEvent } from '@nostr-dev-kit/ndk'; export function UnknownNote({ event }: { event: NDKEvent }) { return ( -
+
Unknown kind: {event.kind} diff --git a/src/shared/user.tsx b/src/shared/user.tsx index a07f54b0..a8a3be2d 100644 --- a/src/shared/user.tsx +++ b/src/shared/user.tsx @@ -23,8 +23,8 @@ export function User({ isChat?: boolean; }) { const { status, user } = useProfile(pubkey); - const createdAt = formatCreatedAt(time, isChat); + const createdAt = formatCreatedAt(time, isChat); const avatarWidth = size === 'small' ? 'w-6' : 'w-11'; const avatarHeight = size === 'small' ? 'h-6' : 'h-11'; @@ -102,10 +102,10 @@ export function User({
{user?.display_name || user?.name || user?.username}
- {user?.nip05 ? ( + {user.nip05 ? ( ) : ( diff --git a/src/utils/hooks/useEvent.ts b/src/utils/hooks/useEvent.ts index f9d36f32..e76f8b7e 100644 --- a/src/utils/hooks/useEvent.ts +++ b/src/utils/hooks/useEvent.ts @@ -19,36 +19,34 @@ export function useEvent(id: string, embed?: string) { } // get event from db const dbEvent = await db.getEventByID(id); - if (dbEvent) { - return dbEvent; + if (dbEvent) return dbEvent; + + // get event from relay if event in db not present + const event = await ndk.fetchEvent(id); + if (!event) throw new Error(`Event not found: ${id}`); + + let root: string; + let reply: string; + + if (event.tags?.[0]?.[0] === 'e' && !event.tags?.[0]?.[3]) { + root = event.tags[0][1]; } else { - // get event from relay if event in db not present - const event = await ndk.fetchEvent(id); - if (!event) throw new Error(`Event not found: ${id}`); - - let root: string; - let reply: string; - - if (event.tags?.[0]?.[0] === 'e' && !event.tags?.[0]?.[3]) { - root = event.tags[0][1]; - } else { - root = event.tags.find((el) => el[3] === 'root')?.[1]; - reply = event.tags.find((el) => el[3] === 'reply')?.[1]; - } - - const rawEvent = toRawEvent(event); - await db.createEvent( - event.id, - JSON.stringify(rawEvent), - event.pubkey, - event.kind, - root, - reply, - event.created_at - ); - - return event; + root = event.tags.find((el) => el[3] === 'root')?.[1]; + reply = event.tags.find((el) => el[3] === 'reply')?.[1]; } + + const rawEvent = toRawEvent(event); + await db.createEvent( + event.id, + JSON.stringify(rawEvent), + event.pubkey, + event.kind, + root, + reply, + event.created_at + ); + + return event; }, { enabled: !!ndk, diff --git a/src/utils/parser.ts b/src/utils/parser.ts index e41b188c..8b89cae4 100644 --- a/src/utils/parser.ts +++ b/src/utils/parser.ts @@ -5,8 +5,6 @@ import { Event, parseReferences } from 'nostr-tools'; import { RichContent } from '@utils/types'; export function parser(event: NDKEvent) { - if (event.kind !== 1) return; - const references = parseReferences(event as unknown as Event); const urls = getUrls(event.content as unknown as string);