From 6445950f9029f75fab1ff03e2e888b0110644801 Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Wed, 17 Sep 2025 20:17:41 -0400 Subject: [PATCH] Last user activity now marked on humie-friendly page-loads and last-socket disconnects, ensuring accurate 'online' status when disconnected from a channel. --- src/app/channel/channelManager.js | 6 ++- src/routers/adminPanelRouter.js | 3 +- src/routers/channelRouter.js | 4 ++ src/routers/indexRouter.js | 4 ++ src/routers/newChannelRouter.js | 4 ++ src/schemas/user/userSchema.js | 5 +++ src/utils/presenceUtils.js | 75 +++++++++++++++++++++++++++++++ 7 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/utils/presenceUtils.js diff --git a/src/app/channel/channelManager.js b/src/app/channel/channelManager.js index 66a14cd..4b594ad 100644 --- a/src/app/channel/channelManager.js +++ b/src/app/channel/channelManager.js @@ -24,6 +24,7 @@ const {userModel} = require('../../schemas/user/userSchema'); const userBanModel = require('../../schemas/user/userBanSchema'); const loggerUtils = require('../../utils/loggerUtils'); const csrfUtils = require('../../utils/csrfUtils'); +const presenceUtils = require('../../utils/presenceUtils'); const activeChannel = require('./activeChannel'); const chatHandler = require('./chatHandler'); @@ -259,7 +260,7 @@ class channelManager{ /** * Handles a disconnection event for a single active user within a given channel (when all sockets disconnect) - * @param {*} userObj + * @param {connectedUser} userObj - Connected user object to handle disconnection of */ handleUserDisconnect(userObj){ //Create array to hold @@ -282,6 +283,9 @@ class channelManager{ }else{ //Delete the user from the status map this.activeUsers.delete(userObj.user); + + //Mark last disconnection as user activity, as they'll no longer be marked as streaming. + presenceUtils.handlePresence(userObj.user); } } diff --git a/src/routers/adminPanelRouter.js b/src/routers/adminPanelRouter.js index 1d54411..129b403 100644 --- a/src/routers/adminPanelRouter.js +++ b/src/routers/adminPanelRouter.js @@ -17,16 +17,17 @@ along with this program. If not, see .*/ //npm imports const { Router } = require('express'); - //local imports const permissionSchema = require("../schemas/permissionSchema"); const adminPanelController = require("../controllers/adminPanelController"); +const presenceUtils = require("../utils/presenceUtils"); //globals const router = Router(); //Use authentication middleware router.use(permissionSchema.reqPermCheck("adminPanel")) +router.use(presenceUtils.presenceMiddleware); //routing functions router.get('/', adminPanelController.get); diff --git a/src/routers/channelRouter.js b/src/routers/channelRouter.js index c8343ad..fb32167 100644 --- a/src/routers/channelRouter.js +++ b/src/routers/channelRouter.js @@ -22,6 +22,7 @@ const { Router } = require('express'); const channelModel = require("../schemas/channel/channelSchema"); const channelController = require("../controllers/channelController"); const channelSettingsController = require("../controllers/channelSettingsController"); +const presenceUtils = require("../utils/presenceUtils"); //globals const router = Router(); @@ -29,6 +30,9 @@ const router = Router(); //User authentication middleware router.use("/*/settings",channelModel.reqPermCheck("manageChannel","/c/")); +//Use presence middleware +router.use(presenceUtils.presenceMiddleware); + //routing functions router.get('/*/settings', channelSettingsController.get); router.get('/*/', channelController.get); diff --git a/src/routers/indexRouter.js b/src/routers/indexRouter.js index 0b3528b..92135dc 100644 --- a/src/routers/indexRouter.js +++ b/src/routers/indexRouter.js @@ -20,10 +20,14 @@ const { Router } = require('express'); //local imports const indexController = require("../controllers/indexController"); +const presenceUtils = require("../utils/presenceUtils"); //globals const router = Router(); +//Use presence middleware +router.use(presenceUtils.presenceMiddleware); + //routing functions router.get('/', indexController.get); diff --git a/src/routers/newChannelRouter.js b/src/routers/newChannelRouter.js index 0dd998c..e77808b 100644 --- a/src/routers/newChannelRouter.js +++ b/src/routers/newChannelRouter.js @@ -21,6 +21,7 @@ const { Router } = require('express'); //local imports const permissionSchema = require("../schemas/permissionSchema"); const newChannelController = require("../controllers/newChannelController"); +const presenceUtils = require("../utils/presenceUtils"); //globals const router = Router(); @@ -28,6 +29,9 @@ const router = Router(); //user authentication middleware router.use("/",permissionSchema.reqPermCheck("registerChannel")); +//Use presence middleware +router.use(presenceUtils.presenceMiddleware); + //routing functions router.get('/', newChannelController.get); diff --git a/src/schemas/user/userSchema.js b/src/schemas/user/userSchema.js index 40a3e6e..5f718ed 100644 --- a/src/schemas/user/userSchema.js +++ b/src/schemas/user/userSchema.js @@ -60,6 +60,11 @@ const userSchema = new mongoose.Schema({ required: true, default: new Date() }, + lastActive: { + type: mongoose.SchemaTypes.Date, + required: true, + default: new Date() + }, rank: { type: mongoose.SchemaTypes.String, required: true, diff --git a/src/utils/presenceUtils.js b/src/utils/presenceUtils.js new file mode 100644 index 0000000..603b5c3 --- /dev/null +++ b/src/utils/presenceUtils.js @@ -0,0 +1,75 @@ +/*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 .*/ + +//local includes +const userSchema = require('../schemas/user/userSchema'); + +//User activity map to keep us from constantly reading off of the DB +let activityMap = new Map(); + +//How much difference between last write and now until we hit the DB again (in millis) +//Defaults to two minutes +const tolerance = 2 * (60 * 1000); + +module.exports.presenceMiddleware = function(req, res, next){ + //Pull user from session + const user = req.session.user; + + //if we have a user object + if(user != null){ + //Handle Presence + module.exports.handlePresence(user.user); + } + + //Go on to next part of the middleware chain + next(); +} + +module.exports.handlePresence = async function(user, userDB, noSave = false){ + //If we don't have a user + if(user == null || user == ''){ + //Drop that shit + return; + } + + //Get current date as epoch (millis) + const now = new Date(); + const millis = now.getTime(); + + //Check last user activity + const activity = activityMap.get(user); + + //If we have no recorded activity, or if the the time between now and the last activity is greater than two minutes + if(activity == null || millis - activity > tolerance){ + //Set last user activity + activityMap.set(user, millis); + + //If we wheren't handed a free user doc + if(userDB == null){ + //Pull one from the username + userDB = await userSchema.userModel.findOne({user: user}); + } + + //Set last active in user's DB document + userDB.lastActive = now; + + //If saving is enabled + if(!noSave){ + //Save document to + await userDB.save(); + } + } +} \ No newline at end of file