mirror of
https://github.com/Blah-IM/Weblah.git
synced 2025-08-21 03:22:40 +00:00
feat: chat list (joined)
This commit is contained in:
parent
48c5ed4687
commit
9e899bbb27
10 changed files with 162 additions and 126 deletions
|
@ -95,6 +95,23 @@ export class BlahChatServerConnection {
|
|||
await this.apiCall('POST', `/room/${id}/admin`, payload);
|
||||
}
|
||||
|
||||
private async fetchRoomList(filter: 'joined' | 'public'): Promise<BlahRoomInfo[]> {
|
||||
const { rooms }: { rooms: BlahRoomInfo[] } = await this.apiCall(
|
||||
'GET',
|
||||
`/room?filter=${filter}`
|
||||
);
|
||||
return rooms;
|
||||
}
|
||||
|
||||
async fetchJoinedRooms(): Promise<BlahRoomInfo[]> {
|
||||
if (!this.keypair) return [];
|
||||
return await this.fetchRoomList('joined');
|
||||
}
|
||||
|
||||
async discoverRooms(): Promise<BlahRoomInfo[]> {
|
||||
return await this.fetchRoomList('public');
|
||||
}
|
||||
|
||||
async sendMessage(room: string, message: BlahRichText): Promise<void> {
|
||||
if (!this.keypair) throw new Error('Must send message with a keypair');
|
||||
const payload: BlahMessage = { room, rich_text: message, typ: 'chat' };
|
||||
|
@ -153,7 +170,7 @@ export class BlahChatServerConnection {
|
|||
}
|
||||
|
||||
connect() {
|
||||
if (!this.webSocket) this.webSocket = this.createWebSocket();
|
||||
if (!this.webSocket && this.keypair) this.webSocket = this.createWebSocket();
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
|
@ -174,8 +191,6 @@ export class BlahChatServerConnection {
|
|||
roomId: string,
|
||||
onNewMessage: (message: BlahSignedPayload<BlahMessage>) => void
|
||||
): { unsubscribe: () => void } {
|
||||
if (!this.webSocket) throw new Error('Must connect to WebSocket before subscribing to rooms');
|
||||
|
||||
const listeners = this.roomListeners.get(roomId) ?? new Set();
|
||||
listeners.add(onNewMessage);
|
||||
this.roomListeners.set(roomId, listeners);
|
||||
|
@ -194,9 +209,6 @@ export class BlahChatServerConnection {
|
|||
subscribe(onNewMessage: (message: BlahSignedPayload<BlahMessage>) => void): {
|
||||
unsubscribe: () => void;
|
||||
} {
|
||||
if (!this.webSocket)
|
||||
throw new Error('Must connect to WebSocket before subscribing to messages');
|
||||
|
||||
this.serverListeners.add(onNewMessage);
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
import type { BlahSignedPayload } from '../crypto';
|
||||
import type { BlahMessage } from './message';
|
||||
|
||||
export type BlahRoomInfo = {
|
||||
ruuid: string;
|
||||
title: string;
|
||||
last_chat?: BlahSignedPayload<BlahMessage>;
|
||||
};
|
||||
|
|
68
src/lib/chatList.ts
Normal file
68
src/lib/chatList.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
import { readable, writable, type Readable, type Writable } from 'svelte/store';
|
||||
import { chatFromBlah, type Chat } from './types';
|
||||
import type { BlahMessage, BlahRoomInfo } from './blah/structures';
|
||||
import type { BlahSignedPayload } from './blah/crypto';
|
||||
|
||||
export class ChatListManager {
|
||||
chatList: Writable<Chat[]>;
|
||||
|
||||
constructor() {
|
||||
this.chatList = writable<Chat[]>([]);
|
||||
}
|
||||
|
||||
private sortChats(chatList: Chat[]) {
|
||||
chatList.sort(
|
||||
(a, b) =>
|
||||
(b.lastMessage?.date ?? new Date(1970, 0, 1)).getTime() ??
|
||||
-(a.lastMessage?.date ?? new Date(1970, 0, 1)).getTime()
|
||||
);
|
||||
}
|
||||
|
||||
ingestChats(chats: BlahRoomInfo[], serverEndpoint: string) {
|
||||
this.chatList.update((chatList) => {
|
||||
for (const chat of chats) {
|
||||
const newChat = chatFromBlah(chat, serverEndpoint);
|
||||
|
||||
const existing = chatList.find((c) => c.id === chat.ruuid);
|
||||
if (existing) {
|
||||
existing.name = newChat.name;
|
||||
existing.lastMessage = newChat.lastMessage ?? existing.lastMessage;
|
||||
} else {
|
||||
chatList.push(newChat);
|
||||
console.log('new chat added to list', newChat);
|
||||
}
|
||||
}
|
||||
|
||||
this.sortChats(chatList);
|
||||
return chatList;
|
||||
});
|
||||
}
|
||||
|
||||
ingestMessage(message: BlahSignedPayload<BlahMessage>, serverEndpoint: string) {
|
||||
this.chatList.update((chatList) => {
|
||||
const chat = chatList.find((c) => c.id === message.signee.payload.room);
|
||||
if (chat) {
|
||||
const newChat = chatFromBlah(
|
||||
{ ruuid: chat.id, title: chat.name, last_chat: message },
|
||||
serverEndpoint
|
||||
);
|
||||
chat.lastMessage = newChat.lastMessage ?? chat.lastMessage;
|
||||
}
|
||||
this.sortChats(chatList);
|
||||
return chatList;
|
||||
});
|
||||
}
|
||||
|
||||
leaveChatServers(serverEndpoints: string[]) {
|
||||
this.chatList.update((chatList) => {
|
||||
return chatList.filter((chat) => serverEndpoints.includes(chat.server));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function useChatList(manager: ChatListManager): Readable<Chat[]> {
|
||||
return readable<Chat[]>([], (set) => {
|
||||
const unsubscribe = manager.chatList.subscribe(set);
|
||||
return unsubscribe;
|
||||
});
|
||||
}
|
|
@ -3,23 +3,44 @@ import { get } from 'svelte/store';
|
|||
import { BlahChatServerConnection } from './blah/connection/chatServer';
|
||||
import { BlahKeyPair, type EncodedBlahKeyPair } from './blah/crypto';
|
||||
import { currentKeyPair } from './keystore';
|
||||
import { ChatListManager } from './chatList';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export const chatServers = persisted<string[]>('weblah-chat-servers', ['https://blah.oxa.li/api']);
|
||||
|
||||
class ChatServerConnectionPool {
|
||||
private connections: Map<string, BlahChatServerConnection> = new Map();
|
||||
private keypair: BlahKeyPair | null = null;
|
||||
chatList: ChatListManager = new ChatListManager();
|
||||
|
||||
constructor() {
|
||||
chatServers.subscribe(this.onChatServersChange.bind(this));
|
||||
currentKeyPair.subscribe(this.onKeyPairChange.bind(this));
|
||||
if (browser) {
|
||||
chatServers.subscribe(this.onChatServersChange.bind(this));
|
||||
currentKeyPair.subscribe(this.onKeyPairChange.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
connectAll(keypair?: BlahKeyPair) {
|
||||
private createAndConnect(endpoint: string) {
|
||||
const connection = new BlahChatServerConnection(endpoint, this.keypair);
|
||||
this.connections.set(endpoint, connection);
|
||||
connection.connect();
|
||||
this.setupChatList(connection);
|
||||
}
|
||||
|
||||
private fetchJoinedRooms(connection: BlahChatServerConnection) {
|
||||
connection
|
||||
.fetchJoinedRooms()
|
||||
.then((rooms) => this.chatList.ingestChats(rooms, connection.endpoint));
|
||||
}
|
||||
|
||||
private setupChatList(connection: BlahChatServerConnection) {
|
||||
this.fetchJoinedRooms(connection);
|
||||
connection.subscribe((message) => this.chatList.ingestMessage(message, connection.endpoint));
|
||||
}
|
||||
|
||||
connectAll() {
|
||||
for (const endpoint of get(chatServers)) {
|
||||
const connection = new BlahChatServerConnection(endpoint, keypair);
|
||||
this.connections.set(endpoint, connection);
|
||||
connection.connect();
|
||||
this.createAndConnect(endpoint);
|
||||
}
|
||||
}
|
||||
disconnectAll() {
|
||||
|
@ -33,24 +54,27 @@ class ChatServerConnectionPool {
|
|||
this.keypair = await BlahKeyPair.fromEncoded(encodedKeyPair);
|
||||
for (const connection of this.connections.values()) {
|
||||
connection.changeKeyPair(this.keypair);
|
||||
connection.connect();
|
||||
this.fetchJoinedRooms(connection);
|
||||
}
|
||||
}
|
||||
|
||||
private async onChatServersChange(newChatServers: string[]) {
|
||||
// Disconnect from chat servers that are no longer in the list
|
||||
const disconnectedEndpoints: string[] = [];
|
||||
for (const [endpoint, connection] of this.connections.entries()) {
|
||||
if (!newChatServers.includes(endpoint)) {
|
||||
connection.disconnect();
|
||||
this.connections.delete(endpoint);
|
||||
disconnectedEndpoints.push(endpoint);
|
||||
}
|
||||
}
|
||||
this.chatList.leaveChatServers(disconnectedEndpoints);
|
||||
|
||||
// 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();
|
||||
this.createAndConnect(endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { Message } from './message';
|
||||
import type { BlahRoomInfo } from '$lib/blah/structures';
|
||||
import { messageFromBlah, type Message } from './message';
|
||||
|
||||
export type Chat = {
|
||||
server: string;
|
||||
|
@ -9,3 +10,13 @@ export type Chat = {
|
|||
lastMessage?: Message;
|
||||
unreadCount?: number;
|
||||
};
|
||||
|
||||
export function chatFromBlah(room: BlahRoomInfo, serverEndpoint: string): Chat {
|
||||
return {
|
||||
server: serverEndpoint,
|
||||
id: room.ruuid,
|
||||
name: room.title,
|
||||
type: 'group',
|
||||
lastMessage: room.last_chat ? messageFromBlah(room.last_chat) : undefined
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue