From 5eb307bb9ef4d4445b37ef6984d9132098bc48f6 Mon Sep 17 00:00:00 2001 From: rainbow napkin Date: Wed, 22 Oct 2025 04:34:05 -0400 Subject: [PATCH] Created dedicated queue broadcast namespace, to make authorized queue broadcasts as painless as possible. --- README.md | 2 +- src/app/auxServer.js | 19 +++-- src/app/channel/channelManager.js | 13 ++- .../channel/media/queueBroadcastManager.js | 82 +++++++++++++++++++ src/app/pm/pmHandler.js | 5 +- .../channel/channelPermissionSchema.js | 6 ++ src/utils/socketUtils.js | 4 + www/js/channel/channel.js | 5 ++ www/js/utils.js | 4 + 9 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 src/app/channel/media/queueBroadcastManager.js diff --git a/README.md b/README.md index ac1f39c..75720ec 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Canopy ====== - + diff --git a/src/app/auxServer.js b/src/app/auxServer.js index 3a66337..2779dbf 100644 --- a/src/app/auxServer.js +++ b/src/app/auxServer.js @@ -51,21 +51,30 @@ class auxServer{ * Handles global server-side initialization for new connections to aux server * @param {Socket} socket - Requesting Socket */ - async handleConnection(socket){ + async handleConnection(socket, lite = true){ try{ + //Define empty value to hold result + let result = null; + //ensure unbanned ip and valid CSRF token if(!(await socketUtils.validateSocket(socket))){ socket.disconnect(); - return false; + return null; + } + + if(lite){ + result = await socketUtils.authSocketLite(socket); + }else{ + result = await socketUtils.authSocket(socket); } //If the socket wasn't authorized - if(await socketUtils.authSocketLite(socket) == null){ + if(result == null){ socket.disconnect(); - return false; + return null; } - return true; + return result; }catch(err){ //Flip a table if something fucks up return loggerUtils.socketCriticalExceptionHandler(socket, err); diff --git a/src/app/channel/channelManager.js b/src/app/channel/channelManager.js index 289c227..e9223f1 100644 --- a/src/app/channel/channelManager.js +++ b/src/app/channel/channelManager.js @@ -25,6 +25,7 @@ const loggerUtils = require('../../utils/loggerUtils'); const presenceUtils = require('../../utils/presenceUtils'); const activeChannel = require('./activeChannel'); const chatHandler = require('./chatHandler'); +const queueBroadcastManager = require('./media/queueBroadcastManager'); /** * Class containing global server-side channel connection management logic @@ -55,6 +56,16 @@ class channelManager{ */ this.chatHandler = new chatHandler(this); + /** + * Global Chat Handler Object + */ + this.chatHandler = new chatHandler(this); + + /** + * Global Auxiliary Server for Authenticated Queue Metadata Brodcasting + */ + this.queueBroadcastManager = new queueBroadcastManager(this.io, this) + //Handle connections from socket.io io.on("connection", this.handleConnection.bind(this) ); } @@ -152,7 +163,7 @@ class channelManager{ * @returns {Object} Object containing users active channel name and channel document object */ async getActiveChan(socket){ - socket.chan = socket.handshake.headers.referer.split('/c/')[1].split('/')[0]; + socket.chan = socketUtils.getChannelName(socket); const chanDB = (await channelModel.findOne({name: socket.chan})); //Check if channel exists diff --git a/src/app/channel/media/queueBroadcastManager.js b/src/app/channel/media/queueBroadcastManager.js new file mode 100644 index 0000000..c72a1c3 --- /dev/null +++ b/src/app/channel/media/queueBroadcastManager.js @@ -0,0 +1,82 @@ +/*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 auxServer = require("../../auxServer"); +const socketUtils = require("../../../utils/socketUtils") +const loggerUtils = require("../../../utils/loggerUtils"); +const channelModel = require("../../../schemas/channel/channelSchema"); + +/** + * Class containg global server-side private message relay logic + * + * Exists to make broadcasting channel queues to groups of authenticated users with the 'read-queue' perm as painless as possible, + * reducing DB call/perm checks to just connection time, and not requireing any out-of-library user iteration at broadcast time. + * + * Calls to modify and write to the schedule are still handled by the main namespace + * This is both for it's ease of access to the rest of the channel logic, but also to keep this class as small as possible. + */ +class queueBroadcastManager extends auxServer{ + /** + * Instantiates object containing global server-side channel schedule broadcasting subsystem + * @param {Socket.io} io - Socket.io server instanced passed down from server.js + * @param {channelManager} chanServer - Sister channel management server object + */ + constructor(io, chanServer){ + super(io, chanServer, "/queue-broadcast"); + } + + /** + * Handles global server-side initialization for new connections to the queue broadcast subsystem + * @param {Socket} socket - Requesting Socket + */ + async handleConnection(socket){ + //Check if we're properly authorized + const userObj = await super.handleConnection(socket); + + //If we're un-authorized + if(userObj == null){ + //Drop the connection + return; + } + + //Set socket channel value + socket.chan = socketUtils.getChannelName(socket); + //Pull channel DB + const chanDB = (await channelModel.findOne({name: socket.chan})); + + //If the user is connecting from an invalid channel + if(chanDB == null){ + //Drop the connection + return; + } + + //If the user is allowed to read the schedule + if(await chanDB.permCheck(socket.user, 'readSchedule')){ + //Throw the user into the channels room within the queue-broadcast instance + socket.join(socket.chan); + + //Define listeners + this.defineListeners(socket); + } + } + + defineListeners(socket){ + super.defineListeners(socket); + } +} + +module.exports = queueBroadcastManager; \ No newline at end of file diff --git a/src/app/pm/pmHandler.js b/src/app/pm/pmHandler.js index 5e16ffc..10c505e 100644 --- a/src/app/pm/pmHandler.js +++ b/src/app/pm/pmHandler.js @@ -19,6 +19,7 @@ const auxServer = require('../auxServer'); const chatPreprocessor = require('../chatPreprocessor'); const loggerUtils = require("../../utils/loggerUtils"); const message = require("./message"); +const socketUtils = require("../../utils/socketUtils"); /** * Class containg global server-side private message relay logic @@ -41,10 +42,10 @@ class pmHandler extends auxServer{ */ async handleConnection(socket){ //Check if we're properly authorized - const authorized = await super.handleConnection(socket, "${user}"); + const authorized = await super.handleConnection(socket); //If we're authorized - if(authorized){ + if(authorized != null){ //Throw the user into their own unique channel socket.join(socket.user.user); diff --git a/src/schemas/channel/channelPermissionSchema.js b/src/schemas/channel/channelPermissionSchema.js index bdd709e..1916efd 100644 --- a/src/schemas/channel/channelPermissionSchema.js +++ b/src/schemas/channel/channelPermissionSchema.js @@ -89,6 +89,12 @@ const channelPermissionSchema = new mongoose.Schema({ default: "admin", required: true }, + readSchedule: { + type: mongoose.SchemaTypes.String, + enum: rankEnum, + default: "admin", + required: true + }, scheduleMedia: { type: mongoose.SchemaTypes.String, enum: rankEnum, diff --git a/src/utils/socketUtils.js b/src/utils/socketUtils.js index 765ea02..8b538df 100644 --- a/src/utils/socketUtils.js +++ b/src/utils/socketUtils.js @@ -98,4 +98,8 @@ module.exports.authSocket = async function(socket){ }; return userDB; +} + +module.exports.getChannelName = function(socket){ + return socket.handshake.headers.referer.split('/c/')[1].split('/')[0]; } \ No newline at end of file diff --git a/www/js/channel/channel.js b/www/js/channel/channel.js index 9198ee1..e63a8a0 100644 --- a/www/js/channel/channel.js +++ b/www/js/channel/channel.js @@ -67,6 +67,10 @@ class channel{ //Freak out any weirdos who take a peek in the dev console for shits n gigs console.log("👁️👄👁️ ℬℴ𝓊𝓃𝒿ℴ𝓊𝓇."); + //Preach the good word about software which is Free as in Freedom + console.log(`Did you know Canopy, the software that runs '${utils.ux.getInstanceName()}' is software that is free as in freedom AND free weed?`); + console.log("This means you can read/modify/redistribute the code, run your own server, and even contribute back!"); + console.log("https://git.ourfore.st/rainbownapkin/canopy"); } /** @@ -81,6 +85,7 @@ class channel{ }; this.socket = io(clientOptions); + this.queueBroadcastSocket = io("/queue-broadcast", clientOptions); this.pmSocket = io("/pm", clientOptions); } diff --git a/www/js/utils.js b/www/js/utils.js index 2db077a..1c25aeb 100644 --- a/www/js/utils.js +++ b/www/js/utils.js @@ -50,6 +50,10 @@ class canopyUXUtils{ constructor(){ } + getInstanceName(){ + return document.querySelector("#instance-title a").innerText; + } + async awaitNextFrame(){ //return a new promise return new Promise((resolve)=>{