110 lines
No EOL
3.5 KiB
TypeScript
110 lines
No EOL
3.5 KiB
TypeScript
import EventEmitter from "events";
|
|
import { WebSocket } from "ws";
|
|
import { MSAgentJoinMessage, MSAgentProtocolMessage, MSAgentProtocolMessageType, MSAgentTalkMessage } from '@msagent-chat/protocol';
|
|
import { MSAgentChatRoom } from "./room.js";
|
|
import * as htmlentities from 'html-entities';
|
|
import RateLimiter from "./ratelimiter.js";
|
|
|
|
// Event types
|
|
|
|
export interface Client {
|
|
on(event: 'join', listener: () => void): this;
|
|
on(event: 'close', listener: () => void): this;
|
|
on(event: 'talk', listener: (msg: string) => void): this;
|
|
|
|
on(event: string, listener: Function): this;
|
|
}
|
|
|
|
export class Client extends EventEmitter {
|
|
ip: string;
|
|
username: string | null;
|
|
agent: string | null;
|
|
|
|
room: MSAgentChatRoom;
|
|
socket: WebSocket;
|
|
|
|
chatRateLimit: RateLimiter
|
|
|
|
constructor(socket: WebSocket, room: MSAgentChatRoom, ip: string) {
|
|
super();
|
|
this.socket = socket;
|
|
this.ip = ip;
|
|
this.room = room;
|
|
this.username = null;
|
|
this.agent = null;
|
|
|
|
this.chatRateLimit = new RateLimiter(this.room.config.ratelimits.chat);
|
|
|
|
this.socket.on('message', (msg, isBinary) => {
|
|
if (isBinary) {
|
|
this.socket.close();
|
|
return;
|
|
}
|
|
this.parseMessage(msg.toString("utf-8"));
|
|
});
|
|
this.socket.on('error', () => {});
|
|
this.socket.on('close', () => {
|
|
this.emit('close');
|
|
});
|
|
}
|
|
|
|
send(msg: MSAgentProtocolMessage) {
|
|
return new Promise<void>((res, rej) => {
|
|
if (this.socket.readyState !== WebSocket.OPEN) {
|
|
res();
|
|
return;
|
|
}
|
|
this.socket.send(JSON.stringify(msg), err => {
|
|
if (err) {
|
|
rej(err);
|
|
return;
|
|
}
|
|
res();
|
|
});
|
|
});
|
|
}
|
|
|
|
private parseMessage(data: string) {
|
|
let msg: MSAgentProtocolMessage;
|
|
try {
|
|
msg = JSON.parse(data);
|
|
} catch {
|
|
this.socket.close();
|
|
return;
|
|
}
|
|
switch (msg.op) {
|
|
case MSAgentProtocolMessageType.Join: {
|
|
let joinMsg = msg as MSAgentJoinMessage;
|
|
if (!joinMsg.data || !joinMsg.data.username || !joinMsg.data.username) {
|
|
this.socket.close();
|
|
return;
|
|
}
|
|
let username = htmlentities.encode(joinMsg.data.username);
|
|
if (this.room.clients.some(u => u.username === username)) {
|
|
let i = 1;
|
|
let uo = username;
|
|
do {
|
|
username = uo + i++;
|
|
} while (this.room.clients.some(u => u.username === username))
|
|
}
|
|
if (!this.room.agents.some(a => a.filename === joinMsg.data.agent)) {
|
|
this.socket.close();
|
|
return;
|
|
}
|
|
this.username = username;
|
|
this.agent = joinMsg.data.agent;
|
|
this.emit('join');
|
|
break;
|
|
}
|
|
case MSAgentProtocolMessageType.Talk: {
|
|
let talkMsg = msg as MSAgentTalkMessage;
|
|
if (!talkMsg.data || !talkMsg.data.msg || !this.chatRateLimit.request()) {
|
|
return;
|
|
}
|
|
if (talkMsg.data.msg.length > this.room.config.charlimit) return;
|
|
this.emit('talk', talkMsg.data.msg);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} |