added useDecryptMessage hook and updated chat messages

This commit is contained in:
Ren Amamiya 2023-04-16 12:45:17 +07:00
parent b778cf6198
commit a7e95fd18a
11 changed files with 108 additions and 61 deletions

View File

@ -4,17 +4,24 @@ import { MessageList } from '@components/chats/messageList';
import FormChat from '@components/form/chat';
import { RelayContext } from '@components/relaysProvider';
import { chatMessagesAtom } from '@stores/chat';
import useLocalStorage from '@rehooks/local-storage';
import { useContext, useEffect, useState } from 'react';
import { useSetAtom } from 'jotai';
import { useResetAtom } from 'jotai/utils';
import { Suspense, useCallback, useContext, useEffect, useRef } from 'react';
export default function Page({ params }: { params: { pubkey: string } }) {
const [pool, relays]: any = useContext(RelayContext);
const [activeAccount]: any = useLocalStorage('activeAccount', {});
const [messages, setMessages] = useState([]);
useEffect(() => {
const unsubscribe = pool.subscribe(
const setChatMessages = useSetAtom(chatMessagesAtom);
const resetChatMessages = useResetAtom(chatMessagesAtom);
const unsubscribe = useRef(null);
const fetchMessages = useCallback(() => {
unsubscribe.current = pool.subscribe(
[
{
kinds: [4],
@ -29,18 +36,29 @@ export default function Page({ params }: { params: { pubkey: string } }) {
],
relays,
(event: any) => {
setMessages((messages) => [event, ...messages]);
setChatMessages((data) => [...data, event]);
}
);
}, [activeAccount.pubkey, params.pubkey, pool, relays, setChatMessages]);
useEffect(() => {
// reset stored messages
resetChatMessages();
// fetch messages from relays
fetchMessages();
return () => {
unsubscribe();
if (unsubscribe.current) {
unsubscribe.current();
}
};
}, [pool, relays, params.pubkey, activeAccount.pubkey]);
}, [fetchMessages, resetChatMessages]);
return (
<div className="flex h-full w-full flex-col justify-between">
<MessageList data={messages.sort((a, b) => a.created_at - b.created_at)} />
<Suspense fallback={<>Loading...</>}>
<MessageList />
</Suspense>
<div className="shrink-0 p-3">
<FormChat receiverPubkey={params.pubkey} />
</div>

View File

@ -23,9 +23,9 @@ export default function ChannelList() {
<div className="flex flex-col gap-px">
<Link
href="/channels"
className="group inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 hover:bg-zinc-950"
className="group inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 hover:bg-zinc-900"
>
<div className="inline-flex h-5 w-5 shrink items-center justify-center rounded bg-zinc-900">
<div className="inline-flex h-5 w-5 shrink items-center justify-center rounded bg-zinc-900 group-hover:bg-zinc-800">
<Globe width={12} height={12} className="text-zinc-500" />
</div>
<div>

View File

@ -51,8 +51,8 @@ export const CreateChannelModal = () => {
return (
<Dialog.Root open={open} onOpenChange={setOpen}>
<Dialog.Trigger asChild>
<div className="group inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 hover:bg-zinc-950">
<div className="inline-flex h-5 w-5 shrink items-center justify-center rounded bg-zinc-900">
<div className="group inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 hover:bg-zinc-900">
<div className="inline-flex h-5 w-5 shrink items-center justify-center rounded bg-zinc-900 group-hover:bg-zinc-800">
<Plus width={12} height={12} className="text-zinc-500" />
</div>
<div>

View File

@ -23,8 +23,8 @@ export const ChatModal = () => {
return (
<Dialog.Root>
<Dialog.Trigger asChild>
<div className="group inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 hover:bg-zinc-950">
<div className="inline-flex h-5 w-5 shrink items-center justify-center rounded bg-zinc-900">
<div className="group inline-flex items-center gap-2 rounded-md px-2.5 py-1.5 hover:bg-zinc-900">
<div className="group-hover:800 inline-flex h-5 w-5 shrink items-center justify-center rounded bg-zinc-900">
<Plus width={12} height={12} className="text-zinc-500" />
</div>
<div>

View File

@ -1,21 +1,23 @@
import MessageListItem from '@components/chats/messageListItem';
import { Placeholder } from '@components/note/placeholder';
import { sortedChatMessagesAtom } from '@stores/chat';
import useLocalStorage from '@rehooks/local-storage';
import { useAtomValue } from 'jotai';
import { useCallback, useRef } from 'react';
import { Virtuoso } from 'react-virtuoso';
export const MessageList = ({ data }: { data: any }) => {
export const MessageList = () => {
const [activeAccount]: any = useLocalStorage('activeAccount', {});
const virtuosoRef = useRef(null);
const data = useAtomValue(sortedChatMessagesAtom);
const itemContent: any = useCallback(
(index: string | number) => {
return (
<MessageListItem
data={data[index]}
activeAccountPubkey={activeAccount.pubkey}
activeAccountPrivkey={activeAccount.privkey}
/>
<MessageListItem data={data[index]} userPubkey={activeAccount.pubkey} userPrivkey={activeAccount.privkey} />
);
},
[activeAccount.privkey, activeAccount.pubkey, data]
@ -33,6 +35,7 @@ export const MessageList = ({ data }: { data: any }) => {
<Virtuoso
ref={virtuosoRef}
data={data}
components={COMPONENTS}
itemContent={itemContent}
computeItemKey={computeItemKey}
initialTopMostItemIndex={data.length - 1}
@ -45,3 +48,8 @@ export const MessageList = ({ data }: { data: any }) => {
</div>
);
};
const COMPONENTS = {
EmptyPlaceholder: () => <Placeholder />,
ScrollSeekPlaceholder: () => <Placeholder />,
};

View File

@ -1,36 +1,11 @@
import { MessageUser } from '@components/chats/messageUser';
import { nip04 } from 'nostr-tools';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { useDecryptMessage } from '@utils/hooks/useDecryptMessage';
const MessageListItem = ({
data,
activeAccountPubkey,
activeAccountPrivkey,
}: {
data: any;
activeAccountPubkey: string;
activeAccountPrivkey: string;
}) => {
const [content, setContent] = useState('');
import { memo } from 'react';
const sender = useMemo(() => {
const pTag = data.tags.find(([k, v]) => k === 'p' && v && v !== '')[1];
if (pTag === activeAccountPubkey) {
return data.pubkey;
} else {
return pTag;
}
}, [data.pubkey, data.tags, activeAccountPubkey]);
const decryptContent = useCallback(async () => {
const result = await nip04.decrypt(activeAccountPrivkey, sender, data.content);
setContent(result);
}, [data.content, activeAccountPrivkey, sender]);
useEffect(() => {
decryptContent().catch(console.error);
}, [decryptContent]);
const MessageListItem = ({ data, userPubkey, userPrivkey }: { data: any; userPubkey: string; userPrivkey: string }) => {
const content = useDecryptMessage(userPubkey, userPrivkey, data.pubkey, data.tags, data.content);
return (
<div className="flex h-min min-h-min w-full select-text flex-col px-5 py-2 hover:bg-black/20">

View File

@ -25,7 +25,6 @@ export default function EventCollector() {
const now = useRef(new Date());
const unsubscribe = useRef(null);
const unlisten = useRef(null);
const createFollowingPlebs = useCallback(
async (tags) => {
@ -115,7 +114,7 @@ export default function EventCollector() {
}, [pool, relays, activeAccount.id, activeAccount.pubkey, follows, setHasNewerNote, createFollowingPlebs]);
const listenWindowClose = useCallback(async () => {
unlisten.current = window.getCurrent().listen(TauriEvent.WINDOW_CLOSE_REQUESTED, () => {
window.getCurrent().listen(TauriEvent.WINDOW_CLOSE_REQUESTED, () => {
writeStorage('lastLogin', now.current);
window.getCurrent().close();
});
@ -129,9 +128,6 @@ export default function EventCollector() {
if (unsubscribe.current) {
unsubscribe.current();
}
if (unlisten.current) {
unlisten.current;
}
};
}, [setHasNewerNote, subscribe, listenWindowClose]);

View File

@ -3,7 +3,7 @@
import { ActiveLink } from '@components/activeLink';
import * as Collapsible from '@radix-ui/react-collapsible';
import { NavArrowUp } from 'iconoir-react';
import { Bonfire, NavArrowUp, PeopleTag } from 'iconoir-react';
import { useState } from 'react';
export default function Newsfeed() {
@ -28,6 +28,7 @@ export default function Newsfeed() {
activeClassName="dark:bg-zinc-900 dark:text-zinc-100 hover:dark:bg-zinc-800"
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-sm font-medium hover:text-zinc-200"
>
<PeopleTag width={16} height={16} className="text-zinc-500" />
<span>Following</span>
</ActiveLink>
<ActiveLink
@ -35,6 +36,7 @@ export default function Newsfeed() {
activeClassName="dark:bg-zinc-900 dark:text-zinc-100 hover:dark:bg-zinc-800"
className="flex h-8 items-center gap-2.5 rounded-md px-2.5 text-sm font-medium hover:text-zinc-200"
>
<Bonfire width={16} height={16} className="text-zinc-500" />
<span>Circle</span>
</ActiveLink>
</Collapsible.Content>

8
src/stores/chat.tsx Normal file
View File

@ -0,0 +1,8 @@
import { atom } from 'jotai';
import { atomWithReset } from 'jotai/utils';
export const chatMessagesAtom = atomWithReset([]);
export const sortedChatMessagesAtom = atom((get) => {
const messages = get(chatMessagesAtom);
return messages.sort((x: { created_at: number }, y: { created_at: number }) => x.created_at - y.created_at);
});

View File

@ -0,0 +1,34 @@
import { nip04 } from 'nostr-tools';
import { useCallback, useEffect, useState } from 'react';
export const useDecryptMessage = (
userKey: string,
userPriv: string,
eventKey: string,
eventTags: string[],
encryptedContent: string
) => {
const [content, setContent] = useState('');
const extractSenderKey = useCallback(() => {
const keyInTags = eventTags.find(([k, v]) => k === 'p' && v && v !== '')[1];
if (keyInTags === userKey) {
return eventKey;
} else {
return keyInTags;
}
}, [eventKey, eventTags, userKey]);
const decrypt = useCallback(async () => {
const senderKey = extractSenderKey();
const result = await nip04.decrypt(userPriv, senderKey, encryptedContent);
// update state with decrypt content
setContent(result);
}, [userPriv, encryptedContent, extractSenderKey]);
useEffect(() => {
decrypt().catch(console.error);
}, [decrypt]);
return content;
};

View File

@ -16,14 +16,20 @@ export const useMetadata = (pubkey) => {
const [profile, setProfile] = useState(null);
const cacheProfile = useMemo(() => {
const findInStorage = plebs.find((item) => item.pubkey === pubkey);
let metadata = false;
if (findInStorage !== undefined) {
return JSON.parse(findInStorage.metadata);
if (pubkey === activeAccount.pubkey) {
metadata = JSON.parse(activeAccount.metadata);
} else {
return false;
const findInStorage = plebs.find((item) => item.pubkey === pubkey);
if (findInStorage !== undefined) {
metadata = JSON.parse(findInStorage.metadata);
}
}
}, [plebs, pubkey]);
return metadata;
}, [plebs, pubkey, activeAccount.pubkey, activeAccount.metadata]);
const insertPlebToDB = useCallback(
async (pubkey: string, metadata: string) => {