Source: backend/data_center/modules/message_handlers.js

/**
 *      StreamRoller Copyright 2023 "SilenusTA https://www.twitch.tv/olddepressedgamer"
 * 
 *      StreamRoller is an all in one streaming solution designed to give a single
 *      'second monitor' control page and allow easy integration for configuring
 *      content (ie. tweets linked to chat, overlays triggered by messages, hue lights
 *      controlled by donations etc)
 * 
 *      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/>.
 */
/**
* Summary. Server Code used to routing functions and all server messages
* Messages are routed by type. Either driect to socket, room or broadcast
* all(excluding sender), or broadcast to all(including sender)
* 
* @see   https://github.com/SilenusTA/StreamRoller
* @author Silenus aka twitch.tv/OldDepressedGamer
* @since  29-Jan-2021
*/

// ============================================================================
//                           IMPORTS/VARIABLES
// ============================================================================
// Description: Import/Variable section
// ----------------------------- notes ----------------------------------------
// ============================================================================
import { Buffer } from 'buffer';
import sr_api from "../public/streamroller-message-api.cjs";
import * as cm from "./common.js";
import * as logger from "./logger.js";

const SYSTEM_LOGGING_TAG = "DATA-CENTER";
const EXTENSION_NAME = "datacenter";
let monitorSocketSentData = false;
let socketSentSize = 0;


// ============================================================================
//                           FUNCTION: sendConfig
// ============================================================================
/**
 * loads and sends the config file to the given extension
 * @param {Socket} client_socket socket to send on
 * @param {String} extensionname extension name
 */
function sendConfig (client_socket, extensionname)
{
    let loadedConfig = cm.loadConfig(extensionname);
    logger.log("[" + SYSTEM_LOGGING_TAG + "]message_handlers.sendConfig",
        "Sending config file", extensionname, loadedConfig);
    let msg = sr_api.ServerPacket("ConfigFile", EXTENSION_NAME, loadedConfig, "", extensionname);
    if (monitorSocketSentData)
        socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg))
    client_socket.emit("message", msg);
}
// ============================================================================
//                           FUNCTION: saveConfig
// ============================================================================
/**
 * save the config file to the server store
 * @param {String} extensionname 
 * @param {Object} configdata 
 */
function saveConfig (extensionname, configdata)
{
    cm.saveConfig(extensionname, configdata);
}
// ============================================================================
//                           FUNCTION: sendData
// ============================================================================
/**
 * loads and sends the data file to the given extension
 * @param {Socket} client_socket socket to send on
 * @param {String} extensionname extension name
 */
function sendData (client_socket, extensionname)
{
    let loadedData = cm.loadData(extensionname);
    logger.log("[" + SYSTEM_LOGGING_TAG + "]message_handlers.sendData",
        "Sending data file", extensionname, loadedData);
    let msg = sr_api.ServerPacket("DataFile", EXTENSION_NAME, loadedData, "", extensionname);
    if (monitorSocketSentData)
        socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg))
    client_socket.emit("message", msg);
}
// ============================================================================
//                           FUNCTION: sendData
// ============================================================================
/**
 * loads and sends the data file to the given extension
 * @param {Socket} client_socket socket to send on
 * @param {String} extensionname extension name
 */
function sendSoftwareVersion (client_socket, extensionname)
{
    let loadedData = cm.loadSoftwareVersion();
    logger.log("[" + SYSTEM_LOGGING_TAG + "]message_handlers.sendData",
        "Sending data file", extensionname, loadedData);
    let msg = sr_api.ServerPacket("SoftwareVersion", EXTENSION_NAME, loadedData, "", extensionname);
    if (monitorSocketSentData)
        socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg))
    client_socket.emit("message", msg);
}
// ============================================================================
//                           FUNCTION: saveData
// ============================================================================
/**
 * save the Data file to the server store
 * @param {String} extensionname 
 * @param {Object} data 
 */
function saveData (extensionname, data)
{
    cm.saveData(extensionname, data);
}
// ============================================================================
//                           FUNCTION: sendExtensionList
// ============================================================================
/**
 * Sends the extension list to extension
 * @param {Socket} clientsocket client to send to
 * @param {String} extensionname extension name to send to
 * @param {Object} extensions Extensions to send
 */
function sendExtensionList (clientsocket, extensionname, extensions)
{
    let names = [];
    for (const [key, value] of Object.entries(extensions))
    {
        names.push(key);
    }
    logger.log("[" + SYSTEM_LOGGING_TAG + "]message_handlers.sendExtensionList", "sending ExtensionList to " + extensionname);
    let msg = sr_api.ServerPacket("ExtensionList", EXTENSION_NAME, names, "", extensionname);
    if (monitorSocketSentData)
        socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg))
    clientsocket.emit("message", msg);
}
// ============================================================================
//                           FUNCTION: sendChannelList
// ============================================================================
/**
 * Send channellist to extension
 * @param {Socket} clientSocket socket to send to 
 * @param {String} extensionname extension name to send to
 * @param {Array} channels channels
 */
function sendChannelList (socket, extensionname, channels)
{
    logger.log("[" + SYSTEM_LOGGING_TAG + "]message_handlers.sendChannelList", "Sending CannelsList to " + extensionname);
    let msg = sr_api.ServerPacket("ChannelList", EXTENSION_NAME, channels, "", extensionname);
    if (monitorSocketSentData)
        socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg))
    socket.emit("message", msg)
}
// ============================================================================
//                           FUNCTION: createChannel
// ============================================================================
/**
 * Create and join a channel. If channel exists the existing one will be joined else created.
 * A ChannelCreated or ChannelJoined message will be broadcast
 * @param {Socket} server_socket 
 * @param {Socket} client_socket 
 * @param {String} channel_to_create 
 * @param {Array} channels 
 * @param {String} extensionname 
 */
function createChannel (server_socket, client_socket, channel_to_create, channels, extensionname)
{
    var msg;
    if (channels.includes(channel_to_create))
    {
        logger.info("[" + SYSTEM_LOGGING_TAG + "] message_handlers.createChannel:",
            "Channel " + channel_to_create + " exists. Joining existing channel instead " + extensionname);
        client_socket.join(channel_to_create);
        msg = sr_api.ServerPacket("ChannelJoined", EXTENSION_NAME, channel_to_create, "", extensionname);
        if (monitorSocketSentData)
            socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg))
        server_socket.emit("message", msg);
    } else
    {
        logger.log("[" + SYSTEM_LOGGING_TAG + "] message_handlers.createChannel", "Extension:"
            + extensionname + " created " + channel_to_create);
        // attach socket to channel
        client_socket.join(channel_to_create);
        // add channel to list
        channels.push(channel_to_create);
        msg = sr_api.ServerPacket("ChannelCreated", EXTENSION_NAME, channel_to_create, "", extensionname);
        if (monitorSocketSentData)
            socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg))
        server_socket.emit("message", msg);
    }
}
// ============================================================================
//                           FUNCTION: joinChannel
// ============================================================================
/**
 * Join a channel. if it doesn't exist an UnknownChannel message is sent back.
 * If it exists it is joined and a ChannelJoined message is broadcast out
 * @param {Socket} server_socket 
 * @param {Socket} client_socket 
 * @param {String} channel_to_join
 * @param {Array} channels 
 * @param {String} extensionname 
 */
function joinChannel (server_socket, client_socket, channel_to_join, channels, extensionname)
{
    var msg;
    if (channels.includes(channel_to_join))
    {
        logger.info("[" + SYSTEM_LOGGING_TAG + "] message_handlers.joinChannel", "Extension:"
            + extensionname + " joining " + channel_to_join);
        // attach socket to channel
        client_socket.join(channel_to_join);
        //broadcast out that the extension doing the channel
        msg = sr_api.ServerPacket("ChannelJoined", EXTENSION_NAME, channel_to_join, "", extensionname);
        if (monitorSocketSentData)
            socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg))
        server_socket.emit("message", msg);
    } else
    {
        logger.warn("[" + SYSTEM_LOGGING_TAG + "] message_handlers.joinChannel", "Extension:"
            + extensionname + " joining unknown channel. extension needs to reschedule. channel:" + channel_to_join);
        msg = sr_api.ServerPacket("UnknownChannel", EXTENSION_NAME, channel_to_join, "", extensionname);
        if (monitorSocketSentData)
            socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg))
        client_socket.emit("message", msg);
    }
}
// ============================================================================
//                           FUNCTION: leaveChannel
// ============================================================================
/**
 * Leave a channel. 
 * @param {Socket} server_socket server socket
 * @param {Socket} client_socket leaving client
 * @param {String} extensionname name of extension
 * @param {String} channel_to_leave channel to leave
 */
function leaveChannel (client_socket, extensionname, channel_to_leave)
{
    try
    {
        logger.log("[" + SYSTEM_LOGGING_TAG + "] message_handlers.leaveChannel Extension:"
            + extensionname + " leaving " + channel_to_leave);
        client_socket.leave(channel_to_leave);
        //server_socket.to(channel_to_leave).emit('ChannelLeft', extensionname);
    } catch (e)
    {
        logger.err("[" + SYSTEM_LOGGING_TAG + "] message_handlers.leaveChannel Extension:"
            + extensionname + " failed to leave channel " + channel_to_leave, e);

    }
}
// ============================================================================
//                           FUNCTION: SetLoggingLevel
// ============================================================================
/**
 * Set the system logging level. new level will be broadcast out
 * @param {Socket} server_socket server socket
 * @param {String} level level to set
 */
function setLoggingLevel (server_socket, level)
{
    logger.log("[" + SYSTEM_LOGGING_TAG + "]message_handlers.setLoggingLevel",
        "setting logging to " + level);
    logger.setLoggingLevel(level);
    let msg = sr_api.ServerPacket("LoggingLevel",
        EXTENSION_NAME, logger.getLoggingLevel());
    if (monitorSocketSentData)
        socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg))
    server_socket.emit("message", msg);
}
// ============================================================================
//                           FUNCTION: sendLoggingLevel
// ============================================================================
/**
 * send out the logging level
 * @param {Socket} client_socket socket who asked for the information
 */
function sendLoggingLevel (client_socket)
{
    logger.log("[" + SYSTEM_LOGGING_TAG + "]message_handlers.sendLoggingLevel",
        "setting logging to " + logger.getLoggingLevel());
    let msg = sr_api.ServerPacket("LoggingLevel",
        EXTENSION_NAME, logger.getLoggingLevel());
    if (monitorSocketSentData)
        socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg))
    client_socket.emit("message", msg);
}
// ============================================================================
//                           FUNCTION: forwardMessage
// ============================================================================
/**
 * forwards a message on based on the data provided
 * priority is "to","dest_channel" and the broadcast (except sender)
 * @param {Socket} server_socket 
 * @param {Object} server_packet 
 * @param {Array} channels 
 * @param {Object} extensions 
 */
function forwardMessage (client_socket, server_packet, channels, extensions)
{
    var msg;
    // check if we tried to send to an invalid channel or extension name
    // if message provides a destination but we don't a client socket for it
    if (server_packet.to && extensions[server_packet.to] && !extensions[server_packet.to].socket)
    {
        logger.log("[" + SYSTEM_LOGGING_TAG + "]message_handlers.forwardMessage",
            "UnknownExtension:", server_packet.to, " connection doesn't exist (maybe still loading?) from " + server_packet.from);
        msg = sr_api.ServerPacket("UnknownExtension", EXTENSION_NAME, { error: "extensions has no connection", message: server_packet });
        if (monitorSocketSentData)
            socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg));
        client_socket.emit("message", msg);
    }
    // send direct to client
    else if (extensions[server_packet.to] && extensions[server_packet.to].socket)
    {
        logger.extra("[" + SYSTEM_LOGGING_TAG + "]message_handlers.forwardMessage",
            "Destination:extension:", server_packet.type, server_packet.to, server_packet.data);
        if (monitorSocketSentData)
            socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(server_packet));
        extensions[server_packet.to].socket.emit("message", server_packet)
    }
    //if message provides a channel but we don't have that channel in our list then return an error
    else if (server_packet.dest_channel && !channels.includes(server_packet.dest_channel))
    {
        logger.extra("[" + SYSTEM_LOGGING_TAG + "]message_handlers.forwardMessage",
            "UnknownChannel:", server_packet.dest_channel, "doesn't exist (is the extension running?) from " + server_packet.from);
        msg = sr_api.ServerPacket("UnknownChannel", EXTENSION_NAME, server_packet.dest_channel);
        if (monitorSocketSentData)
            socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg));
        client_socket.emit("message", msg);
    }
    // send to channel
    else if (server_packet.dest_channel && channels.includes(server_packet.dest_channel))
    {
        logger.extra("[" + SYSTEM_LOGGING_TAG + "]message_handlers.forwardMessage",
            "Destination:channel:", server_packet.type, server_packet.dest_channel);
        if (monitorSocketSentData)
            socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(server_packet));
        client_socket.to(server_packet.dest_channel).emit("message", server_packet);
    }
    else
    {// broadcast (except the sender)
        logger.info("[" + SYSTEM_LOGGING_TAG + "]message_handlers.forwardMessage",
            "Destination:BROADCAST(Except sender):", server_packet);
        if (monitorSocketSentData)
            socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(server_packet));
        client_socket.broadcast.emit("message", server_packet);
    }
}
// ============================================================================
//                           FUNCTION: UpdateCredentials
// ============================================================================
function UpdateCredentials (server_packet)
{
    cm.saveCredentials(server_packet);
}
// ============================================================================
//                           FUNCTION: DeleteCredentials
// ============================================================================
function DeleteCredentials (from)
{
    cm.deleteCredentials(from)
}
// ============================================================================
//                           FUNCTION: RetrieveCredentials
// ============================================================================
function RetrieveCredentials (from, extensions)
{

    let loadedCredentials = cm.loadCredentials(from);
    let msg
    logger.log("[" + SYSTEM_LOGGING_TAG + "]message_handlers.RetrieveCredentials",
        "Sending credential file", from);
    if (loadedCredentials && loadedCredentials.ExtensionName && extensions[loadedCredentials.ExtensionName])
    {
        // create our message packet
        let msg = sr_api.ServerPacket("CredentialsFile", EXTENSION_NAME, loadedCredentials, "", loadedCredentials.ExtensionName);
        if (monitorSocketSentData)
            socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg));
        extensions[loadedCredentials.ExtensionName].socket.emit("message", msg);
    }
    else
    {
        logger.info("[" + SYSTEM_LOGGING_TAG + "]message_handlers.RetrieveCredentials",
            from + " No data Credentials available or ExtensionName not valid in credential file");
        // send an empty message back so the extension can check for missin credentials
        msg = sr_api.ServerPacket("CredentialsFile", EXTENSION_NAME, "", "", from)
        if (monitorSocketSentData)
            socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg));
        extensions[from].socket.emit("message", msg);
    }
}
// ============================================================================
//                           FUNCTION: broadcastMessage
// ============================================================================
/**
 * Broadcast message
 * @param {serverSocket} server_socket 
 * @param {object} server_packet 
 */
function broadcastMessage (server_socket, server_packet)
{
    logger.log("[" + SYSTEM_LOGGING_TAG + "]message_handlers.broadcastMessage",
        "Destination:BROADCAST(Except sender)", server_packet.type, server_packet.from);
    if (monitorSocketSentData)
        socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(server_packet));
    server_socket.emit("message", server_packet);
}
// ============================================================================
//                           FUNCTION: errorMessage
// ============================================================================
/**
 * Send an error message to the given socket
 * @param {Socket} client_socket 
 * @param {String} error 
 * @param {Object} data 
 */
function errorMessage (client_socket, error, data)
{
    logger.err("[" + SYSTEM_LOGGING_TAG + "]message_handlers.errorMessage",
        error, data);
    let msg = sr_api.ServerPacket("InvalidMessage",
        EXTENSION_NAME,
        { error: error, data: data });
    if (monitorSocketSentData)
        socketSentSize = socketSentSize + Buffer.byteLength(JSON.stringify(msg));
    client_socket.emit("message", msg)
}
// ============================================================================
//                           FUNCTION: errorMessage
// ============================================================================
/**
 * Send an error message to the given socket
 * @param {object} server_socket 
 * @param {int} receivedSize 
 */
function sendDataLoad (server_socket, receivedSize)
{
    //server_socket.emit("message", { "Input": receivedSize, "Output": socketSentSize });
    console.log("Input KB:\t", (receivedSize / 1024).toFixed(2), "   \tOutput KB\t", (socketSentSize / 1024).toFixed(2));
    socketSentSize = 0;
}
// ============================================================================
//                           EXPORTS: 
// ============================================================================
export
{
    broadcastMessage, createChannel, errorMessage, forwardMessage, joinChannel, leaveChannel, RetrieveCredentials, saveConfig, saveData, sendChannelList, sendConfig, sendData, sendExtensionList, sendLoggingLevel, sendSoftwareVersion, setLoggingLevel, UpdateCredentials, DeleteCredentials, sendDataLoad
};