/*Canopy - The next generation of stoner streaming software Copyright (C) 2024-2025 Rainbownapkin and the TTN Community This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see .*/ //NPM Imports const validator = require('validator');//No express here, so regular validator it is! //local includes const loggerUtils = require("../../utils/loggerUtils"); const socketUtils = require("../../utils/socketUtils"); const message = require("./message"); /** * Class containg global server-side private message relay logic */ class pmHandler{ /** * Instantiates object containing global server-side private message relay logic * @param {Socket.io} io - Socket.io server instanced passed down from server.js */ constructor(io){ /** * Socket.io server instance passed down from server.js */ this.io = io; /** * Socket.io server namespace for handling messaging */ this.namespace = io.of('/pm'); //Handle connections from private messaging namespace this.namespace.on("connection", this.handleConnection.bind(this) ); } /** * Handles global server-side initialization for new connections to the private messaging system * @param {Socket} socket - Requesting Socket */ async handleConnection(socket){ try{ //ensure unbanned ip and valid CSRF token if(!(await socketUtils.validateSocket(socket))){ socket.disconnect(); return; } //If the socket wasn't authorized if(await socketUtils.authSocketLite(socket) == null){ socket.disconnect(); return; } //Throw socket into room named after it's user socket.join(socket.user.user); //Define network related event listeners against socket this.defineListeners(socket); }catch(err){ //Flip a table if something fucks up return loggerUtils.socketCriticalExceptionHandler(socket, err); } } defineListeners(socket){ socket.on("pm", (data)=>{this.handlePM(data, socket)}); } async handlePM(data, socket){ try{ //Create empty list of recipients let recipients = []; //For each requested recipient for(let user of data.recipients){ //If the given user is online and didn't send the message if(this.checkPresence(user) && user != socket.user.user){ //Add the recipient to the list recipients.push(user); } } //If we don't have any valid recipients if(recipients.length <= 0){ //Drop that shit return; } //Sanatize Message const msg = this.sanatizeMessage(data.msg); //If we have an invalid message if(msg == null){ //Drop that shit return; } //Create message object and relay it off to the recipients this.relayPMObj(new message( socket.user.user, recipients, msg, [] )); //If something fucked up }catch(err){ //Bitch and moan return loggerUtils.socketExceptionHandler(socket, err); } } relayPMObj(msg){ //For each recipient for(let user of msg.recipients){ //Send the message this.namespace.to(user).emit("message", msg); } //Acknowledge the sent message this.namespace.to(msg.sender).emit("sent", msg); } /** * Basic function for checking presence * This could be done using Channel Presence, but running off of bare Socket.io functionality makes this easier to implement outside the channel if need be * @param {String} user - Username to check presence of * @returns {Boolean} Whether or not the user is currently able to accept messages */ checkPresence(user){ //Pull room map from the guts of socket.io and run a null check against the given username return this.namespace.adapter.rooms.get(user) != null; } /** * Sanatizes and Validates a single message, Temporary until we get commandPreprocessor split up. * @param {String} msg - message to validate/sanatize * @returns {String} sanatized/validates message, returns null on validation failure */ sanatizeMessage(msg){ //Normally I'd kill empty messages here //But instead we're allowing them for sesh startups //Trim and Sanatize for XSS msg = validator.trim(validator.escape(msg)); //Return whether or not the shit was too long if(validator.isLength(msg, {min: 0, max: 255})){ //If it's valid return the message return msg; } //if not return nothing return null; } } module.exports = pmHandler;