Last user activity now marked on humie-friendly page-loads and last-socket disconnects, ensuring accurate 'online' status when disconnected from a channel.

This commit is contained in:
rainbow napkin 2025-09-17 20:17:41 -04:00
parent 6222535c47
commit 6445950f90
7 changed files with 99 additions and 2 deletions

View file

@ -24,6 +24,7 @@ const {userModel} = require('../../schemas/user/userSchema');
const userBanModel = require('../../schemas/user/userBanSchema'); const userBanModel = require('../../schemas/user/userBanSchema');
const loggerUtils = require('../../utils/loggerUtils'); const loggerUtils = require('../../utils/loggerUtils');
const csrfUtils = require('../../utils/csrfUtils'); const csrfUtils = require('../../utils/csrfUtils');
const presenceUtils = require('../../utils/presenceUtils');
const activeChannel = require('./activeChannel'); const activeChannel = require('./activeChannel');
const chatHandler = require('./chatHandler'); 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) * 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){ handleUserDisconnect(userObj){
//Create array to hold //Create array to hold
@ -282,6 +283,9 @@ class channelManager{
}else{ }else{
//Delete the user from the status map //Delete the user from the status map
this.activeUsers.delete(userObj.user); this.activeUsers.delete(userObj.user);
//Mark last disconnection as user activity, as they'll no longer be marked as streaming.
presenceUtils.handlePresence(userObj.user);
} }
} }

View file

@ -17,16 +17,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//npm imports //npm imports
const { Router } = require('express'); const { Router } = require('express');
//local imports //local imports
const permissionSchema = require("../schemas/permissionSchema"); const permissionSchema = require("../schemas/permissionSchema");
const adminPanelController = require("../controllers/adminPanelController"); const adminPanelController = require("../controllers/adminPanelController");
const presenceUtils = require("../utils/presenceUtils");
//globals //globals
const router = Router(); const router = Router();
//Use authentication middleware //Use authentication middleware
router.use(permissionSchema.reqPermCheck("adminPanel")) router.use(permissionSchema.reqPermCheck("adminPanel"))
router.use(presenceUtils.presenceMiddleware);
//routing functions //routing functions
router.get('/', adminPanelController.get); router.get('/', adminPanelController.get);

View file

@ -22,6 +22,7 @@ const { Router } = require('express');
const channelModel = require("../schemas/channel/channelSchema"); const channelModel = require("../schemas/channel/channelSchema");
const channelController = require("../controllers/channelController"); const channelController = require("../controllers/channelController");
const channelSettingsController = require("../controllers/channelSettingsController"); const channelSettingsController = require("../controllers/channelSettingsController");
const presenceUtils = require("../utils/presenceUtils");
//globals //globals
const router = Router(); const router = Router();
@ -29,6 +30,9 @@ const router = Router();
//User authentication middleware //User authentication middleware
router.use("/*/settings",channelModel.reqPermCheck("manageChannel","/c/")); router.use("/*/settings",channelModel.reqPermCheck("manageChannel","/c/"));
//Use presence middleware
router.use(presenceUtils.presenceMiddleware);
//routing functions //routing functions
router.get('/*/settings', channelSettingsController.get); router.get('/*/settings', channelSettingsController.get);
router.get('/*/', channelController.get); router.get('/*/', channelController.get);

View file

@ -20,10 +20,14 @@ const { Router } = require('express');
//local imports //local imports
const indexController = require("../controllers/indexController"); const indexController = require("../controllers/indexController");
const presenceUtils = require("../utils/presenceUtils");
//globals //globals
const router = Router(); const router = Router();
//Use presence middleware
router.use(presenceUtils.presenceMiddleware);
//routing functions //routing functions
router.get('/', indexController.get); router.get('/', indexController.get);

View file

@ -21,6 +21,7 @@ const { Router } = require('express');
//local imports //local imports
const permissionSchema = require("../schemas/permissionSchema"); const permissionSchema = require("../schemas/permissionSchema");
const newChannelController = require("../controllers/newChannelController"); const newChannelController = require("../controllers/newChannelController");
const presenceUtils = require("../utils/presenceUtils");
//globals //globals
const router = Router(); const router = Router();
@ -28,6 +29,9 @@ const router = Router();
//user authentication middleware //user authentication middleware
router.use("/",permissionSchema.reqPermCheck("registerChannel")); router.use("/",permissionSchema.reqPermCheck("registerChannel"));
//Use presence middleware
router.use(presenceUtils.presenceMiddleware);
//routing functions //routing functions
router.get('/', newChannelController.get); router.get('/', newChannelController.get);

View file

@ -60,6 +60,11 @@ const userSchema = new mongoose.Schema({
required: true, required: true,
default: new Date() default: new Date()
}, },
lastActive: {
type: mongoose.SchemaTypes.Date,
required: true,
default: new Date()
},
rank: { rank: {
type: mongoose.SchemaTypes.String, type: mongoose.SchemaTypes.String,
required: true, required: true,

View file

@ -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 <https://www.gnu.org/licenses/>.*/
//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();
}
}
}