Handle message updates via websockets for current channel
parent
d9a7282929
commit
7dbb36ddb0
|
@ -3,27 +3,27 @@ import { WebSocket } from "ws";
|
||||||
|
|
||||||
export const attachEvents = (ws: WebSocket) => {
|
export const attachEvents = (ws: WebSocket) => {
|
||||||
events.on('file-uploaded', (id, channelId, messageId, filePath, fileType, fileSize, originalName) => {
|
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) => {
|
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) => {
|
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) => {
|
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) => {
|
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) => {
|
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) => {
|
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) => {
|
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 }}));
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -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,
|
||||||
|
};
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { MessagingSystem } from "./events/messaging-system";
|
||||||
import { IChannel, Channel } from "./model/channel";
|
import { IChannel, Channel } from "./model/channel";
|
||||||
import { IChannelList, ChannelList } from "./model/channel-list";
|
import { IChannelList, ChannelList } from "./model/channel-list";
|
||||||
import { IState } from "./model/state";
|
import { IState } from "./model/state";
|
||||||
|
@ -12,11 +13,13 @@ export class State implements IState {
|
||||||
unsentMessages!: IUnsentMessage[];
|
unsentMessages!: IUnsentMessage[];
|
||||||
currentChannel!: Channel | null;
|
currentChannel!: Channel | null;
|
||||||
defaultChannelId!: number;
|
defaultChannelId!: number;
|
||||||
|
public events: MessagingSystem;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.token = "";
|
this.token = "";
|
||||||
this.channelList = new ChannelList();
|
this.channelList = new ChannelList();
|
||||||
this.unsentMessages = [];
|
this.unsentMessages = [];
|
||||||
|
this.events = new MessagingSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getToken(): string {
|
public getToken(): string {
|
||||||
|
@ -45,7 +48,7 @@ export class State implements IState {
|
||||||
|
|
||||||
public async save(): Promise<void> {
|
public async save(): Promise<void> {
|
||||||
// stringify everything here except the currentChannel object.
|
// stringify everything here except the currentChannel object.
|
||||||
const { currentChannel, ...state } = this;
|
const { currentChannel, events, ...state } = this;
|
||||||
await set("notebrook", state);
|
await set("notebrook", state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { RecordAudioDialog } from "../dialogs/record-audio";
|
||||||
import { SearchDialog } from "../dialogs/search";
|
import { SearchDialog } from "../dialogs/search";
|
||||||
import { SettingsDialog } from "../dialogs/settings";
|
import { SettingsDialog } from "../dialogs/settings";
|
||||||
import { TakePhotoDialog } from "../dialogs/take-photo";
|
import { TakePhotoDialog } from "../dialogs/take-photo";
|
||||||
|
import { MessageUpdated } from "../events/message-events";
|
||||||
import { Channel } from "../model/channel";
|
import { Channel } from "../model/channel";
|
||||||
import { IMessage, Message } from "../model/message";
|
import { IMessage, Message } from "../model/message";
|
||||||
import { UnsentMessage } from "../model/unsent-message";
|
import { UnsentMessage } from "../model/unsent-message";
|
||||||
|
@ -52,6 +53,24 @@ export class MainView extends View {
|
||||||
this.updateVisibleMessageShownTimestamps();
|
this.updateVisibleMessageShownTimestamps();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
setTimeout(() => this.attemptToSendUnsentMessages(), 2000);
|
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 {
|
private renderMessage(message: IMessage): UINode {
|
||||||
const itm = new ListItem(`${message.content}; ${this.convertIsoTimeStringToRelative(message.createdAt)}`);
|
const itm = new ListItem(`${message.content}; ${this.convertIsoTimeStringToRelative(message.createdAt)}`);
|
||||||
itm.setUserData(message);
|
itm.setUserData(message.id);
|
||||||
itm.onClick(() => {
|
itm.onClick(() => {
|
||||||
new MessageDialog(message).open();
|
new MessageDialog(message).open();
|
||||||
})
|
})
|
||||||
|
@ -430,7 +449,8 @@ export class MainView extends View {
|
||||||
for (let i = lowerIndex; i < upperIndex; i++) {
|
for (let i = lowerIndex; i < upperIndex; i++) {
|
||||||
const child = this.messageList.children[i];
|
const child = this.messageList.children[i];
|
||||||
if (!child) break;
|
if (!child) break;
|
||||||
const message = child.getUserData() as IMessage;
|
const messageId = child.getUserData() as number;
|
||||||
|
const message = state.currentChannel?.getMessage(messageId);
|
||||||
if (message) {
|
if (message) {
|
||||||
(child as ListItem).setText(`${message.content}; ${this.convertIsoTimeStringToRelative(message.createdAt)}`);
|
(child as ListItem).setText(`${message.content}; ${this.convertIsoTimeStringToRelative(message.createdAt)}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { API } from "./api";
|
import { API } from "./api";
|
||||||
|
import { state } from "./state";
|
||||||
|
|
||||||
export const connectToWebsocket = () => {
|
export const connectToWebsocket = () => {
|
||||||
const ws = new WebSocket(`ws://localhost:3000`);
|
const ws = new WebSocket(`ws://localhost:3000`);
|
||||||
|
@ -7,6 +8,7 @@ export const connectToWebsocket = () => {
|
||||||
}
|
}
|
||||||
ws.onmessage = (data) => {
|
ws.onmessage = (data) => {
|
||||||
const message = JSON.parse(data.data.toString());
|
const message = JSON.parse(data.data.toString());
|
||||||
|
state.events.sendMessage(message);
|
||||||
console.log(message);
|
console.log(message);
|
||||||
}
|
}
|
||||||
ws.onclose= () => {
|
ws.onclose= () => {
|
||||||
|
|
Loading…
Reference in New Issue