Skip to content

Commit

Permalink
A lot of refactoring on new events system
Browse files Browse the repository at this point in the history
  • Loading branch information
y9san9 committed Nov 30, 2024
1 parent 17bd83b commit 8bc64d7
Show file tree
Hide file tree
Showing 33 changed files with 397 additions and 431 deletions.
1 change: 1 addition & 0 deletions src/api/create-server-socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export function createServerSocket(url: string): SeedSocket {
}

if (event.type == "event") {
console.log("<< ws: event", event);
events.emit(event.event);
}
};
Expand Down
10 changes: 8 additions & 2 deletions src/api/event/socket-event.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import {SocketEventNew} from "@/api/event/socket-event-new.ts";
import {Message} from "@/api/message/message.ts";

export type SocketEvent = SocketEventNew;
export type SocketEvent = {
type: "new";
message: Message;
} | {
type: "wait";
chatId: string;
};
3 changes: 2 additions & 1 deletion src/api/request/subscribe-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import {SocketRequest} from "@/api/request/socket-request.ts";

export interface SubscribeRequest extends SocketRequest<never> {
type: "subscribe";
chatId: string[];
chatId: string;
nonce: number;
}
19 changes: 11 additions & 8 deletions src/components/AppDependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,18 @@ export async function createAppDependencies(): Promise<AppDependencies> {
const persistence = await createPersistence();
const socket = createServerSocket("https://meetacy.app/seed-go");

socket.bind({ type: "subscribe", chatId: [chat.chatId] } as SubscribeRequest);
const lastNonce = await persistence.message.lastMessageNonce(chat);

if (await persistence.key.last({ chat }) == null) {
await persistence.key.push({
chat: chat,
socket.bind({ type: "subscribe", chatId: chat.chatId, nonce: lastNonce ?? 0 } as SubscribeRequest);

if (await persistence.message.lastMessage(chat) == null) {
await persistence.message.add({
...chat,
key: key,
nonce: 0
nonce: 0,
content: {
type: "deferred"
}
});
}

Expand All @@ -34,9 +39,7 @@ export async function createAppDependencies(): Promise<AppDependencies> {
socket: socket,
persistence: persistence,
createChat() {
const result = createChatDependencies(this, chat);
result.loadMore();
return result;
return createChatDependencies(this, chat);
}
};

Expand Down
41 changes: 25 additions & 16 deletions src/components/chat/ChatDependencies.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import {createChatEventBus} from "@/usecase/chat/event-bus/create-chat-event-bus.ts";
import {createGetHistoryUsecase} from "@/usecase/chat/get-history-usecase/create-get-history-usecase.ts";
import {createLoadMoreUsecase} from "@/usecase/chat/load-more-usecase/create-load-more-usecase.ts";
import {AppDependencies} from "@/components/AppDependencies.ts";
import {createMessageCoder} from "@/crypto/create-message-coder.ts";
import {createGetMessageKeyUsecase} from "@/usecase/chat/get-message-key-usecase/create-get-message-key-usecase.ts";
import {Chat} from "@/persistence/chat/chat.ts";
import {EventBus} from "@/usecase/chat/event-bus/event-bus.ts";
import {LoadMoreUsecase} from "@/usecase/chat/load-more-usecase/load-more-usecase.ts";
import {SendMessageUsecase} from "@/usecase/chat/send-message/send-message-usecase.ts";
import {createSendMessageUsecase} from "@/usecase/chat/send-message/create-send-message-usecase.ts";
import {
createSendTextMessageUsecase,
SendTextMessageUsecase
} from "@/usecase/chat/send-message/send-text-message-usecase.ts";
import {createLocalNonceUsecase} from "@/usecase/chat/nonce/create-local-nonce-usecase.ts";
import {SetNicknameUsecase} from "@/usecase/chat/nickname/set-nickname-usecase.ts";
import {createSetNicknameUsecase} from "@/usecase/chat/nickname/create-set-nickname-usecase.ts";
Expand All @@ -20,12 +19,14 @@ import {
} from "@/usecase/chat/messages-snapshot-usecase/create-messages-snapshot-usecase.ts";
import {createDecodeMessagesUsecase} from "@/usecase/chat/decode-messages-usecase/decode-message-usecase.ts";
import {handleSocketEventsUsecase} from "@/usecase/chat/socket-event-usecase/handle-socket-events-usecase.ts";
import {createSendMessageUsecase} from "@/usecase/chat/send-message/send-message-usecase.ts";
import {messagesHistoryUsecase} from "@/usecase/chat/messages-history-usecase/messages-history-usecase.ts";
import {OptionPredicator} from "typia/lib/programmers/helpers/OptionPredicator";

export interface ChatDependencies {
chat: Chat;
events: EventBus;
loadMore: LoadMoreUsecase;
sendMessage: SendMessageUsecase;
sendMessage: SendTextMessageUsecase;
setNickname: SetNicknameUsecase;
getNickname: GetNicknameUsecase;
}
Expand All @@ -36,8 +37,6 @@ export function createChatDependencies(
): ChatDependencies {
const {socket, persistence} = app;

const {key} = persistence;

const events = createChatEventBus();
const coder = createMessageCoder();
const sanitizeContent = createSanitizeContentUsecase();
Expand All @@ -53,34 +52,44 @@ export function createChatDependencies(
storage: persistence.nickname
});

const getMessageKey = createGetMessageKeyUsecase({ keyStorage: key, coder, chat });
const getMessageKey = createGetMessageKeyUsecase({ messageStorage: persistence.message, coder, chat });

const decodeMessage = createDecodeMessagesUsecase({
getMessageKey, coder,
sanitizeContent, getNickname
});

const getHistory = createGetHistoryUsecase({ socket, chat, decodeMessage });
const loadMore = createLoadMoreUsecase({ getHistory, events });

const localNonce = createLocalNonceUsecase();

const sendMessage = createSendMessageUsecase({
coder, events, localNonce,
getMessageKey, socket,
nickname, sanitizeContent
sanitizeContent, chat
});

const sendTextMessage = createSendTextMessageUsecase({
nickname, sendMessage
});

const setNickname = createSetNicknameUsecase({
events,
storage: persistence.nickname
});

const socketEventUsecase = handleSocketEventsUsecase({decodeMessage, events, socket, messagesSnapshot});
const socketEventUsecase = handleSocketEventsUsecase({
decodeMessage, events,
socket, messagesSnapshot,
messagesStorage: persistence.message
});
socketEventUsecase();

const messageHistoryUsecase = messagesHistoryUsecase({
chat, events, getNickname, messagesStorage: persistence.message
});
messageHistoryUsecase();

return {
events, loadMore, sendMessage,
events, sendMessage: sendTextMessage,
chat, setNickname, getNickname
};
}
28 changes: 17 additions & 11 deletions src/components/chat/ChatScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,22 @@ import {EmptyMessages} from "@/components/chat/EmptyMessages.tsx";
import {MessageInput} from "@/components/chat/MessageInput.tsx";
import {useEffect, useState} from "react";
import {ChatDependencies} from "@/components/chat/ChatDependencies.ts";
import {LoadingSpinner} from "@/components/ui/loading-spinner.tsx";

export function ChatScreen(
{chat, events, loadMore, sendMessage, setNickname, getNickname}: ChatDependencies
{events, sendMessage, setNickname, getNickname}: ChatDependencies
) {
const [messages, setMessages] = useState<Message[]>([]);

const [hasMore, setHasMore] = useState(true);
const [loaded, setLoaded] = useState(false);
const [text, setText] = useState("");
const [nickname, setNicknameState] = useState(getNickname());

useEffect(() => {
const subscription = events.flow.collect((event) => {
switch (event.type) {
case "has_no_more":
setHasMore(false);
case "loaded":
setLoaded(true);
break;
case "messages_snapshot":
setMessages(event.messages);
Expand All @@ -42,18 +43,17 @@ export function ChatScreen(
<div className="w-full flex-grow flex justify-center">
<div className="h-full flex-grow max-w-full md:max-w-3xl flex flex-col">
{
(messages.length == 0 && !hasMore)
? EmptyMessages()
: <MessagesList
messages={messages}
hasMore={hasMore}
next={loadMore} />
loaded
? messages.length == 0
? EmptyMessages()
: <MessagesList messages={messages}/>
: LoadingMessages()
}

<MessageInput
text={text}
setText={setText}
onClick={() => sendMessage({ text, ...chat })}
onClick={() => sendMessage({ text })}
/>
</div>
</div>
Expand All @@ -66,3 +66,9 @@ export function ChatScreen(
</>
)
}

function LoadingMessages() {
return <>
<div className="w-full h-full flex justify-center items-center"><LoadingSpinner/></div>
</>;
}
10 changes: 3 additions & 7 deletions src/components/chat/MessagesList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,15 @@ import {LoadingSpinner} from "@/components/ui/loading-spinner.tsx";
import {CircleX} from "lucide-react";

export function MessagesList(
{messages, hasMore, next}: {
messages: Message[];
hasMore: boolean;
next(): void;
}
{messages}: { messages: Message[]; }
) {
return <>
<div className="flex flex-col-reverse flex-grow h-0 w-full overflow-y-scroll no-scrollbar" id="chatMessageListScroll">

<ChatMessageList
dataLength={messages.length}
next={next}
hasMore={hasMore}
next={() => {}}
hasMore={false}
style={{display: 'flex', flexDirection: 'column-reverse'}}>
{messages.map((message) => {
const variant = message.isAuthor ? 'sent' : 'received';
Expand Down
33 changes: 18 additions & 15 deletions src/crypto/create-message-coder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,46 @@ import {decryptAes256, encryptAes256, hmacSha256, verifyHmacSha256} from "@/cryp
export function createMessageCoder(): MessageCoder {
return {
async decode({ content, contentIV, signature, key }) {
// Check that message is signed
const verify = await verifyHmacSha256({
data: "SIGNATURE",
key: key,
signature: signature
})

if (!verify) {
return null
}

// Decrypt content
const decryptedJSON = await decryptAes256({
encrypted: content,
iv: contentIV,
key: key,
})
});

if (!decryptedJSON) return null;

// Check that message is signed
const verify = await verifyHmacSha256({
data: "SIGNATURE:" + decryptedJSON.string,
key: key,
signature: signature
});

if (!verify) return null;

return JSON.parse(decryptedJSON.string) as MessageContent;
},

async encode({ content, key }) {
const contentString = JSON.stringify(content);

const signature = await hmacSha256({
data: "SIGNATURE",
data: "SIGNATURE:" + contentString,
key: key
})
});

const encryptedContent = await encryptAes256({
data: JSON.stringify(content),
data: contentString,
key: key
});

return {
contentIV: encryptedContent.iv,
content: encryptedContent.encrypted,
signature: signature
}
};
},

deriveNextKey(key: string): Promise<string> {
Expand Down
4 changes: 4 additions & 0 deletions src/crypto/message/content/typing-content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface TypingContent {
type: "typing";
title: string;
}
4 changes: 3 additions & 1 deletion src/crypto/message/content/unknown-content.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export type UnknownContent = { type: "unknown" };
export interface UnknownContent {
type: "unknown";
}
28 changes: 15 additions & 13 deletions src/crypto/subtle-crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,24 @@ export async function decryptAes256(
iv: string;
key: string;
}
): Promise<{ string: string }> {
): Promise<{ string: string } | undefined> {
const { encrypted, iv, key } = options;
const importedKey = await importCryptoKey(key, aesOptions, "decrypt");

const decrypted = await crypto.decrypt(
{
iv: base64ToArrayBuffer(iv),
...aesOptions
},
importedKey,
base64ToArrayBuffer(encrypted)
)

return {
string: arrayBufferToString(decrypted)
}
try {
const decrypted = await crypto.decrypt(
{
iv: base64ToArrayBuffer(iv),
...aesOptions
},
importedKey,
base64ToArrayBuffer(encrypted)
)

return {
string: arrayBufferToString(decrypted)
}
} catch (error) {}
}


Expand Down
3 changes: 0 additions & 3 deletions src/persistence/create-persistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@ import {Persistence} from "@/persistence/persistence.ts";
import {openDB} from "idb";
import {createChatObjectStore, createChatStorage} from "@/persistence/chat/create-chat-storage.ts";
import {createMessageObjectStore, createMessageStorage} from "@/persistence/message/create-message-storage.ts";
import {createKeyObjectStore, createKeyStorage} from "@/persistence/key/create-key-storage.ts";
import {createNicknameObjectStore, createNicknameStorage} from "@/persistence/nickname/create-nickname-storage.ts";

export async function createPersistence(): Promise<Persistence> {
const db = await openDB("persistence", 2, {
upgrade(database, version) {
if (version == 0) {
createMessageObjectStore(database);
createKeyObjectStore(database);
createChatObjectStore(database);
createNicknameObjectStore(database);
}
Expand All @@ -23,7 +21,6 @@ export async function createPersistence(): Promise<Persistence> {
return {
chat: createChatStorage(db),
message: createMessageStorage(db),
key: createKeyStorage(db),
nickname: createNicknameStorage(db)
};
}
Loading

0 comments on commit 8bc64d7

Please sign in to comment.