Created dedicated queue broadcast namespace, to make authorized queue broadcasts as painless as possible.

This commit is contained in:
rainbow napkin 2025-10-22 04:34:05 -04:00
parent e88ef29852
commit 5eb307bb9e
9 changed files with 131 additions and 9 deletions

View file

@ -2,7 +2,7 @@ Canopy
====== ======
<img src="https://camo.githubusercontent.com/bee100e0a2439d329fd52512a3acd1b4df3adf924e315507c5433d68ab79ab95/68747470733a2f2f70726964652d6261646765732e706f6e792e776f726b6572732e6465762f7374617469632f76313f6c6162656c3d656e627977617265266c6162656c436f6c6f723d2532333535352673747269706557696474683d3826737472697065436f6c6f72733d464346343334253243464646464646253243394335394431253243324332433243"> <img src="https://camo.githubusercontent.com/bee100e0a2439d329fd52512a3acd1b4df3adf924e315507c5433d68ab79ab95/68747470733a2f2f70726964652d6261646765732e706f6e792e776f726b6572732e6465762f7374617469632f76313f6c6162656c3d656e627977617265266c6162656c436f6c6f723d2532333535352673747269706557696474683d3826737472697065436f6c6f72733d464346343334253243464646464646253243394335394431253243324332433243">
<img src="https://img.shields.io/badge/developed-while%20high-green"> <img src="https://img.shields.io/badge/developed-while%20high-339933">
<img src="https://git.ourfore.st/rainbownapkin/canopy/badges/issues/open.svg"> <img src="https://git.ourfore.st/rainbownapkin/canopy/badges/issues/open.svg">
<img src="https://git.ourfore.st/rainbownapkin/canopy/badges/issues/closed.svg"> <img src="https://git.ourfore.st/rainbownapkin/canopy/badges/issues/closed.svg">

View file

@ -51,21 +51,30 @@ class auxServer{
* Handles global server-side initialization for new connections to aux server * Handles global server-side initialization for new connections to aux server
* @param {Socket} socket - Requesting Socket * @param {Socket} socket - Requesting Socket
*/ */
async handleConnection(socket){ async handleConnection(socket, lite = true){
try{ try{
//Define empty value to hold result
let result = null;
//ensure unbanned ip and valid CSRF token //ensure unbanned ip and valid CSRF token
if(!(await socketUtils.validateSocket(socket))){ if(!(await socketUtils.validateSocket(socket))){
socket.disconnect(); 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 the socket wasn't authorized
if(await socketUtils.authSocketLite(socket) == null){ if(result == null){
socket.disconnect(); socket.disconnect();
return false; return null;
} }
return true; return result;
}catch(err){ }catch(err){
//Flip a table if something fucks up //Flip a table if something fucks up
return loggerUtils.socketCriticalExceptionHandler(socket, err); return loggerUtils.socketCriticalExceptionHandler(socket, err);

View file

@ -25,6 +25,7 @@ const loggerUtils = require('../../utils/loggerUtils');
const presenceUtils = require('../../utils/presenceUtils'); const presenceUtils = require('../../utils/presenceUtils');
const activeChannel = require('./activeChannel'); const activeChannel = require('./activeChannel');
const chatHandler = require('./chatHandler'); const chatHandler = require('./chatHandler');
const queueBroadcastManager = require('./media/queueBroadcastManager');
/** /**
* Class containing global server-side channel connection management logic * Class containing global server-side channel connection management logic
@ -55,6 +56,16 @@ class channelManager{
*/ */
this.chatHandler = new chatHandler(this); 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 //Handle connections from socket.io
io.on("connection", this.handleConnection.bind(this) ); 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 * @returns {Object} Object containing users active channel name and channel document object
*/ */
async getActiveChan(socket){ 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})); const chanDB = (await channelModel.findOne({name: socket.chan}));
//Check if channel exists //Check if channel exists

View file

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

View file

@ -19,6 +19,7 @@ const auxServer = require('../auxServer');
const chatPreprocessor = require('../chatPreprocessor'); const chatPreprocessor = require('../chatPreprocessor');
const loggerUtils = require("../../utils/loggerUtils"); const loggerUtils = require("../../utils/loggerUtils");
const message = require("./message"); const message = require("./message");
const socketUtils = require("../../utils/socketUtils");
/** /**
* Class containg global server-side private message relay logic * Class containg global server-side private message relay logic
@ -41,10 +42,10 @@ class pmHandler extends auxServer{
*/ */
async handleConnection(socket){ async handleConnection(socket){
//Check if we're properly authorized //Check if we're properly authorized
const authorized = await super.handleConnection(socket, "${user}"); const authorized = await super.handleConnection(socket);
//If we're authorized //If we're authorized
if(authorized){ if(authorized != null){
//Throw the user into their own unique channel //Throw the user into their own unique channel
socket.join(socket.user.user); socket.join(socket.user.user);

View file

@ -89,6 +89,12 @@ const channelPermissionSchema = new mongoose.Schema({
default: "admin", default: "admin",
required: true required: true
}, },
readSchedule: {
type: mongoose.SchemaTypes.String,
enum: rankEnum,
default: "admin",
required: true
},
scheduleMedia: { scheduleMedia: {
type: mongoose.SchemaTypes.String, type: mongoose.SchemaTypes.String,
enum: rankEnum, enum: rankEnum,

View file

@ -99,3 +99,7 @@ module.exports.authSocket = async function(socket){
return userDB; return userDB;
} }
module.exports.getChannelName = function(socket){
return socket.handshake.headers.referer.split('/c/')[1].split('/')[0];
}

View file

@ -67,6 +67,10 @@ class channel{
//Freak out any weirdos who take a peek in the dev console for shits n gigs //Freak out any weirdos who take a peek in the dev console for shits n gigs
console.log("👁️👄👁️ 𝓊𝓃𝒿𝓊𝓇."); 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.socket = io(clientOptions);
this.queueBroadcastSocket = io("/queue-broadcast", clientOptions);
this.pmSocket = io("/pm", clientOptions); this.pmSocket = io("/pm", clientOptions);
} }

View file

@ -50,6 +50,10 @@ class canopyUXUtils{
constructor(){ constructor(){
} }
getInstanceName(){
return document.querySelector("#instance-title a").innerText;
}
async awaitNextFrame(){ async awaitNextFrame(){
//return a new promise //return a new promise
return new Promise((resolve)=>{ return new Promise((resolve)=>{