Handle message updates via websockets for current channel

main
Talon 2024-08-24 18:13:18 +02:00
parent d9a7282929
commit 7dbb36ddb0
6 changed files with 126 additions and 13 deletions

View File

@ -3,27 +3,27 @@ import { WebSocket } from "ws";
export const attachEvents = (ws: WebSocket) => {
events.on('file-uploaded', (id, channelId, messageId, filePath, fileType, fileSize, originalName) => {
ws.send(JSON.stringify({ type: 'file-uploaded', id, channelId, messageId, filePath, fileType, fileSize, originalName }));
ws.send(JSON.stringify({ type: 'file-uploaded', data: {id, channelId, messageId, filePath, fileType, fileSize, originalName }}));
});
events.on('message-created', (id, channelId, content) => {
ws.send(JSON.stringify({ type: 'message-created', id, channelId, content }));
ws.send(JSON.stringify({ type: 'message-created', data: {id, channelId, content }}));
});
events.on('message-updated', (id, content) => {
ws.send(JSON.stringify({ type: 'message-updated', id, content }));
ws.send(JSON.stringify({ type: 'message-updated', data: {id, content }}));
});
events.on('message-deleted', (id) => {
ws.send(JSON.stringify({ type: 'message-deleted', id }));
ws.send(JSON.stringify({ type: 'message-deleted', data: {id }}));
});
events.on('channel-created', (channel) => {
ws.send(JSON.stringify({ type: 'channel-created', channel }));
ws.send(JSON.stringify({ type: 'channel-created', data: {channel }}));
});
events.on('channel-deleted', (id) => {
ws.send(JSON.stringify({ type: 'channel-deleted', id }));
ws.send(JSON.stringify({ type: 'channel-deleted', data: {id} }));
});
events.on('channel-merged', (channelId, targetChannelId) => {
ws.send(JSON.stringify({ type: 'channel-merged', channelId, targetChannelId }));
ws.send(JSON.stringify({ type: 'channel-merged', data: {channelId, targetChannelId }}));
});
events.on('channel-updated', (id, name) => {
ws.send(JSON.stringify({ type: 'channel-updated', id, name }));
ws.send(JSON.stringify({ type: 'channel-updated', data: {id, name }}));
});
}

View File

@ -0,0 +1,28 @@
export type MessageCreated = {
channelId: string,
id: string,
content: string,
};
export type MessageDeleted = {
channelId: string,
messageId: string,
};
export type MessageUpdated = {
id: string,
content: string,
};
export type ChannelCreated = {
name: string,
};
export type ChannelDeleted = {
channelId: string,
};
export type ChannelUpdated = {
channelId: string,
name: string,
};

View File

@ -0,0 +1,60 @@
export type Message<T> = {
type: string,
data?: T,
};
export type MessageHandler<T> = (message: Message<T>) => void;
export class MessagingSystem {
private handlers: Record<string, MessageHandler<any>[]> = {};
public registerHandler<T>(type: string, handler: MessageHandler<T>): void {
if (!this.handlers[type]) {
this.handlers[type] = [];
}
if (!this.handlers[type].includes(handler)) {
this.handlers[type].push(handler);
}
}
public unregisterHandler<T>(type: string, handler: MessageHandler<T>): void {
if (this.handlers[type]) {
this.handlers[type] = this.handlers[type].filter(h => h !== handler);
}
}
public registerHandlerOnce<T>(type: string, handler: MessageHandler<T>): void {
const wrappedHandler = (message: Message<T>) => {
handler(message);
this.unregisterHandler(type, wrappedHandler);
};
this.registerHandler(type, wrappedHandler);
}
public waitForMessage<T>(type: string, timeout?: number): Promise<T> {
return new Promise((resolve, reject) => {
const handler = (message: Message<T>) => {
if (timer) clearTimeout(timer);
resolve(message.data!);
this.unregisterHandler(type, handler);
};
this.registerHandler(type, handler);
let timer: ReturnType<typeof setTimeout> | undefined;
if (timeout) {
timer = setTimeout(() => {
this.unregisterHandler(type, handler);
reject(new Error(`Timeout waiting for message of type '${type}'`));
}, timeout);
}
});
}
public sendMessage<T>(message: Message<T>): void {
const handlers = this.handlers[message.type];
if (handlers) {
handlers.forEach(handler => handler(message));
}
}
}

View File

@ -1,3 +1,4 @@
import { MessagingSystem } from "./events/messaging-system";
import { IChannel, Channel } from "./model/channel";
import { IChannelList, ChannelList } from "./model/channel-list";
import { IState } from "./model/state";
@ -12,11 +13,13 @@ export class State implements IState {
unsentMessages!: IUnsentMessage[];
currentChannel!: Channel | null;
defaultChannelId!: number;
public events: MessagingSystem;
constructor() {
this.token = "";
this.channelList = new ChannelList();
this.unsentMessages = [];
this.events = new MessagingSystem();
}
public getToken(): string {
@ -45,7 +48,7 @@ export class State implements IState {
public async save(): Promise<void> {
// stringify everything here except the currentChannel object.
const { currentChannel, ...state } = this;
const { currentChannel, events, ...state } = this;
await set("notebrook", state);
}

View File

@ -7,6 +7,7 @@ import { RecordAudioDialog } from "../dialogs/record-audio";
import { SearchDialog } from "../dialogs/search";
import { SettingsDialog } from "../dialogs/settings";
import { TakePhotoDialog } from "../dialogs/take-photo";
import { MessageUpdated } from "../events/message-events";
import { Channel } from "../model/channel";
import { IMessage, Message } from "../model/message";
import { UnsentMessage } from "../model/unsent-message";
@ -52,6 +53,24 @@ export class MainView extends View {
this.updateVisibleMessageShownTimestamps();
}, 1000);
setTimeout(() => this.attemptToSendUnsentMessages(), 2000);
state.events.registerHandler<MessageUpdated>("message-updated", (message) => {
const { data } = message;
if (!data) return;
const channel = state.currentChannel;
if (!channel) return;
const existing = channel.getMessage(parseInt(data!.id));
if (!existing) {
return;
} else {
existing.content = data.content;
state.save();
const renderedMessage = this.messageElementMap.get(existing.id);
if (renderedMessage) {
(renderedMessage as ListItem).setText(`${existing.content}; ${this.convertIsoTimeStringToRelative(existing.createdAt)}`);
}
}
});
}
@ -257,7 +276,7 @@ export class MainView extends View {
private renderMessage(message: IMessage): UINode {
const itm = new ListItem(`${message.content}; ${this.convertIsoTimeStringToRelative(message.createdAt)}`);
itm.setUserData(message);
itm.setUserData(message.id);
itm.onClick(() => {
new MessageDialog(message).open();
})
@ -430,7 +449,8 @@ export class MainView extends View {
for (let i = lowerIndex; i < upperIndex; i++) {
const child = this.messageList.children[i];
if (!child) break;
const message = child.getUserData() as IMessage;
const messageId = child.getUserData() as number;
const message = state.currentChannel?.getMessage(messageId);
if (message) {
(child as ListItem).setText(`${message.content}; ${this.convertIsoTimeStringToRelative(message.createdAt)}`);
}

View File

@ -1,4 +1,5 @@
import { API } from "./api";
import { state } from "./state";
export const connectToWebsocket = () => {
const ws = new WebSocket(`ws://localhost:3000`);
@ -7,6 +8,7 @@ export const connectToWebsocket = () => {
}
ws.onmessage = (data) => {
const message = JSON.parse(data.data.toString());
state.events.sendMessage(message);
console.log(message);
}
ws.onclose= () => {