From 595492883464bd4ca79d30e724d7796510e923b9 Mon Sep 17 00:00:00 2001 From: Shibo Lyu Date: Wed, 4 Sep 2024 03:01:00 +0800 Subject: [PATCH] refactor: extract logic from chat page --- src/lib/blah/connection/chatServer.ts | 67 +++++++++++++++--- src/lib/chatServers.ts | 63 +++++++++++++++++ src/routes/(app)/chats/[chatId]/+page.svelte | 68 ------------------- .../chats/[server]/[chatId]/+page.svelte | 45 ++++++++++++ .../{ => [server]}/[chatId]/ChatHeader.svelte | 6 +- .../[chatId]/ChatHistory.svelte | 0 .../{ => [server]}/[chatId]/ChatInput.svelte | 33 +++------ .../[chatId]/ChatMessage.svelte | 0 .../chats/[server]/[chatId]/ChatPage.svelte | 25 +++++++ 9 files changed, 203 insertions(+), 104 deletions(-) create mode 100644 src/lib/chatServers.ts delete mode 100644 src/routes/(app)/chats/[chatId]/+page.svelte create mode 100644 src/routes/(app)/chats/[server]/[chatId]/+page.svelte rename src/routes/(app)/chats/{ => [server]}/[chatId]/ChatHeader.svelte (94%) rename src/routes/(app)/chats/{ => [server]}/[chatId]/ChatHistory.svelte (100%) rename src/routes/(app)/chats/{ => [server]}/[chatId]/ChatInput.svelte (68%) rename src/routes/(app)/chats/{ => [server]}/[chatId]/ChatMessage.svelte (100%) create mode 100644 src/routes/(app)/chats/[server]/[chatId]/ChatPage.svelte diff --git a/src/lib/blah/connection/chatServer.ts b/src/lib/blah/connection/chatServer.ts index 705a706..93868c4 100644 --- a/src/lib/blah/connection/chatServer.ts +++ b/src/lib/blah/connection/chatServer.ts @@ -1,5 +1,7 @@ import { version } from '$app/environment'; import type { BlahRichText } from '$lib/richText'; +import { messageFromBlah, type Chat, type Message } from '$lib/types'; +import { readable, type Readable } from 'svelte/store'; import type { BlahKeyPair, BlahSignedPayload } from '../crypto'; import type { BlahAuth, BlahMessage, BlahRoomInfo, BlahUserJoinMessage } from '../structures'; import { BlahError } from './error'; @@ -11,14 +13,14 @@ export class BlahChatServerConnection { private static commonHeaders = { 'x-blah-client': `Weblah/${version}` }; private endpoint: string; - private keypair?: BlahKeyPair; + private keypair: BlahKeyPair | null; private webSocket: WebSocket | null = null; private messageListeners: Map) => void>> = new Map(); private webSocketRetryTimeout: number | null = null; - constructor(endpoint: string, keypair?: BlahKeyPair) { + constructor(endpoint: string, keypair: BlahKeyPair | null = null) { this.endpoint = endpoint; this.keypair = keypair; } @@ -147,11 +149,29 @@ export class BlahChatServerConnection { return socket; } + connect() { + if (!this.webSocket) this.webSocket = this.createWebSocket(); + } + + disconnect() { + if (this.webSocketRetryTimeout) clearTimeout(this.webSocketRetryTimeout); + this.webSocket?.close(); + this.webSocket = null; + } + + changeKeyPair(keypair: BlahKeyPair | null) { + this.keypair = keypair; + if (this.webSocket) { + this.disconnect(); + this.connect(); + } + } + subscribeRoom( roomId: string, onNewMessage: (message: BlahSignedPayload) => void ): { unsubscribe: () => void } { - if (!this.webSocket) this.webSocket = this.createWebSocket(); + if (!this.webSocket) throw new Error('Must connect to WebSocket before subscribing to rooms'); const listeners = this.messageListeners.get(roomId) ?? new Set(); listeners.add(onNewMessage); @@ -164,12 +184,43 @@ export class BlahChatServerConnection { if (listeners.size === 0) { this.messageListeners.delete(roomId); } - if (this.messageListeners.size === 0) { - if (this.webSocketRetryTimeout) clearTimeout(this.webSocketRetryTimeout); - this.webSocket?.close(); - this.webSocket = null; - } } }; } + + chat(chatId: string): { + info: Readable; + messages: Readable; + sendMessage: (brt: BlahRichText) => Promise; + } { + const info = readable( + { server: this.endpoint, id: chatId, name: '', type: 'group' }, + (set) => { + this.fetchRoomInfo(chatId).then((room) => { + set({ server: this.endpoint, id: chatId, name: room.title, type: 'group' }); + }); + } + ); + + const messages = readable([], (set, update) => { + this.fetchRoomHistory(chatId).then((history) => + update((messages) => [ + ...history.map(messageFromBlah).toSorted((a, b) => a.date.getTime() - b.date.getTime()), + ...messages + ]) + ); + + const { unsubscribe } = this.subscribeRoom(chatId, (message) => { + update((messages) => [...messages, messageFromBlah(message)]); + }); + + return unsubscribe; + }); + + const sendMessage = async (brt: BlahRichText) => { + await this.sendMessage(chatId, brt); + }; + + return { info, messages, sendMessage }; + } } diff --git a/src/lib/chatServers.ts b/src/lib/chatServers.ts new file mode 100644 index 0000000..1761a5d --- /dev/null +++ b/src/lib/chatServers.ts @@ -0,0 +1,63 @@ +import { persisted } from 'svelte-persisted-store'; +import { get } from 'svelte/store'; +import { BlahChatServerConnection } from './blah/connection/chatServer'; +import { BlahKeyPair, type EncodedBlahKeyPair } from './blah/crypto'; +import { currentKeyPair } from './keystore'; + +export const chatServers = persisted('weblah-chat-servers', ['https://blah.oxa.li/api']); + +class ChatServerConnectionPool { + private connections: Map = new Map(); + private keypair: BlahKeyPair | null = null; + + constructor() { + chatServers.subscribe(this.onChatServersChange.bind(this)); + currentKeyPair.subscribe(this.onKeyPairChange.bind(this)); + } + + connectAll(keypair?: BlahKeyPair) { + for (const endpoint of get(chatServers)) { + const connection = new BlahChatServerConnection(endpoint, keypair); + this.connections.set(endpoint, connection); + connection.connect(); + } + } + disconnectAll() { + for (const connection of this.connections.values()) { + connection.disconnect(); + } + this.connections.clear(); + } + + private async onKeyPairChange(encodedKeyPair: EncodedBlahKeyPair) { + this.keypair = await BlahKeyPair.fromEncoded(encodedKeyPair); + for (const connection of this.connections.values()) { + connection.changeKeyPair(this.keypair); + } + } + + private async onChatServersChange(newChatServers: string[]) { + // Disconnect from chat servers that are no longer in the list + for (const [endpoint, connection] of this.connections.entries()) { + if (!newChatServers.includes(endpoint)) { + connection.disconnect(); + this.connections.delete(endpoint); + } + } + + // Connect to chat servers that are in the list but not yet connected + for (const endpoint of newChatServers) { + if (!this.connections.has(endpoint)) { + const connection = new BlahChatServerConnection(endpoint, this.keypair); + this.connections.set(endpoint, connection); + connection.connect(); + } + } + } + + getConnection(endpoint: string): BlahChatServerConnection | null { + return this.connections.get(endpoint) ?? null; + } +} + +export const chatServerConnectionPool = new ChatServerConnectionPool(); diff --git a/src/routes/(app)/chats/[chatId]/+page.svelte b/src/routes/(app)/chats/[chatId]/+page.svelte deleted file mode 100644 index fc6f94c..0000000 --- a/src/routes/(app)/chats/[chatId]/+page.svelte +++ /dev/null @@ -1,68 +0,0 @@ - - -
- - - - - -
diff --git a/src/routes/(app)/chats/[server]/[chatId]/+page.svelte b/src/routes/(app)/chats/[server]/[chatId]/+page.svelte new file mode 100644 index 0000000..6a3ff89 --- /dev/null +++ b/src/routes/(app)/chats/[server]/[chatId]/+page.svelte @@ -0,0 +1,45 @@ + + +
+ {#if server} + {@const { info, messages, sendMessage } = server.chat(roomId)} + + {:else} + + To view this chat, you need to connect to chat server + {serverEndpoint}. + + {/if} +
diff --git a/src/routes/(app)/chats/[chatId]/ChatHeader.svelte b/src/routes/(app)/chats/[server]/[chatId]/ChatHeader.svelte similarity index 94% rename from src/routes/(app)/chats/[chatId]/ChatHeader.svelte rename to src/routes/(app)/chats/[server]/[chatId]/ChatHeader.svelte index 0cc31e0..98cd57b 100644 --- a/src/routes/(app)/chats/[chatId]/ChatHeader.svelte +++ b/src/routes/(app)/chats/[server]/[chatId]/ChatHeader.svelte @@ -4,7 +4,7 @@ import type { Chat } from '$lib/types'; import { AvatarBeam } from 'svelte-boring-avatars'; - export let chat: Chat; + export let info: Chat; export let outsideUnreadCount = 0; @@ -28,10 +28,10 @@ Back
-

{chat.name}

+

{info.name}

- +