/*Canopy - The next generation of stoner streaming software Copyright (C) 2024 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 .*/ //Local Imports const channelModel = require('../../schemas/channel/channelSchema'); const userModel = require('../../schemas/userSchema'); const loggerUtils = require('../../utils/loggerUtils'); const activeChannel = require('./activeChannel'); const chatHandler = require('./chatHandler'); module.exports = class{ constructor(io){ //Set the socket.io server this.io = io; //Load this.activeChannels = new Map; //Load server components this.chatHandler = new chatHandler(this); //Handle connections from socket.io io.on("connection", this.handleConnection.bind(this) ); } async handleConnection(socket){ //Prevent logged out connections and authenticate socket if(socket.request.session.user != null){ try{ //Authenticate socket const userDB = await this.authSocket(socket); //Get the active channel based on the socket var {activeChan, chanDB} = await this.getActiveChan(socket); //Check for ban const ban = await chanDB.checkBanByUserDoc(userDB); if(ban != null){ //Toss out banned user's if(ban.expirationDays < 0){ socket.emit("kick", {type: "Banned", reason: "You have been permanently banned from this channel!"}); }else{ socket.emit("kick", {type: "Banned", reason: `You have been temporarily banned from this channel, and will be unbanned in ${ban.getDaysUntilExpiration()} day(s)!`}); } socket.disconnect(); return; } //Define listeners this.defineListeners(socket); this.chatHandler.defineListeners(socket); //Connect the socket to it's given channel //Lil' hacky to pass chanDB like that, but why double up on DB calls? activeChan.handleConnection(userDB, chanDB, socket); }catch(err){ //Flip a table if something fucks up return loggerUtils.socketCriticalExceptionHandler(socket, err); } }else{ //Toss out anon's socket.emit("kick", {type: "Disconnected", reason: "You must log-in to join this channel!"}); socket.disconnect(); return; } } async authSocket(socket){ //Find the user in the Database since the session won't store enough data to fulfill our needs :P const userDB = await userModel.findOne({user: socket.request.session.user.user}); if(userDB == null){ throw new Error("User not found!"); } //Set socket user and channel values socket.user = { id: userDB.id, user: userDB.user, }; return userDB; } async getActiveChan(socket){ socket.chan = socket.handshake.headers.referer.split('/c/')[1]; const chanDB = (await channelModel.findOne({name: socket.chan})) //Check if channel exists if(chanDB == null){ throw new Error("Channel not found!"); } //Check if current channel is active var activeChan = this.activeChannels.get(socket.chan); if(!activeChan){ //If not, make it so activeChan = new activeChannel(this, chanDB); this.activeChannels.set(socket.chan, activeChan); } //Return whatever the active channel is (new or old) return {activeChan, chanDB}; //return activeChan; } defineListeners(socket){ //Socket Listeners socket.conn.on("close", (reason) => {this.handleDisconnect(socket, reason)}); } handleDisconnect(socket, reason){ var activeChan = this.activeChannels.get(socket.chan); activeChan.handleDisconnect(socket, reason); } getSocketInfo(socket){ const channel = this.activeChannels.get(socket.chan); return channel.userList.get(socket.user.user); } getConnectedChannels(socket){ //Create a list to hold connected channels var chanList = []; //For each channel this.activeChannels.forEach((channel) => { //Check and see if the user is connected const foundUser = channel.userList.get(socket.user.user); //If we found a user and this channel hasn't been added to the list if(foundUser){ chanList.push(channel); } }); //return the channels this user is connected to return chanList; } crawlConnections(user, cb){ //For each channel this.activeChannels.forEach((channel) => { //Check and see if the user is connected const foundUser = channel.userList.get(user); //If we found a user and this channel hasn't been added to the list if(foundUser){ cb(foundUser); } }); } getConnections(user){ //Create a list to store our connections var connections = []; //crawl through connections //this.crawlConnections(user,(foundUser)=>{connections.push(foundUser)}); this.crawlConnections(user,(foundUser)=>{connections.push(foundUser)}); //return connects return connections; } kickConnections(user, reason){ //crawl through connections and kick user this.crawlConnections(user,(foundUser)=>{foundUser.disconnect(reason)}); } }