From 8cadb7467fe3f900df2af8c8350392e3cc71746c Mon Sep 17 00:00:00 2001 From: Ren Amamiya <123083837+reyamir@users.noreply.github.com> Date: Thu, 27 Apr 2023 08:57:41 +0700 Subject: [PATCH] use swr for note fetcher --- src/components/note/parent.tsx | 173 +++++++++++----------------- src/components/note/quote.tsx | 113 +++++++----------- src/components/note/quoteRepost.tsx | 20 +--- src/components/note/rootNote.tsx | 113 ++++++------------ src/pages/index.page.tsx | 66 +++++------ src/utils/broadcast.tsx | 11 ++ src/utils/transform.tsx | 20 ++++ 7 files changed, 204 insertions(+), 312 deletions(-) create mode 100644 src/utils/broadcast.tsx diff --git a/src/components/note/parent.tsx b/src/components/note/parent.tsx index 486b4c8c..464855b4 100644 --- a/src/components/note/parent.tsx +++ b/src/components/note/parent.tsx @@ -1,4 +1,3 @@ -import { AccountContext } from '@components/accountProvider'; import { NoteMetadata } from '@components/note/metadata'; import { RelayContext } from '@components/relaysProvider'; import { UserExtend } from '@components/user/extend'; @@ -6,126 +5,90 @@ import { UserExtend } from '@components/user/extend'; import { READONLY_RELAYS } from '@stores/constants'; import { contentParser } from '@utils/parser'; -import { createNote, getNoteByID } from '@utils/storage'; -import { getParentID } from '@utils/transform'; -import { memo, useCallback, useContext, useEffect, useState } from 'react'; +import { memo, useContext } from 'react'; +import useSWRSubscription from 'swr/subscription'; export const NoteParent = memo(function NoteParent({ id }: { id: string }) { const pool: any = useContext(RelayContext); - const activeAccount: any = useContext(AccountContext); - const [event, setEvent] = useState(null); - const content = event ? contentParser(event.content, event.tags) : ''; - - const fetchEvent = useCallback(async () => { - const unsubscribe = pool.subscribe( - [ - { - ids: [id], - kinds: [1], + const { data, error } = useSWRSubscription( + id + ? [ + { + ids: [id], + kinds: [1], + }, + ] + : null, + (key, { next }) => { + const unsubscribe = pool.subscribe( + key, + READONLY_RELAYS, + (event: any) => { + next(null, event); }, - ], - READONLY_RELAYS, - (event: any) => { - // update state - setEvent(event); - // insert to database - const parentID = getParentID(event.tags, event.id); - // insert event to local database - createNote( - event.id, - activeAccount.id, - event.pubkey, - event.kind, - event.tags, - event.content, - event.created_at, - parentID - ); - }, - undefined, - undefined, - { - unsubscribeOnEose: true, - } - ); - - return () => { - unsubscribe(); - }; - }, [activeAccount.id, id, pool]); - - const checkNoteIsSaved = useCallback(async () => { - getNoteByID(id) - .then((res) => { - if (res) { - setEvent(res); - } else { - fetchEvent(); + undefined, + undefined, + { + unsubscribeOnEose: true, } - }) - .catch(console.error); - }, [fetchEvent, id]); + ); - useEffect(() => { - let ignore = false; - - if (!ignore) { - checkNoteIsSaved(); + return () => { + unsubscribe(); + }; } + ); - return () => { - ignore = true; - }; - }, [checkNoteIsSaved]); - - if (event) { - return ( -
-
-
- -
-
{content}
+ return ( +
+ {error &&
failed to load
} + {!data ? ( +
+
+
+
+
+
+
+ · +
+
+
+
-
e.stopPropagation()} className="mt-5 pl-[52px]"> - -
-
-
- ); - } else { - return ( -
-
-
-
-
-
-
- · +
+
+
+
+
-
-
-
-
-
-
-
+ ) : ( + <> +
+
+ +
+
+ {contentParser(data.content, data.tags)} +
+
+
e.stopPropagation()} className="mt-5 pl-[52px]"> +
-
-
- ); - } + + )} +
+ ); }); diff --git a/src/components/note/quote.tsx b/src/components/note/quote.tsx index ccf080bb..9e73a54f 100644 --- a/src/components/note/quote.tsx +++ b/src/components/note/quote.tsx @@ -1,95 +1,60 @@ -import { AccountContext } from '@components/accountProvider'; import { RelayContext } from '@components/relaysProvider'; import { UserExtend } from '@components/user/extend'; import { READONLY_RELAYS } from '@stores/constants'; import { contentParser } from '@utils/parser'; -import { createNote, getNoteByID } from '@utils/storage'; -import { getParentID } from '@utils/transform'; -import { memo, useCallback, useContext, useEffect, useState } from 'react'; +import { memo, useContext } from 'react'; +import useSWRSubscription from 'swr/subscription'; export const NoteQuote = memo(function NoteQuote({ id }: { id: string }) { const pool: any = useContext(RelayContext); - const activeAccount: any = useContext(AccountContext); - const [event, setEvent] = useState(null); - const content = event ? contentParser(event.content, event.tags) : ''; - - const fetchEvent = useCallback(async () => { - const unsubscribe = pool.subscribe( - [ - { - ids: [id], - kinds: [1], + const { data, error } = useSWRSubscription( + id + ? [ + { + ids: [id], + kinds: [1], + }, + ] + : null, + (key, { next }) => { + const unsubscribe = pool.subscribe( + key, + READONLY_RELAYS, + (event: any) => { + next(null, event); }, - ], - READONLY_RELAYS, - (event: any) => { - // update state - setEvent(event); - // insert to database - const parentID = getParentID(event.tags, event.id); - createNote( - event.id, - activeAccount.id, - event.pubkey, - event.kind, - event.tags, - event.content, - event.created_at, - parentID - ); - }, - undefined, - undefined, - { - unsubscribeOnEose: true, - } - ); - - return () => { - unsubscribe(); - }; - }, [activeAccount.id, id, pool]); - - const checkNoteIsSaved = useCallback(async () => { - getNoteByID(id) - .then((res) => { - if (res) { - setEvent(res); - } else { - fetchEvent(); + undefined, + undefined, + { + unsubscribeOnEose: true, } - }) - .catch(console.error); - }, [fetchEvent, id]); + ); - useEffect(() => { - let ignore = false; - - if (!ignore) { - checkNoteIsSaved(); + return () => { + unsubscribe(); + }; } + ); - return () => { - ignore = true; - }; - }, [checkNoteIsSaved]); - - if (event) { - return ( -
+ return ( +
+ {error &&
failed to load
} + {!data ? ( +
+ ) : (
- +
-
{content}
+
+ {contentParser(data.content, data.tags)} +
-
- ); - } else { - return
; - } + )} +
+ ); }); diff --git a/src/components/note/quoteRepost.tsx b/src/components/note/quoteRepost.tsx index e80f3aba..729fdbfe 100644 --- a/src/components/note/quoteRepost.tsx +++ b/src/components/note/quoteRepost.tsx @@ -1,24 +1,12 @@ import { RootNote } from '@components/note/rootNote'; import { UserQuoteRepost } from '@components/user/quoteRepost'; -import destr from 'destr'; +import { getQuoteID } from '@utils/transform'; + import { memo } from 'react'; export const NoteQuoteRepost = memo(function NoteQuoteRepost({ event }: { event: any }) { - const rootNote = () => { - let note = null; - - if (event.content) { - const content = destr(event.content); - if (content) { - note = ; - } - } else { - note = ; - } - - return note; - }; + const rootID = getQuoteID(event.tags); return (
@@ -26,7 +14,7 @@ export const NoteQuoteRepost = memo(function NoteQuoteRepost({ event }: { event:
- {rootNote()} +
); }); diff --git a/src/components/note/rootNote.tsx b/src/components/note/rootNote.tsx index f43cb3ca..b73759a8 100644 --- a/src/components/note/rootNote.tsx +++ b/src/components/note/rootNote.tsx @@ -6,42 +6,37 @@ import { READONLY_RELAYS } from '@stores/constants'; import { contentParser } from '@utils/parser'; -import { memo, useCallback, useContext, useEffect, useState } from 'react'; +import { memo, useContext } from 'react'; +import useSWRSubscription from 'swr/subscription'; import { navigate } from 'vite-plugin-ssr/client/router'; -export const RootNote = memo(function RootNote({ event }: { event: any }) { +export const RootNote = memo(function RootNote({ id }: { id: string }) { const pool: any = useContext(RelayContext); - const [data, setData] = useState(null); - const [content, setContent] = useState(''); - - const openUserPage = (e) => { - e.stopPropagation(); - navigate(`/user?pubkey=${event.pubkey}`); - }; - const openThread = (e) => { const selection = window.getSelection(); if (selection.toString().length === 0) { - navigate(`/newsfeed/note?id=${event.parent_id}`); + navigate(`/newsfeed/note?id=${id}`); } else { e.stopPropagation(); } }; - const fetchEvent = useCallback( - async (id: string) => { - const unsubscribe = pool.subscribe( - [ + const { data, error } = useSWRSubscription( + id + ? [ { ids: [id], kinds: [1], }, - ], + ] + : null, + (key, { next }) => { + const unsubscribe = pool.subscribe( + key, READONLY_RELAYS, (event: any) => { - setData(event); - setContent(contentParser(event.content, event.tags)); + next(null, event); }, undefined, undefined, @@ -53,72 +48,32 @@ export const RootNote = memo(function RootNote({ event }: { event: any }) { return () => { unsubscribe(); }; - }, - [pool] + } ); - useEffect(() => { - let ignore = false; - - if (!ignore) { - if (typeof event === 'object') { - setData(event); - setContent(contentParser(event.content, event.tags)); - } else { - fetchEvent(event); - } - } - - return () => { - ignore = true; - }; - }, [event, fetchEvent]); - - if (data) { - return ( -
openThread(e)} className="relative z-10 flex flex-col"> -
openUserPage(e)}> + return ( + <> + {error &&
failed to load
} + {!data ? ( +
+ ) : ( +
openThread(e)} className="relative z-10 flex flex-col"> -
-
-
{content}
-
-
e.stopPropagation()} className="mt-5 pl-[52px]"> - -
-
- ); - } else { - return ( -
-
-
-
-
-
-
- · -
-
-
+
+
+ {contentParser(data.content, data.tags)}
-
-
-
-
-
-
-
-
+
e.stopPropagation()} className="mt-5 pl-[52px]"> +
-
- ); - } + )} + + ); }); diff --git a/src/pages/index.page.tsx b/src/pages/index.page.tsx index e11a0f99..3be7beec 100644 --- a/src/pages/index.page.tsx +++ b/src/pages/index.page.tsx @@ -1,6 +1,6 @@ import { RelayContext } from '@components/relaysProvider'; -import { FULL_RELAYS } from '@stores/constants'; +import { READONLY_RELAYS } from '@stores/constants'; import { dateToUnix, hoursAgo } from '@utils/getDate'; import { @@ -16,17 +16,18 @@ import { getParentID } from '@utils/transform'; import LumeSymbol from '@assets/icons/Lume'; -import { useCallback, useContext, useEffect, useRef } from 'react'; +import { useContext, useEffect, useRef } from 'react'; import { navigate } from 'vite-plugin-ssr/client/router'; export function Page() { const pool: any = useContext(RelayContext); - const now = useRef(new Date()); - const timeout = useRef(null); - const fetchData = useCallback( - async (account: { id: number; pubkey: string; chats: string[] }, tags: any) => { + useEffect(() => { + let unsubscribe: () => void; + let timeout: any; + + const fetchInitalData = async (account: { pubkey: string; id: number }, tags: string) => { const lastLogin = await getLastLogin(); const notes = await countTotalNotes(); @@ -52,6 +53,7 @@ export function Page() { since: since, until: dateToUnix(now.current), }); + // kind 4 (chats) query query.push({ kinds: [4], @@ -59,6 +61,7 @@ export function Page() { since: 0, until: dateToUnix(now.current), }); + // kind 43, 43 (mute user, hide message) query query.push({ authors: [account.pubkey], @@ -66,10 +69,11 @@ export function Page() { since: 0, until: dateToUnix(now.current), }); + // subscribe relays - const unsubscribe = pool.subscribe( + unsubscribe = pool.subscribe( query, - FULL_RELAYS, + READONLY_RELAYS, (event: { kind: number; tags: string[]; id: string; pubkey: string; content: string; created_at: number }) => { switch (event.kind) { // short text note @@ -123,44 +127,30 @@ export function Page() { undefined, () => { updateLastLogin(dateToUnix(now.current)); - timeout.current = setTimeout(() => { + timeout = setTimeout(() => { navigate('/newsfeed/following', { overwriteLastHistoryEntry: true }); }, 5000); - }, - { - unsubscribeOnEose: true, - logAllEvents: false, } ); + }; - return () => { - unsubscribe(); - }; - }, - [pool] - ); - - useEffect(() => { - let ignore = false; - - if (!ignore) { - getActiveAccount() - .then((res: any) => { - if (res) { - const account = res; - fetchData(account, account.follows); - } else { - navigate('/onboarding', { overwriteLastHistoryEntry: true }); - } - }) - .catch(console.error); - } + getActiveAccount() + .then((res: any) => { + if (res) { + fetchInitalData(res, res.follows); + } else { + navigate('/onboarding', { overwriteLastHistoryEntry: true }); + } + }) + .catch(console.error); return () => { - ignore = true; - clearTimeout(timeout.current); + if (unsubscribe) { + unsubscribe(); + } + clearTimeout(timeout); }; - }, [fetchData]); + }, []); return (
diff --git a/src/utils/broadcast.tsx b/src/utils/broadcast.tsx new file mode 100644 index 00000000..26778af0 --- /dev/null +++ b/src/utils/broadcast.tsx @@ -0,0 +1,11 @@ +import { WRITEONLY_RELAYS } from '@stores/constants'; + +import { getEventHash, signEvent } from 'nostr-tools'; + +export const broadcast = ({ pool, data, privkey }: { pool: any; data: any; privkey: string }) => { + const event = data; + event.id = getEventHash(event); + event.sig = signEvent(event, privkey); + + pool.publish(event, WRITEONLY_RELAYS); +}; diff --git a/src/utils/transform.tsx b/src/utils/transform.tsx index 1fb1caf2..aca05ac9 100644 --- a/src/utils/transform.tsx +++ b/src/utils/transform.tsx @@ -50,6 +50,26 @@ export const getParentID = (arr: string[], fallback: string) => { return parentID; }; +// get parent id from event tags +export const getQuoteID = (arr: string[]) => { + const tags = destr(arr); + let quoteID = null; + + if (tags.length > 0) { + if (tags[0][0] === 'e') { + quoteID = tags[0][1]; + } else { + tags.forEach((tag) => { + if (tag[0] === 'e') { + quoteID = tag[1]; + } + }); + } + } + + return quoteID; +}; + // sort messages by timestamp export const sortMessages = (arr: any) => { arr.sort((a, b) => {