channelManager now tracks all active connectedUser objects for a given user.

This commit is contained in:
rainbow napkin 2025-09-17 05:11:45 -04:00
parent 261dce7b29
commit 6222535c47
7 changed files with 160 additions and 24 deletions

View file

@ -74,6 +74,7 @@ class activeChannel{
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
* @param {Socket} socket - Requesting Socket
* @returns {activeUser} active user object generated by the new connection
*/
async handleConnection(userDB, chanDB, socket){
//get current user object from the userlist
@ -104,10 +105,13 @@ class activeChannel{
this.playlistHandler.defineListeners(socket);
//Hand off the connection initiation to it's user object
await userObj.handleConnection(userDB, chanDB, socket)
const activeUser = await userObj.handleConnection(userDB, chanDB, socket)
//Send out the userlist
this.broadcastUserList(socket.chan);
//Return active user connection object for use by the channelManager object
return activeUser;
}
/**
@ -115,11 +119,11 @@ class activeChannel{
* @param {Socket} socket - Requesting Socket
*/
handleDisconnect(socket){
//If we have more than one active connection
if(this.userList.get(socket.user.user).sockets.length > 1){
//temporarily store userObj
var userObj = this.userList.get(socket.user.user);
//temporarily store userObj
let userObj = this.userList.get(socket.user.user);
//If we have more than one active connection
if(userObj.sockets.length > 1){
//Filter out disconnecting socket from socket list, and set as current socket list for user
userObj.sockets = userObj.sockets.filter((id) => {
return id != socket.id;
@ -127,7 +131,11 @@ class activeChannel{
//Update the userlist
this.userList.set(socket.user.user, userObj);
//If this is the last one
}else{
//Tell the server to handle the disconnection of this user object
this.server.handleUserDisconnect(userObj);
//If this is the last connection for this user, remove them from the userlist
this.userList.delete(socket.user.user);
}

View file

@ -33,7 +33,7 @@ const chatHandler = require('./chatHandler');
class channelManager{
/**
* Instantiates object containing global server-side channel conection management logic
* @param {Server} io - Socket.io server instanced passed down from server.js
* @param {Socket.io} io - Socket.io server instanced passed down from server.js
*/
constructor(io){
/**
@ -46,6 +46,11 @@ class channelManager{
*/
this.activeChannels = new Map;
/**
* Map containing all active users. This may be redundant, however it improves preformance for user-specific inter-channel functionality
*/
this.activeUsers = new Map;
/**
* Global Chat Handler Object
*/
@ -88,13 +93,48 @@ class channelManager{
return;
}
//Connection accepted past this point
//Define listeners for inter-channel classes
this.defineListeners(socket);
this.chatHandler.defineListeners(socket);
//Hand off the connection to it's given active channel object
//Lil' hacky to pass chanDB like that, but why double up on DB calls?
activeChan.handleConnection(userDB, chanDB, socket);
const activeUser = await activeChan.handleConnection(userDB, chanDB, socket);
//Pull status from server-wide activeUsers map
let status = this.activeUsers.get(activeUser.user);
//If this user isn't connected anywhere else
if(status == null){
//initiate the entry
this.activeUsers.set(activeUser.user, [activeUser]);
//otherwise
}else{
//Push user to array by default
let pushUser = true;
//For each active connection within the status map
for(let curUser of status){
//If we're already listing this active user (we're splitting a user connection amongst several sockets)
if(curUser.channel.name == activeUser.channel.name){
//don't need to push it again
pushUser = false;
}
}
//if the user is flagged as un-added
if(pushUser){
//Add their connection object into the status array we pulled
status.push(activeUser);
//Set status entry to updated array
this.activeUsers.set(activeUser.set, status);
}
}
}else{
//Toss out anon's
socket.emit("kick", {type: "disconnected", reason: "You must log-in to join this channel!"});
@ -217,6 +257,34 @@ class channelManager{
activeChan.handleDisconnect(socket, reason);
}
/**
* Handles a disconnection event for a single active user within a given channel (when all sockets disconnect)
* @param {*} userObj
*/
handleUserDisconnect(userObj){
//Create array to hold
let stillConnected = [];
//Crawl through all known user connections
this.crawlConnections(userObj.user, (curUser)=>{
//If we have a matching username from a different channel
if(curUser.user == userObj.user && userObj.channel.name != curUser.channel.name){
//Keep current user
stillConnected.push(curUser);
}
});
//If we have anyone left
if(stillConnected.length > 0){
//save the remainder to the status map, otherwise unset the value.
this.activeUsers.set(userObj.user, stillConnected);
//Otherwise
}else{
//Delete the user from the status map
this.activeUsers.delete(userObj.user);
}
}
/**
* Pulls user information by socket
* @param {Socket} socket - Socket to check
@ -257,30 +325,28 @@ class channelManager{
* @param {Function} cb - Callback function to run active connections of a given user against
*/
crawlConnections(user, cb){
//For each channel
this.activeChannels.forEach((channel) => {
//Check and see if the user is connected
const foundUser = channel.userList.get(user);
//Pull connection list from status map
const list = this.activeUsers.get(user);
//If we found a user and this channel hasn't been added to the list
if(foundUser){
cb(foundUser);
//If we have active connections
if(list != null){
//For each connection
for(let user of list){
//Run the callback against it
cb(user);
}
});
}
}
/**
* Iterates through connections by a given username, and runs them through a given callback function/method
* This function is deprecated. Instead use channelManager.activeUsers.get(user)
* @param {String} user - Username to crawl connections against
* @param {Function} cb - Callback function to run active connections of a given user against
*/
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)});
const connections = this.activeUsers.get(user);
//return connects
return connections;

View file

@ -91,6 +91,7 @@ class connectedUser{
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
* @param {Socket} socket - Requesting Socket
* @returns {activeUser} active user object generated by the new connection
*/
async handleConnection(userDB, chanDB, socket){
//send metadata to client
@ -115,6 +116,10 @@ class connectedUser{
//Tattoo hashed IP address to user account for seven days
await userDB.tattooIPRecord(socket.handshake.address);
}
//Return active user object for use by activeChannel and channelManager objects
return this;
}
/**

View file

@ -0,0 +1,52 @@
/*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 <https://www.gnu.org/licenses/>.*/
/**
* Class representing a single chat message
*/
class message{
/**
* Instantiates a chat message object
* @param {connectedUser} sender - User who sent the message
* @param {Array} recipients - Array of connected users who are supposed to receive the message
* @param {String} msg - Contents of the message, with links replaced with numbered file-seperator markers
* @param {Array} links - Array of URLs/Links included in the message.
*/
constructor(sender, recipients, msg, links){
/**
* User who sent the message
*/
this.sender = sender;
/**
* Array of strings containing usernames to send message to
*/
this.recipients = recipients;
/**
* Contenst of the messages, with links replaced with numbered file-seperator markers
*/
this.msg = msg;
/**
* Array of URLs/Links included in the message.
*/
this.links = links;
}
}
module.exports = message;