mirror of
https://github.com/Blah-IM/Weblah.git
synced 2025-05-01 08:41:08 +00:00
refactor: chatServerConnection.chat -> useChat
This commit is contained in:
parent
c65ff6c892
commit
48c5ed4687
3 changed files with 74 additions and 54 deletions
|
@ -1,7 +1,5 @@
|
||||||
import { version } from '$app/environment';
|
import { version } from '$app/environment';
|
||||||
import type { BlahRichText } from '$lib/richText';
|
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 { BlahKeyPair, BlahSignedPayload } from '../crypto';
|
||||||
import type { BlahAuth, BlahMessage, BlahRoomInfo, BlahUserJoinMessage } from '../structures';
|
import type { BlahAuth, BlahMessage, BlahRoomInfo, BlahUserJoinMessage } from '../structures';
|
||||||
import { BlahError } from './error';
|
import { BlahError } from './error';
|
||||||
|
@ -12,16 +10,21 @@ const RECONNECT_MAX_TRIES = 5;
|
||||||
export class BlahChatServerConnection {
|
export class BlahChatServerConnection {
|
||||||
private static commonHeaders = { 'x-blah-client': `Weblah/${version}` };
|
private static commonHeaders = { 'x-blah-client': `Weblah/${version}` };
|
||||||
|
|
||||||
private endpoint: string;
|
private endpoint_: string;
|
||||||
private keypair: BlahKeyPair | null;
|
private keypair: BlahKeyPair | null;
|
||||||
|
|
||||||
|
get endpoint() {
|
||||||
|
return this.endpoint_;
|
||||||
|
}
|
||||||
|
|
||||||
private webSocket: WebSocket | null = null;
|
private webSocket: WebSocket | null = null;
|
||||||
private messageListeners: Map<string, Set<(message: BlahSignedPayload<BlahMessage>) => void>> =
|
private roomListeners: Map<string, Set<(message: BlahSignedPayload<BlahMessage>) => void>> =
|
||||||
new Map();
|
new Map();
|
||||||
|
private serverListeners: Set<(message: BlahSignedPayload<BlahMessage>) => void> = new Set();
|
||||||
private webSocketRetryTimeout: number | null = null;
|
private webSocketRetryTimeout: number | null = null;
|
||||||
|
|
||||||
constructor(endpoint: string, keypair: BlahKeyPair | null = null) {
|
constructor(endpoint: string, keypair: BlahKeyPair | null = null) {
|
||||||
this.endpoint = endpoint;
|
this.endpoint_ = endpoint;
|
||||||
this.keypair = keypair;
|
this.keypair = keypair;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,14 +68,14 @@ export class BlahChatServerConnection {
|
||||||
let response: Response;
|
let response: Response;
|
||||||
if (method === 'GET') {
|
if (method === 'GET') {
|
||||||
if (this.keypair) {
|
if (this.keypair) {
|
||||||
response = await this.fetchWithAuthHeader(`${this.endpoint}${path}`);
|
response = await this.fetchWithAuthHeader(`${this.endpoint_}${path}`);
|
||||||
} else {
|
} else {
|
||||||
response = await fetch(`${this.endpoint}${path}`, {
|
response = await fetch(`${this.endpoint_}${path}`, {
|
||||||
headers: BlahChatServerConnection.commonHeaders
|
headers: BlahChatServerConnection.commonHeaders
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
response = await this.fetchWithSignedPayload(`${this.endpoint}${path}`, payload, { method });
|
response = await this.fetchWithSignedPayload(`${this.endpoint_}${path}`, payload, { method });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.ok) throw await BlahError.fromResponse(response);
|
if (!response.ok) throw await BlahError.fromResponse(response);
|
||||||
|
@ -112,7 +115,7 @@ export class BlahChatServerConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
private createWebSocket(remainingTries: number = RECONNECT_MAX_TRIES - 1): WebSocket {
|
private createWebSocket(remainingTries: number = RECONNECT_MAX_TRIES - 1): WebSocket {
|
||||||
const socket = new WebSocket(`${this.endpoint}/ws`);
|
const socket = new WebSocket(`${this.endpoint_}/ws`);
|
||||||
const onSocketClose = (e: Event) => {
|
const onSocketClose = (e: Event) => {
|
||||||
console.error('WebSocket error or closed:', e);
|
console.error('WebSocket error or closed:', e);
|
||||||
this.webSocket?.close();
|
this.webSocket?.close();
|
||||||
|
@ -139,7 +142,7 @@ export class BlahChatServerConnection {
|
||||||
|
|
||||||
if ('chat' in frameJson) {
|
if ('chat' in frameJson) {
|
||||||
const message = frameJson.chat;
|
const message = frameJson.chat;
|
||||||
const listeners = this.messageListeners.get(message.signee.payload.room);
|
const listeners = this.roomListeners.get(message.signee.payload.room);
|
||||||
if (listeners) for (const listener of listeners) listener(message);
|
if (listeners) for (const listener of listeners) listener(message);
|
||||||
} else {
|
} else {
|
||||||
console.log('Unknown WebSocket frame:', frameJson);
|
console.log('Unknown WebSocket frame:', frameJson);
|
||||||
|
@ -173,54 +176,33 @@ export class BlahChatServerConnection {
|
||||||
): { unsubscribe: () => void } {
|
): { unsubscribe: () => void } {
|
||||||
if (!this.webSocket) throw new Error('Must connect to WebSocket before subscribing to rooms');
|
if (!this.webSocket) throw new Error('Must connect to WebSocket before subscribing to rooms');
|
||||||
|
|
||||||
const listeners = this.messageListeners.get(roomId) ?? new Set();
|
const listeners = this.roomListeners.get(roomId) ?? new Set();
|
||||||
listeners.add(onNewMessage);
|
listeners.add(onNewMessage);
|
||||||
this.messageListeners.set(roomId, listeners);
|
this.roomListeners.set(roomId, listeners);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
unsubscribe: () => {
|
unsubscribe: () => {
|
||||||
const listeners = this.messageListeners.get(roomId) ?? new Set();
|
const listeners = this.roomListeners.get(roomId) ?? new Set();
|
||||||
listeners.delete(onNewMessage);
|
listeners.delete(onNewMessage);
|
||||||
if (listeners.size === 0) {
|
if (listeners.size === 0) {
|
||||||
this.messageListeners.delete(roomId);
|
this.roomListeners.delete(roomId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
chat(chatId: string): {
|
subscribe(onNewMessage: (message: BlahSignedPayload<BlahMessage>) => void): {
|
||||||
info: Readable<Chat>;
|
unsubscribe: () => void;
|
||||||
messages: Readable<Message[]>;
|
|
||||||
sendMessage: (brt: BlahRichText) => Promise<void>;
|
|
||||||
} {
|
} {
|
||||||
const info = readable<Chat>(
|
if (!this.webSocket)
|
||||||
{ server: this.endpoint, id: chatId, name: '', type: 'group' },
|
throw new Error('Must connect to WebSocket before subscribing to messages');
|
||||||
(set) => {
|
|
||||||
this.fetchRoomInfo(chatId).then((room) => {
|
this.serverListeners.add(onNewMessage);
|
||||||
set({ server: this.endpoint, id: chatId, name: room.title, type: 'group' });
|
|
||||||
});
|
return {
|
||||||
|
unsubscribe: () => {
|
||||||
|
this.serverListeners.delete(onNewMessage);
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
const messages = readable<Message[]>([], (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 };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
45
src/lib/chat.ts
Normal file
45
src/lib/chat.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { readable, type Readable } from 'svelte/store';
|
||||||
|
import type { BlahChatServerConnection } from './blah/connection/chatServer';
|
||||||
|
import type { BlahRichText } from './richText';
|
||||||
|
import { messageFromBlah, type Chat, type Message } from './types';
|
||||||
|
|
||||||
|
export function useChat(
|
||||||
|
server: BlahChatServerConnection,
|
||||||
|
chatId: string
|
||||||
|
): {
|
||||||
|
info: Readable<Chat>;
|
||||||
|
messages: Readable<Message[]>;
|
||||||
|
sendMessage: (brt: BlahRichText) => Promise<void>;
|
||||||
|
} {
|
||||||
|
const info = readable<Chat>(
|
||||||
|
{ server: server.endpoint, id: chatId, name: '', type: 'group' },
|
||||||
|
(set) => {
|
||||||
|
server.fetchRoomInfo(chatId).then((room) => {
|
||||||
|
set({ server: server.endpoint, id: chatId, name: room.title, type: 'group' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const messages = readable<Message[]>([], (set, update) => {
|
||||||
|
server
|
||||||
|
.fetchRoomHistory(chatId)
|
||||||
|
.then((history) =>
|
||||||
|
update((messages) => [
|
||||||
|
...history.map(messageFromBlah).toSorted((a, b) => a.date.getTime() - b.date.getTime()),
|
||||||
|
...messages
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
const { unsubscribe } = server.subscribeRoom(chatId, (message) => {
|
||||||
|
update((messages) => [...messages, messageFromBlah(message)]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return unsubscribe;
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendMessage = async (brt: BlahRichText) => {
|
||||||
|
await server.sendMessage(chatId, brt);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { info, messages, sendMessage };
|
||||||
|
}
|
|
@ -1,18 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import BgPattern from '$lib/components/BgPattern.svelte';
|
|
||||||
import { messageFromBlah, type Chat, type Message } from '$lib/types';
|
|
||||||
import { onDestroy } from 'svelte';
|
|
||||||
import ChatHeader from './ChatHeader.svelte';
|
|
||||||
import ChatHistory from './ChatHistory.svelte';
|
|
||||||
import ChatInput from './ChatInput.svelte';
|
|
||||||
import { BlahChatServerConnection } from '$lib/blah/connection/chatServer';
|
import { BlahChatServerConnection } from '$lib/blah/connection/chatServer';
|
||||||
import { currentKeyPair } from '$lib/keystore';
|
|
||||||
import { BlahKeyPair, type EncodedBlahKeyPair } from '$lib/blah/crypto';
|
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { chatServerConnectionPool } from '$lib/chatServers';
|
import { chatServerConnectionPool } from '$lib/chatServers';
|
||||||
import ServiceMessage from '$lib/components/ServiceMessage.svelte';
|
import ServiceMessage from '$lib/components/ServiceMessage.svelte';
|
||||||
import ChatPage from './ChatPage.svelte';
|
import ChatPage from './ChatPage.svelte';
|
||||||
|
import { useChat } from '$lib/chat';
|
||||||
|
|
||||||
$: roomId = $page.params.chatId;
|
$: roomId = $page.params.chatId;
|
||||||
|
|
||||||
|
@ -34,7 +27,7 @@
|
||||||
|
|
||||||
<div class="flex h-full w-full flex-col items-center justify-center">
|
<div class="flex h-full w-full flex-col items-center justify-center">
|
||||||
{#if server}
|
{#if server}
|
||||||
{@const { info, messages, sendMessage } = server.chat(roomId)}
|
{@const { info, messages, sendMessage } = useChat(server, roomId)}
|
||||||
<ChatPage {info} {messages} on:sendMessage={sendMessage} />
|
<ChatPage {info} {messages} on:sendMessage={sendMessage} />
|
||||||
{:else}
|
{:else}
|
||||||
<ServiceMessage>
|
<ServiceMessage>
|
||||||
|
|
Loading…
Add table
Reference in a new issue