chamesu
Version:
This package contains all packages of the chat application.
256 lines (207 loc) • 8.12 kB
text/typescript
/*
* Copyright (c) 2022.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/
import {Context} from "@nuxt/types";
import {Socket} from "socket.io-client";
import {SecurityKeyPair} from "~/modules/security";
import {decodeContactMessage, encodeContactMessage} from "~/domains/contact/encryption";
import {decodeChatRoomMessage, encodeChatRoomMessage} from "~/domains/chat-room/encryption";
export interface ChatRoom extends Record<string, any> {
id: string,
contactId: string | null,
[key: string]: any
}
export interface ChatRoomRegistration extends Record<string, any> {
subscriptions: number,
room: {
id: string,
contactId: string | null,
[key: string]: any
}
}
export class ChatModule {
protected ctx: Context;
private roomRegistrations: Map<string,ChatRoomRegistration>;
public socket: Socket;
constructor(ctx: Context) {
this.ctx = ctx;
this.roomRegistrations = new Map<string, ChatRoomRegistration>();
this.socket = this.ctx.$socket.use('/chat');
this.initSocketHandler();
this.subscribeStore();
}
//-------------------------------------------------------
async joinRoom(room: ChatRoom) {
return new Promise(((resolve, reject) => {
const connectionTimeout = setTimeout(() => {
return reject(new Error('Die Verbindung konnte nicht aufgebaut werden...'));
},10000);
return this.socket.emit('join', {
id: room.id,
type: 'chat-room'
}, (data) => {
clearTimeout(connectionTimeout);
if(data.success) {
return this.registerRoom(room).then(() => {
return resolve(data.data);
}).catch(reject);
} else {
return reject(new Error(data.message));
}
});
}));
}
async leaveRoom(roomId: string) {
return new Promise(((resolve, reject) => {
const connectionTimeout = setTimeout(() => {
return reject(new Error('Die Verbindung konnte nicht getrennt werden...'));
},10000);
return this.socket.emit('leave', {
id: roomId,
type: 'chat-room',
}, (data) => {
clearTimeout(connectionTimeout);
this.unregisterRoom(roomId);
this.ctx.store.dispatch('chat/dropRoomMemberOnline', roomId).then(r => r);
if(data.success) {
return resolve(data.success);
} else {
return reject(new Error(data.message));
}
});
}));
}
public getRegistration(roomId: string) : ChatRoomRegistration {
const room : undefined | ChatRoomRegistration = this.roomRegistrations.get(roomId);
if(typeof room === 'undefined') {
throw new Error('Der Raum #'+roomId+' wurde noch nicht registriert.');
}
return room;
}
private setRegistration(roomId: string, registration: ChatRoomRegistration) {
this.roomRegistrations.set(roomId, registration);
}
public async registerRoom(room: ChatRoom) {
if(this.roomRegistrations.has(room.id)) {
const registration = <ChatRoomRegistration> this.roomRegistrations.get(room.id);
registration.subscriptions++;
this.setRegistration(room.id, registration);
return;
}
const registration : ChatRoomRegistration = {
subscriptions: 1,
room: room
};
this.setRegistration(room.id, registration);
this.loadSockets(room.id).then(r => r);
//await this.initEncryption(room.id);
}
public unregisterRoom(roomId: string) {
if(this.roomRegistrations.has(roomId)) {
const room = <ChatRoomRegistration> this.roomRegistrations.get(roomId);
room.subscriptions--;
if(room.subscriptions === 0) {
this.roomRegistrations.delete(roomId);
} else {
this.setRegistration(roomId, room);
}
}
}
//-------------------------------------------------------
// Socket
//-------------------------------------------------------
private initSocketHandler() {
const handler = (event, data) => {
switch (event) {
case 'userOnline':
this.handleSocketUserOnlineEvent(data);
break;
case 'userOffline':
this.handleSocketUserOfflineEvent(data);
break;
case 'reconnect':
this.handleSocketReconnect();
break;
case 'connectionClose':
this.handleSocketConnectionCloseEvent(data);
break;
}
};
handler.bind(this);
this.socket.onAny(handler);
}
private async loadAllSockets() {
await this.roomRegistrations.forEach((async (value, key) => {
await this.loadSockets(key);
}))
}
private async loadSockets(roomId: string) {
return new Promise(((resolve, reject) => {
this.socket.emit('onlineUsers', {
id: roomId,
type: 'chat-room',
}, (response) => {
if(typeof response === 'object') {
if (response.success) {
const {data} = response.data;
const items : Record<string, any>[] = [];
for (let i = 0; i < data.length; i++) {
items.push({id: data[i].id, roomId: roomId});
}
this.ctx.store.dispatch('chat/setMembersOnline', items).then(r => r);
resolve(data);
} else {
reject(new Error());
}
}
});
}));
}
private handleSocketReconnect() {
this.loadAllSockets().then(r => r);
}
private handleSocketConnectionCloseEvent(data) {
this.ctx.store.dispatch('chat/dropRoomMemberOnline', data.id).then(r => r);
}
private handleSocketUserOnlineEvent(data) {
this.ctx.store.dispatch('chat/addMemberOnline', {roomId: data.meta.roomId, id: data.id}).then(r => r);
}
private handleSocketUserOfflineEvent(data) {
this.ctx.store.dispatch('chat/dropMemberOnline', {roomId: data.meta.roomId, id: data.id}).then(r => r);
}
//-------------------------------------------------------
// Security
//-------------------------------------------------------
encodeMessage(roomId: string, message: string) {
const registration = this.getRegistration(roomId);
if(registration.room.contactId) {
return encodeContactMessage(message, registration.room.contactId);
} else {
return encodeChatRoomMessage(message, registration.room.id);
}
}
decodeMessage(roomId: string, message: string) {
const registration = this.getRegistration(roomId);
if(registration.room.contactId) {
return decodeContactMessage(message, registration.room.contactId);
} else {
return decodeChatRoomMessage(message, registration.room.id);
}
}
//-------------------------------------------------------
private subscribeStore() {
this.ctx.store.subscribe((mutation: any) => {
switch (mutation.type) {
case 'auth/unsetToken':
this.ctx.$socket.reconnect('/chat');
break;
case 'auth/setToken':
this.ctx.$socket.reconnect('/chat');
break;
}
});
}
}