Files
Werewolf/server/modules/singletons/SocketManager.js
2023-01-12 18:07:33 -05:00

136 lines
4.7 KiB
JavaScript

const globals = require('../../config/globals');
const EVENT_IDS = globals.EVENT_IDS;
const { RateLimiterMemory } = require('rate-limiter-flexible');
const redis = require('redis');
const Events = require('../Events');
class SocketManager {
constructor (logger, instanceId) {
if (SocketManager.instance) {
throw new Error('The server attempted to instantiate more than one SocketManager.');
}
logger.info('CREATING SINGLETON SOCKET MANAGER');
this.logger = logger;
this.io = null;
this.publisher = null;
this.activeGameRunner = null;
this.gameManager = null;
this.instanceId = instanceId;
SocketManager.instance = this;
}
broadcast = (message) => {
this.io?.emit(globals.EVENTS.BROADCAST, message);
};
createRedisPublisher = async () => {
this.publisher = redis.createClient();
await this.publisher.connect();
this.logger.info('SOCKET MANAGER - CREATED GAME SYNC PUBLISHER');
}
createSocketServer = (main, app, port, logger) => {
let io;
if (process.env.NODE_ENV.trim() === 'development') {
io = require('socket.io')(main, {
cors: { origin: 'http://localhost:' + port }
});
} else {
io = require('socket.io')(main, {
cors: { origin: 'https://play-werewolf.app' }
});
}
registerRateLimiter(io, logger);
this.io = io;
return io;
};
createGameSocketNamespace = (server, logger, gameManager) => {
const namespace = server.of('/in-game');
const registerHandlers = this.registerHandlers;
registerRateLimiter(namespace, logger);
namespace.on('connection', function (socket) {
socket.on('disconnecting', (reason) => {
logger.trace('client socket disconnecting because: ' + reason);
});
registerHandlers(namespace, socket, gameManager);
});
return server.of('/in-game');
};
registerHandlers = (namespace, socket) => {
socket.on(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, async (eventId, accessCode, args = null, ackFn = null) => {
const game = await this.activeGameRunner.getActiveGame(accessCode);
if (game) {
await this.handleAndSyncEvent(eventId, game, socket, args, ackFn);
} else {
ackFn(null);
}
});
};
handleAndSyncEvent = async (eventId, game, socket, socketArgs, ackFn) => {
await this.handleEventById(eventId, game, socket?.id, game.accessCode, socketArgs, ackFn, false);
/* This server should publish events initiated by a connected socket to Redis for consumption by other instances. */
if (globals.SYNCABLE_EVENTS().includes(eventId)) {
await this.gameManager.refreshGame(game);
await this.publisher?.publish(
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
game.accessCode + ';' + eventId + ';' + JSON.stringify(socketArgs) + ';' + this.instanceId
);
}
}
handleEventById = async (eventId, game, socketId, accessCode, socketArgs, ackFn, syncOnly) => {
this.logger.trace('ARGS TO HANDLER: ' + JSON.stringify(socketArgs));
const event = Events.find((event) => event.id === eventId);
const additionalVars = {
gameManager: this.gameManager,
socketId: socketId,
ackFn: ackFn
};
if (event) {
if (!syncOnly) {
event.stateChange(game, socketArgs, additionalVars);
}
event.communicate(game, socketArgs, additionalVars);
}
switch (eventId) {
case EVENT_IDS.PAUSE_TIMER:
await this.gameManager.pauseTimer(game, this.logger);
break;
case EVENT_IDS.RESUME_TIMER:
await this.gameManager.resumeTimer(game, this.logger);
break;
case EVENT_IDS.GET_TIME_REMAINING:
await this.gameManager.getTimeRemaining(game, socketId);
break;
default:
break;
}
}
}
function registerRateLimiter (server, logger) {
const rateLimiter = new RateLimiterMemory(
{
points: 10,
duration: 1
});
server.use(async (socket, next) => {
try {
await rateLimiter.consume(socket.handshake.address);
logger.trace('consumed point from ' + socket.handshake.address);
next();
} catch (rejection) {
next(new Error('Your connection has been blocked.'));
}
});
}
module.exports = SocketManager;