use EventEmitter for websockets

This commit is contained in:
HF 2021-07-10 15:51:15 +02:00
parent 21c99f7447
commit 566f91c690
13 changed files with 214 additions and 258 deletions

View File

@ -3,12 +3,12 @@ import { Op } from 'sequelize';
import logger from './logger'; import logger from './logger';
import redis from '../data/redis'; import redis from '../data/redis';
import User from '../data/models/User'; import User from '../data/models/User';
import webSockets from '../socket/websockets';
import RateLimiter from '../utils/RateLimiter'; import RateLimiter from '../utils/RateLimiter';
import { import {
Channel, RegUser, UserChannel, Message, Channel, RegUser, UserChannel, Message,
} from '../data/models'; } from '../data/models';
import ChatMessageBuffer from './ChatMessageBuffer'; import ChatMessageBuffer from './ChatMessageBuffer';
import socketEvents from '../socket/SocketEvents';
import { cheapDetector } from './isProxy'; import { cheapDetector } from './isProxy';
import { DailyCron } from '../utils/cron'; import { DailyCron } from '../utils/cron';
import ttags from './ttag'; import ttags from './ttag';
@ -51,6 +51,18 @@ export class ChatProvider {
this.mutedCountries = []; this.mutedCountries = [];
this.chatMessageBuffer = new ChatMessageBuffer(); this.chatMessageBuffer = new ChatMessageBuffer();
this.clearOldMessages = this.clearOldMessages.bind(this); this.clearOldMessages = this.clearOldMessages.bind(this);
socketEvents.on('recvChatMessage', (user, message, channelId) => {
const errorMsg = this.sendMessage(user, message, channelId);
if (errorMsg) {
this.broadcastChatMessage(
'info',
errorMsg,
channelId,
this.infoUserId,
);
}
});
} }
async clearOldMessages() { async clearOldMessages() {
@ -174,7 +186,7 @@ export class ChatProvider {
}); });
if (created) { if (created) {
webSockets.broadcastAddChatChannel( socketEvents.broadcastAddChatChannel(
userId, userId,
channelId, channelId,
channelArray, channelArray,
@ -430,7 +442,7 @@ export class ChatProvider {
id, id,
country, country,
); );
webSockets.broadcastChatMessage( socketEvents.broadcastChatMessage(
name, name,
message, message,
channelId, channelId,
@ -497,5 +509,4 @@ export class ChatProvider {
} }
} }
const chatProvider = new ChatProvider(); export default new ChatProvider();
export default chatProvider;

View File

@ -4,7 +4,7 @@
* @flow * @flow
*/ */
import webSockets from '../socket/websockets'; import socketEvents from '../socket/SocketEvents';
class PixelCache { class PixelCache {
PXL_CACHE: Map<number, Buffer>; PXL_CACHE: Map<number, Buffer>;
@ -56,7 +56,7 @@ class PixelCache {
cache.forEach((pxls, chunkCanvasId) => { cache.forEach((pxls, chunkCanvasId) => {
const canvasId = (chunkCanvasId & 0xFF0000) >> 16; const canvasId = (chunkCanvasId & 0xFF0000) >> 16;
const chunkId = chunkCanvasId & 0x00FFFF; const chunkId = chunkCanvasId & 0x00FFFF;
webSockets.broadcastPixels(canvasId, chunkId, pxls); socketEvents.broadcastPixels(canvasId, chunkId, pxls);
}); });
} }
} }

View File

@ -6,8 +6,7 @@
* *
* @flow * @flow
*/ */
import webSockets from '../socket/websockets'; import socketEvents from '../socket/SocketEvents';
import WebSocketEvents from '../socket/WebSocketEvents';
import PixelUpdate from '../socket/packets/PixelUpdateServer'; import PixelUpdate from '../socket/packets/PixelUpdateServer';
import { setPixelByOffset } from './setPixel'; import { setPixelByOffset } from './setPixel';
import { TILE_SIZE } from './constants'; import { TILE_SIZE } from './constants';
@ -19,7 +18,7 @@ const TARGET_RADIUS = 62;
const EVENT_DURATION_MIN = 10; const EVENT_DURATION_MIN = 10;
// const EVENT_DURATION_MIN = 1; // const EVENT_DURATION_MIN = 1;
class Void extends WebSocketEvents { class Void {
i: number; i: number;
j: number; j: number;
maxClr: number; maxClr: number;
@ -33,7 +32,6 @@ class Void extends WebSocketEvents {
ended: boolean; ended: boolean;
constructor(centerCell) { constructor(centerCell) {
super();
// chunk coordinates // chunk coordinates
const [i, j] = centerCell; const [i, j] = centerCell;
this.i = i; this.i = i;
@ -41,7 +39,7 @@ class Void extends WebSocketEvents {
this.ended = false; this.ended = false;
this.maxClr = canvases[CANVAS_ID].colors.length; this.maxClr = canvases[CANVAS_ID].colors.length;
const area = TARGET_RADIUS ** 2 * Math.PI; const area = TARGET_RADIUS ** 2 * Math.PI;
const online = webSockets.onlineCounter; const online = socketEvents.onlineCounter;
// require an average of 0.25 px / min / user // require an average of 0.25 px / min / user
const requiredSpeed = Math.floor(online / 1.8); const requiredSpeed = Math.floor(online / 1.8);
const ppm = Math.ceil(area / EVENT_DURATION_MIN + requiredSpeed); const ppm = Math.ceil(area / EVENT_DURATION_MIN + requiredSpeed);
@ -60,7 +58,7 @@ class Void extends WebSocketEvents {
this.cancel = this.cancel.bind(this); this.cancel = this.cancel.bind(this);
this.checkStatus = this.checkStatus.bind(this); this.checkStatus = this.checkStatus.bind(this);
this.broadcastPixelBuffer = this.broadcastPixelBuffer.bind(this); this.broadcastPixelBuffer = this.broadcastPixelBuffer.bind(this);
webSockets.addListener(this); socketEvents.addListener('pixelUpdate', this.broadcastPixelBuffer);
this.voidLoop(); this.voidLoop();
} }
@ -162,13 +160,13 @@ class Void extends WebSocketEvents {
} }
cancel() { cancel() {
webSockets.remListener(this); socketEvents.removeListener('pixelUpdate', this.broadcastPixelBuffer);
this.ended = true; this.ended = true;
} }
checkStatus() { checkStatus() {
if (this.ended) { if (this.ended) {
webSockets.remListener(this); socketEvents.removeListener('pixelUpdate', this.broadcastPixelBuffer);
return 100; return 100;
} }
return Math.floor(this.curRadius * 100 / TARGET_RADIUS); return Math.floor(this.curRadius * 100 / TARGET_RADIUS);

View File

@ -6,7 +6,7 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import webSockets from '../../../socket/websockets'; import socketEvents from '../../../socket/SocketEvents';
import { RegUser } from '../../../data/models'; import { RegUser } from '../../../data/models';
import { validateName } from '../../../utils/validation'; import { validateName } from '../../../utils/validation';
@ -46,7 +46,7 @@ export default async (req: Request, res: Response) => {
await user.regUser.update({ name }); await user.regUser.update({ name });
webSockets.reloadUser(oldname); socketEvents.reloadUser(oldname);
res.json({ res.json({
success: true, success: true,

View File

@ -5,7 +5,7 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import webSockets from '../../../socket/websockets'; import socketEvents from '../../../socket/SocketEvents';
import getHtml from '../../../ssr-components/RedirectionPage'; import getHtml from '../../../ssr-components/RedirectionPage';
import { getHostFromRequest } from '../../../utils/ip'; import { getHostFromRequest } from '../../../utils/ip';
import mailProvider from '../../../core/mail'; import mailProvider from '../../../core/mail';
@ -19,7 +19,7 @@ export default async (req: Request, res: Response) => {
if (name) { if (name) {
// notify websoecket to reconnect user // notify websoecket to reconnect user
// thats a bit counter productive because it directly links to the websocket // thats a bit counter productive because it directly links to the websocket
webSockets.reloadUser(name); socketEvents.reloadUser(name);
// --- // ---
const index = getHtml( const index = getHtml(
t`Mail verification`, t`Mail verification`,

View File

@ -8,7 +8,7 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import logger from '../../core/logger'; import logger from '../../core/logger';
import webSockets from '../../socket/websockets'; import socketEvents from '../../socket/SocketEvents';
import { RegUser, UserBlock, Channel } from '../../data/models'; import { RegUser, UserBlock, Channel } from '../../data/models';
async function block(req: Request, res: Response) { async function block(req: Request, res: Response) {
@ -108,8 +108,8 @@ async function block(req: Request, res: Response) {
if (channel) { if (channel) {
const channelId = channel.id; const channelId = channel.id;
channel.destroy(); channel.destroy();
webSockets.broadcastRemoveChatChannel(user.id, channelId); socketEvents.broadcastRemoveChatChannel(user.id, channelId);
webSockets.broadcastRemoveChatChannel(userId, channelId); socketEvents.broadcastRemoveChatChannel(userId, channelId);
} }
if (ret) { if (ret) {

View File

@ -8,7 +8,7 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import logger from '../../core/logger'; import logger from '../../core/logger';
import webSockets from '../../socket/websockets'; import socketEvents from '../../socket/SocketEvents';
async function blockdm(req: Request, res: Response) { async function blockdm(req: Request, res: Response) {
const { block } = req.body; const { block } = req.body;
@ -47,8 +47,8 @@ async function blockdm(req: Request, res: Response) {
const channelId = channel.id; const channelId = channel.id;
const { dmu1id, dmu2id } = channel; const { dmu1id, dmu2id } = channel;
channel.destroy(); channel.destroy();
webSockets.broadcastRemoveChatChannel(dmu1id, channelId); socketEvents.broadcastRemoveChatChannel(dmu1id, channelId);
webSockets.broadcastRemoveChatChannel(dmu2id, channelId); socketEvents.broadcastRemoveChatChannel(dmu2id, channelId);
} }
} }

View File

@ -8,7 +8,7 @@
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import logger from '../../core/logger'; import logger from '../../core/logger';
import webSockets from '../../socket/websockets'; import socketEvents from '../../socket/SocketEvents';
async function leaveChan(req: Request, res: Response) { async function leaveChan(req: Request, res: Response) {
const channelId = parseInt(req.body.channelId, 10); const channelId = parseInt(req.body.channelId, 10);
@ -65,7 +65,7 @@ async function leaveChan(req: Request, res: Response) {
user.regUser.removeChannel(channel); user.regUser.removeChannel(channel);
webSockets.broadcastRemoveChatChannel(user.id, channelId); socketEvents.broadcastRemoveChatChannel(user.id, channelId);
res.json({ res.json({
status: 'ok', status: 'ok',

View File

@ -10,14 +10,11 @@
import WebSocket from 'ws'; import WebSocket from 'ws';
import socketEvents from './SocketEvents';
import WebSocketEvents from './WebSocketEvents';
import webSockets from './websockets';
import { getIPFromRequest } from '../utils/ip'; import { getIPFromRequest } from '../utils/ip';
import { setPixelByCoords } from '../core/setPixel'; import { setPixelByCoords } from '../core/setPixel';
import logger from '../core/logger'; import logger from '../core/logger';
import { APISOCKET_KEY } from '../core/config'; import { APISOCKET_KEY } from '../core/config';
import chatProvider from '../core/ChatProvider';
function heartbeat() { function heartbeat() {
this.isAlive = true; this.isAlive = true;
@ -38,13 +35,11 @@ async function verifyClient(info, done) {
} }
class APISocketServer extends WebSocketEvents { class APISocketServer {
wss: WebSocket.Server; wss: WebSocket.Server;
constructor() { constructor() {
super();
logger.info('Starting API websocket server'); logger.info('Starting API websocket server');
webSockets.addListener(this);
const wss = new WebSocket.Server({ const wss = new WebSocket.Server({
perMessageDeflate: false, perMessageDeflate: false,
@ -75,7 +70,15 @@ class APISocketServer extends WebSocketEvents {
}); });
}); });
this.broadcast = this.broadcast.bind(this);
this.broadcastPixelBuffer = this.broadcastPixelBuffer.bind(this);
this.ping = this.ping.bind(this); this.ping = this.ping.bind(this);
this.broadcastChatMessage = this.broadcastChatMessage.bind(this);
socketEvents.on('broadcast', this.broadcast);
socketEvents.on('pixelUpdate', this.broadcastPixelBuffer);
socketEvents.on('chatMessage', this.broadcastChatMessage);
setInterval(this.ping, 45 * 1000); setInterval(this.ping, 45 * 1000);
} }
@ -100,26 +103,34 @@ class APISocketServer extends WebSocketEvents {
}); });
} }
broadcastOnlineCounter(buffer) { broadcast(data) {
const frame = WebSocket.Sender.frame(buffer, { if (typeof data === 'string') {
readOnly: true, this.wss.clients.forEach((ws) => {
mask: false, if (ws.readyState === WebSocket.OPEN) {
rsv1: false, ws.send(data);
opcode: 2, }
fin: true, });
}); } else {
this.wss.clients.forEach((client) => { const frame = WebSocket.Sender.frame(data, {
if (client.subOnline && client.readyState === WebSocket.OPEN) { readOnly: true,
frame.forEach((data) => { mask: false,
try { rsv1: false,
// eslint-disable-next-line no-underscore-dangle opcode: 2,
client._socket.write(data); fin: true,
} catch (error) { });
logger.error('(!) Catched error on write apisocket:', error); this.wss.clients.forEach((ws) => {
} if (ws.readyState === WebSocket.OPEN) {
}); frame.forEach((buffer) => {
} try {
}); // eslint-disable-next-line no-underscore-dangle
ws._socket.write(buffer);
} catch (error) {
logger.error(`WebSocket broadcast error: ${error.message}`);
}
});
}
});
}
} }
broadcastPixelBuffer(canvasId, chunkid, buffer) { broadcastPixelBuffer(canvasId, chunkid, buffer) {
@ -181,11 +192,17 @@ class APISocketServer extends WebSocketEvents {
logger.info(`APISocket message ${message}`); logger.info(`APISocket message ${message}`);
if (command === 'chat') { if (command === 'chat') {
const [name, msg, country, channelId] = packet; const [name, msg, country, channelId] = packet;
chatProvider.broadcastChatMessage( /*
* do not send message back up ws that sent it
* TODO: user id should not be hardcoded,
* consider it whenever this actually gets used and
* becomes an issue.
*/
socketEvents.broadcastChatMessage(
name, name,
msg, msg,
channelId, channelId,
chatProvider.infoUserId, 1,
country, country,
false, false,
); );
@ -193,7 +210,7 @@ class APISocketServer extends WebSocketEvents {
name, name,
msg, msg,
channelId, channelId,
chatProvider.infoUserId, 1,
country, country,
true, true,
ws, ws,

View File

@ -1,42 +1,25 @@
/* @flow /* @flow
* *
* Serverside communication with websockets. * Events for WebSockets
* In general all values that get broadcasted here have to be sanitized already.
*
*/ */
import EventEmitter from 'events';
import OnlineCounter from './packets/OnlineCounter'; import OnlineCounter from './packets/OnlineCounter';
import PixelUpdate from './packets/PixelUpdateServer'; import PixelUpdate from './packets/PixelUpdateServer';
class WebSockets { class SocketEvents extends EventEmitter {
listeners: Array<Object>;
onlineCounter: number;
constructor() { constructor() {
this.listeners = []; super();
this.onlineCounter = 0; this.onlineCounter = 0;
} }
addListener(listener) {
this.listeners.push(listener);
}
remListener(listener) {
const index = this.listeners.indexOf(listener);
if (index > -1) {
this.listeners.splice(index, 1);
}
}
/* /*
* broadcast message via websocket * broadcast message via websocket
* @param message Message to send * @param message Message to send
*/ */
broadcast(message: Buffer) { broadcast(message: Buffer) {
this.listeners.forEach( this.emit('broadcast', message);
(listener) => listener.broadcast(message),
);
} }
/* /*
@ -51,9 +34,21 @@ class WebSockets {
pixels: Buffer, pixels: Buffer,
) { ) {
const buffer = PixelUpdate.dehydrate(chunkId, pixels); const buffer = PixelUpdate.dehydrate(chunkId, pixels);
this.listeners.forEach( this.emit('pixelUpdate', canvasId, chunkId, buffer);
(listener) => listener.broadcastPixelBuffer(canvasId, chunkId, buffer), }
);
/*
* received Chat message on own websocket
* @param user User Instance that sent the message
* @param message text message
* @param channelId numerical channel id
*/
recvChatMessage(
user: Object,
message: string,
channelId: number,
) {
this.emit('recvChatMessage', user, message, channelId);
} }
/* /*
@ -71,16 +66,14 @@ class WebSockets {
country: string = 'xx', country: string = 'xx',
sendapi: boolean = true, sendapi: boolean = true,
) { ) {
country = country || 'xx'; this.emit(
this.listeners.forEach( 'chatMessage',
(listener) => listener.broadcastChatMessage( name,
name, message,
message, channelId,
channelId, id,
id, country || 'xx',
country, sendapi,
sendapi,
),
); );
} }
@ -95,12 +88,11 @@ class WebSockets {
channelId: number, channelId: number,
channelArray: Array, channelArray: Array,
) { ) {
this.listeners.forEach( this.emit(
(listener) => listener.broadcastAddChatChannel( 'addChatChannel',
userId, userId,
channelId, channelId,
channelArray, channelArray,
),
); );
} }
@ -114,21 +106,14 @@ class WebSockets {
userId: number, userId: number,
channelId: number, channelId: number,
) { ) {
this.listeners.forEach( this.emit('remChatChannel', userId, channelId);
(listener) => listener.broadcastRemoveChatChannel(
userId,
channelId,
),
);
} }
/* /*
* reload user on websocket to get changes * reload user on websocket to get changes
*/ */
reloadUser(name: string) { reloadUser(name: string) {
this.listeners.forEach( this.emit('reloadUser', name);
(listener) => listener.reloadUser(name),
);
} }
/* /*
@ -138,11 +123,8 @@ class WebSockets {
broadcastOnlineCounter(online: number) { broadcastOnlineCounter(online: number) {
this.onlineCounter = online; this.onlineCounter = online;
const buffer = OnlineCounter.dehydrate({ online }); const buffer = OnlineCounter.dehydrate({ online });
this.listeners.forEach( this.emit('broadcast', buffer);
(listener) => listener.broadcastOnlineCounter(buffer),
);
} }
} }
const webSockets = new WebSockets(); export default new SocketEvents();
export default webSockets;

View File

@ -18,10 +18,9 @@ import DeRegisterMultipleChunks from './packets/DeRegisterMultipleChunks';
import ChangedMe from './packets/ChangedMe'; import ChangedMe from './packets/ChangedMe';
import OnlineCounter from './packets/OnlineCounter'; import OnlineCounter from './packets/OnlineCounter';
import socketEvents from './SocketEvents';
import chatProvider, { ChatProvider } from '../core/ChatProvider'; import chatProvider, { ChatProvider } from '../core/ChatProvider';
import authenticateClient from './verifyClient'; import authenticateClient from './verifyClient';
import WebSocketEvents from './WebSocketEvents';
import webSockets from './websockets';
import { drawSafeByOffsets } from '../core/draw'; import { drawSafeByOffsets } from '../core/draw';
import { needCaptcha } from '../utils/captcha'; import { needCaptcha } from '../utils/captcha';
import { cheapDetector } from '../core/isProxy'; import { cheapDetector } from '../core/isProxy';
@ -50,16 +49,14 @@ async function verifyClient(info, done) {
} }
class SocketServer extends WebSocketEvents { class SocketServer {
wss: WebSocket.Server; wss: WebSocket.Server;
CHUNK_CLIENTS: Map<number, Array>; CHUNK_CLIENTS: Map<number, Array>;
// constructor(server: http.Server) { // constructor(server: http.Server) {
constructor() { constructor() {
super();
this.CHUNK_CLIENTS = new Map(); this.CHUNK_CLIENTS = new Map();
logger.info('Starting websocket server'); logger.info('Starting websocket server');
webSockets.addListener(this);
const wss = new WebSocket.Server({ const wss = new WebSocket.Server({
perMessageDeflate: false, perMessageDeflate: false,
@ -80,7 +77,6 @@ class SocketServer extends WebSocketEvents {
ws.isAlive = true; ws.isAlive = true;
ws.canvasId = null; ws.canvasId = null;
ws.startDate = Date.now(); ws.startDate = Date.now();
ws.on('pong', heartbeat);
const user = await authenticateClient(req); const user = await authenticateClient(req);
ws.user = user; ws.user = user;
ws.name = user.getName(); ws.name = user.getName();
@ -91,15 +87,20 @@ class SocketServer extends WebSocketEvents {
})); }));
const ip = getIPFromRequest(req); const ip = getIPFromRequest(req);
ws.on('error', (e) => { ws.on('error', (e) => {
logger.error(`WebSocket Client Error for ${ws.name}: ${e.message}`); logger.error(`WebSocket Client Error for ${ws.name}: ${e.message}`);
}); });
ws.on('pong', heartbeat);
ws.on('close', () => { ws.on('close', () => {
// is close called on terminate? // is close called on terminate?
// possible memory leak? // possible memory leak?
ipCounter.delete(ip); ipCounter.delete(ip);
this.deleteAllChunks(ws); this.deleteAllChunks(ws);
}); });
ws.on('message', (message) => { ws.on('message', (message) => {
if (typeof message === 'string') { if (typeof message === 'string') {
this.onTextMessage(message, ws); this.onTextMessage(message, ws);
@ -109,18 +110,53 @@ class SocketServer extends WebSocketEvents {
}); });
}); });
this.broadcast = this.broadcast.bind(this);
this.broadcastPixelBuffer = this.broadcastPixelBuffer.bind(this);
this.reloadUser = this.reloadUser.bind(this);
this.ping = this.ping.bind(this); this.ping = this.ping.bind(this);
/* socketEvents.on('broadcast', this.broadcast);
* i don't tink that we really need that, it just stresses the server socketEvents.on('pixelUpdate', this.broadcastPixelBuffer);
* with lots of reconnects at once, the overhead of having a few idle socketEvents.on('reloadUser', this.reloadUser);
* connections isn't too bad in comparison
*/ socketEvents.on('chatMessage', (
// this.killOld = this.killOld.bind(this); name,
// setInterval(this.killOld, 10 * 60 * 1000); message,
channelId,
id,
country,
) => {
const text = JSON.stringify([name, message, country, channelId, id]);
this.wss.clients.forEach((ws) => {
if (ws.readyState === WebSocket.OPEN) {
if (chatProvider.userHasChannelAccess(ws.user, channelId)) {
ws.send(text);
}
}
});
});
socketEvents.on('addChatChannel', (userId, channelId, channelArray) => {
this.findAllWsByUerId(userId).forEach((ws) => {
ws.user.addChannel(channelId, channelArray);
const text = JSON.stringify([
'addch', {
[channelId]: channelArray,
},
]);
ws.send(text);
});
});
socketEvents.on('remChatChannel', (userId, channelId) => {
this.findAllWsByUerId(userId).forEach((ws) => {
ws.user.removeChannel(channelId);
const text = JSON.stringify(['remch', channelId]);
ws.send(text);
});
});
setInterval(SocketServer.onlineCounterBroadcast, 10 * 1000); setInterval(SocketServer.onlineCounterBroadcast, 10 * 1000);
// https://github.com/websockets/ws#how-to-detect-and-close-broken-connections
setInterval(this.ping, 45 * 1000); setInterval(this.ping, 45 * 1000);
} }
@ -129,47 +165,34 @@ class SocketServer extends WebSocketEvents {
* https://github.com/websockets/ws/issues/617 * https://github.com/websockets/ws/issues/617
* @param data * @param data
*/ */
broadcast(data: Buffer) { broadcast(data) {
const frame = WebSocket.Sender.frame(data, { if (typeof data === 'string') {
readOnly: true, this.wss.clients.forEach((ws) => {
mask: false, if (ws.readyState === WebSocket.OPEN) {
rsv1: false, ws.send(data);
opcode: 2,
fin: true,
});
this.wss.clients.forEach((ws) => {
if (ws.readyState === WebSocket.OPEN) {
frame.forEach((buffer) => {
try {
// eslint-disable-next-line no-underscore-dangle
ws._socket.write(buffer);
} catch (error) {
logger.error(`WebSocket broadcast error: ${error.message}`);
}
});
}
});
}
broadcastOnlineCounter(buffer: Buffer) {
this.broadcast(buffer);
}
broadcastChatMessage(
name: string,
message: string,
channelId: number,
id: number,
country: string,
) {
const text = JSON.stringify([name, message, country, channelId, id]);
this.wss.clients.forEach((ws) => {
if (ws.readyState === WebSocket.OPEN) {
if (chatProvider.userHasChannelAccess(ws.user, channelId)) {
ws.send(text);
} }
} });
}); } else {
const frame = WebSocket.Sender.frame(data, {
readOnly: true,
mask: false,
rsv1: false,
opcode: 2,
fin: true,
});
this.wss.clients.forEach((ws) => {
if (ws.readyState === WebSocket.OPEN) {
frame.forEach((buffer) => {
try {
// eslint-disable-next-line no-underscore-dangle
ws._socket.write(buffer);
} catch (error) {
logger.error(`WebSocket broadcast error: ${error.message}`);
}
});
}
});
}
} }
/* /*
@ -189,35 +212,18 @@ class SocketServer extends WebSocketEvents {
return null; return null;
} }
broadcastAddChatChannel( findAllWsByUerId(userId) {
userId: number, const clients = [];
channelId: number, const it = this.wss.clients.keys();
channelArray: Array, let client = it.next();
) { while (!client.done) {
this.wss.clients.forEach((ws) => { const ws = client.value;
if (ws.user.id === userId && ws.readyState === WebSocket.OPEN) { if (ws.user.id === userId && ws.readyState === WebSocket.OPEN) {
ws.user.addChannel(channelId, channelArray); clients.push(ws);
const text = JSON.stringify([
'addch', {
[channelId]: channelArray,
},
]);
ws.send(text);
} }
}); client = it.next();
} }
return clients;
broadcastRemoveChatChannel(
userId: number,
channelId: number,
) {
this.wss.clients.forEach((ws) => {
if (ws.user.id === userId && ws.readyState === WebSocket.OPEN) {
ws.user.removeChannel(channelId);
const text = JSON.stringify(['remch', channelId]);
ws.send(text);
}
});
} }
broadcastPixelBuffer(canvasId: number, chunkid, data: Buffer) { broadcastPixelBuffer(canvasId: number, chunkid, data: Buffer) {
@ -263,14 +269,6 @@ class SocketServer extends WebSocketEvents {
}); });
} }
killOld() {
const now = Date.now();
this.wss.clients.forEach((ws) => {
const lifetime = now - ws.startDate;
if (lifetime > 30 * 60 * 1000 && Math.random() < 0.3) ws.terminate();
});
}
ping() { ping() {
this.wss.clients.forEach((ws) => { this.wss.clients.forEach((ws) => {
if (!ws.isAlive) { if (!ws.isAlive) {
@ -284,7 +282,7 @@ class SocketServer extends WebSocketEvents {
static onlineCounterBroadcast() { static onlineCounterBroadcast() {
const online = ipCounter.amount() || 0; const online = ipCounter.amount() || 0;
webSockets.broadcastOnlineCounter(online); socketEvents.broadcastOnlineCounter(online);
} }
async onTextMessage(text, ws) { async onTextMessage(text, ws) {
@ -319,6 +317,9 @@ class SocketServer extends WebSocketEvents {
* if DM channel, make sure that other user has DM open * if DM channel, make sure that other user has DM open
* (needed because we allow user to leave one-sided * (needed because we allow user to leave one-sided
* and auto-join on message) * and auto-join on message)
* TODO: if we scale and have multiple websocket servers at some point
* this might be an issue. We would hve to make a shared list of online
* users and act based on that on 'chatMessage' event
*/ */
const dmUserId = chatProvider.checkIfDm(user, channelId); const dmUserId = chatProvider.checkIfDm(user, channelId);
if (dmUserId) { if (dmUserId) {
@ -337,20 +338,11 @@ class SocketServer extends WebSocketEvents {
/* /*
* send chat message * send chat message
*/ */
const errorMsg = await chatProvider.sendMessage( socketEvents.recvChatMessage(
user, user,
message, message,
channelId, channelId,
); );
if (errorMsg) {
ws.send(JSON.stringify([
'info',
errorMsg,
'il',
channelId,
chatProvider.infoUserId,
]));
}
} else { } else {
logger.info('Got empty message or message from unidentified ws'); logger.info('Got empty message or message from unidentified ws');
} }

View File

@ -1,44 +0,0 @@
/* @flow
*
* Parent class for socket servers
*
*/
/* eslint-disable no-unused-vars */
/* eslint-disable class-methods-use-this */
class WebSocketEvents {
broadcast(message: Buffer) {
}
broadcastPixelBuffer(canvasId: number, chunkid: number, buffer: Buffer) {
}
broadcastChatMessage(
name: string,
message: string,
channelId: number,
userId: number,
) {}
broadcastAddChatChannel(
userId: number,
channelId: number,
channelArray: Array,
) {
}
broadcastRemoveChatChannel(
userId: number,
channelId: number,
) {
}
reloadUser(name: string) {
}
broadcastOnlineCounter(data: Buffer) {
}
}
export default WebSocketEvents;

View File

@ -194,9 +194,9 @@ const promise = models.sync({ alter: { drop: false } })
// const promise = models.sync() // const promise = models.sync()
.catch((err) => logger.error(err.stack)); .catch((err) => logger.error(err.stack));
promise.then(() => { promise.then(() => {
rankings.updateRanking();
chatProvider.initialize();
server.listen(PORT, HOST, () => { server.listen(PORT, HOST, () => {
rankings.updateRanking();
chatProvider.initialize();
const address = server.address(); const address = server.address();
logger.log( logger.log(
'info', 'info',