Skip to content

Commit

Permalink
feat: websocket events (#2641)
Browse files Browse the repository at this point in the history
* chore: add overlayExtension to effectManager types

* chore: convert http-server-manager to typescript

* chore: fix code typo

* chore: move websocket to seperate file

* feat: websocket events

* chore: update to invoke-response-event model

* chore: move overlay-event to event in IRE model

* chore: move websocket events to eventemitters and capture from single file

* fix: overlay clients display with event websocket

* chore: rework overlay client detection

* fix: incorrect check for overlay client report

---------

Co-authored-by: Erik Bigler <erikbigler@gmail.com>
Co-authored-by: SReject <SReject@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 1, 2024
1 parent b8f4547 commit 74d2612
Show file tree
Hide file tree
Showing 14 changed files with 579 additions and 137 deletions.
4 changes: 4 additions & 0 deletions src/backend/app-management/electron/events/when-ready.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ exports.whenReady = async () => {
const httpServerManager = require("../../../../server/http-server-manager");
httpServerManager.start();

// register websocket event handlers
const websocketEventsHandler = require("../../../../server/websocket-events-handler");
websocketEventsHandler.createComponentEventListeners();

windowManagement.updateSplashScreenStatus("Loading channel rewards...");
const channelRewardManager = require("../../../channel-rewards/channel-reward-manager");
await channelRewardManager.loadChannelRewards();
Expand Down
33 changes: 29 additions & 4 deletions src/backend/chat/commands/command-manager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import EventEmitter from "events";
import { TypedEmitter } from "tiny-typed-emitter";
import { JsonDB } from "node-json-db";
import { DateTime } from "luxon";

Expand All @@ -9,6 +9,14 @@ import profileManager from "../../common/profile-manager";
import frontendCommunicator from "../../common/frontend-communicator";
import accountAccess from "../../common/account-access";

type Events = {
"created-item": (item: object) => void;
"updated-item": (item: object) => void;
"deleted-item": (item: object) => void;
"systemCommandRegistered": (item: SystemCommand) => void;
"systemCommandUnRegistered": (id: string) => void;
};

interface SystemCommandOverrides {
[overrideId: string]: SystemCommandDefinition
}
Expand All @@ -21,7 +29,7 @@ interface CommandCache {
/**
* The class for the manager object that maintains Firebot system/custom chat commands
*/
class CommandManager extends EventEmitter {
class CommandManager extends TypedEmitter<Events> {
private _registeredSysCommands: SystemCommand[] = [];
private _commandCache: CommandCache = {
systemCommandOverrides: {},
Expand Down Expand Up @@ -71,6 +79,8 @@ class CommandManager extends EventEmitter {

this._registeredSysCommands.push(command);

this.emit("created-item", command);

logger.debug(`Registered Sys Command ${command.definition.id}`);

this.emit("systemCommandRegistered", command);
Expand All @@ -82,7 +92,10 @@ class CommandManager extends EventEmitter {
* @param id The ID of the system command to unregister
*/
unregisterSystemCommand(id: string): void {
const command = this._registeredSysCommands.find(c => c.definition.id === id);
this._registeredSysCommands = this._registeredSysCommands.filter(c => c.definition.id !== id);

this.emit("deleted-item", command);
this.emit("systemCommandUnRegistered", id);
logger.debug(`Unregistered Sys Command ${id}`);
}
Expand Down Expand Up @@ -242,14 +255,15 @@ class CommandManager extends EventEmitter {
const override = this._commandCache.systemCommandOverrides[id];
if (override != null) {
override.trigger = newTrigger;
this.saveSystemCommandOverride(override);
this.saveSystemCommandOverride(override, false);
}

const defaultCmd = this._registeredSysCommands.find(
c => c.definition.id === id
);
if (defaultCmd != null) {
defaultCmd.definition.trigger = newTrigger;
this.emit("updated-item", defaultCmd);
}

frontendCommunicator.send("system-commands-updated");
Expand All @@ -260,7 +274,7 @@ class CommandManager extends EventEmitter {
*
* @param command The `SystemCommandDefinition` with the system command override data
*/
saveSystemCommandOverride(command: SystemCommandDefinition): void {
saveSystemCommandOverride(command: SystemCommandDefinition, fireEvent = true): void {
this._commandCache.systemCommandOverrides[command.id] = command;

const commandDb = this.getCommandsDb();
Expand All @@ -270,6 +284,10 @@ class CommandManager extends EventEmitter {

try {
commandDb.push(`/systemCommandOverrides/${id}`, command);

if (fireEvent) {
this.emit("updated-item", command);
}
} catch (err) { }

frontendCommunicator.send("system-command-override-saved", command);
Expand All @@ -289,6 +307,8 @@ class CommandManager extends EventEmitter {
id = id.replace("/", "");
try {
commandDb.delete(`/systemCommandOverrides/${id}`);
const command = this.getSystemCommandById(id);
this.emit("updated-item", command);
} catch (err) {} //eslint-disable-line no-empty

frontendCommunicator.send("system-commands-updated");
Expand All @@ -304,7 +324,9 @@ class CommandManager extends EventEmitter {
* @param user The user who is creating/editing the custom command
*/
saveCustomCommand(command: CommandDefinition, user?: string): void {
let eventType: keyof Events = "updated-item";
if (command.id == null || command.id === "") {
eventType = "created-item";
// generate id for new command
const uuidv1 = require("uuid/v1");
command.id = uuidv1();
Expand All @@ -330,6 +352,7 @@ class CommandManager extends EventEmitter {

try {
commandDb.push(`/customCommands/${command.id}`, command);
this.emit(eventType, command);
} catch (err) {}

const existingCommandIndex = this._commandCache.customCommands.findIndex(c => c.id === command.id);
Expand Down Expand Up @@ -392,7 +415,9 @@ class CommandManager extends EventEmitter {
}

try {
const command = this.getCustomCommandById(id);
commandDb.delete(`/customCommands/${id}`);
this.emit("deleted-item", command);
} catch (err) {
logger.warn("error when deleting command", err.message);
}
Expand Down
27 changes: 26 additions & 1 deletion src/backend/common/custom-variable-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const eventManager = require("../events/EventManager");
const windowManagement = require("../app-management/electron/window-management");
const { ipcMain } = require("electron");

const EventEmitter = require("events");

const NodeCache = require("node-cache");

const cache = new NodeCache({ stdTTL: 0, checkperiod: 1 });
Expand All @@ -20,7 +22,11 @@ const onCustomVariableExpire = (key, value) => {
windowManagement.sendVariableExpireToInspector(key, value);
};

const onCustomVariableDelete = (key) => {
const onCustomVariableDelete = (key, value) => {
exports.events.emit("deleted-item", {
name: key,
value: value
});
windowManagement.sendVariableDeleteToInspector(key);
};

Expand Down Expand Up @@ -85,6 +91,8 @@ exports.addCustomVariable = (name, data, ttl = 0, propertyPath = null) => {
//silently fail
}

const eventType = !cache.keys().includes(name) ? "created-item" : "updated-item";

const dataRaw = data != null ? data.toString().toLowerCase() : "null";
const dataIsNull = dataRaw === "null" || dataRaw === "undefined";

Expand All @@ -97,6 +105,10 @@ exports.addCustomVariable = (name, data, ttl = 0, propertyPath = null) => {
dataToSet = currentData;
}
cache.set(name, dataToSet, ttl === "" ? 0 : ttl);
exports.events.emit(eventType, {
name: name,
value: dataToSet
});
} else {
const currentData = cache.get(name);
if (!currentData) {
Expand Down Expand Up @@ -132,6 +144,10 @@ exports.addCustomVariable = (name, data, ttl = 0, propertyPath = null) => {
}
}
cache.set(name, currentData, ttl === "" ? 0 : ttl);
exports.events.emit(eventType, {
name: name,
value: currentData
});
} catch (error) {
logger.debug(`error setting data to custom variable ${name} using property path ${propertyPath}`);
}
Expand Down Expand Up @@ -189,4 +205,13 @@ ipcMain.on("customVariableDelete", (_, key) => {
deleteCustomVariable(key);
});

/**
* @type {import("tiny-typed-emitter").TypedEmitter<{
* "created-item": (item: unknown) => void;
* "deleted-item": (item: unknown) => void;
* "updated-item": (item: unknown) => void;
* }>}
*/
exports.events = new EventEmitter();

exports.deleteCustomVariable = deleteCustomVariable;
7 changes: 5 additions & 2 deletions src/backend/effects/effectManager.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { EffectType } from "../../types/effects";
import { EffectType, OverlayExtension } from "../../types/effects";
import { EventEmitter } from "node:events";

declare class EffectManager {

declare class EffectManager extends EventEmitter {
registerEffect: <EffectModel>(effectType: EffectType<EffectModel>) => void;
getEffectById: (effectId: string) => EffectType<unknown> | undefined;
getEffectOverlayExtensions: () => OverlayExtension[];
}

declare const _EffectManager: EffectManager;
Expand Down
24 changes: 18 additions & 6 deletions src/backend/effects/queues/effect-queue-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const logger = require("../../logwrapper");
const effectRunner = require("../../common/effect-runner");
const eventManager = require("../../events/EventManager");
const frontendCommunicator = require("../../common/frontend-communicator");
const EventEmitter = require("events");

/**
* Queue Entry
Expand Down Expand Up @@ -31,14 +32,18 @@ class EffectQueue {
}

sendQueueLengthUpdate(lengthOverride = null) {
frontendCommunicator.send("updateQueueLength", {
const queue = {
id: this.id,
length: lengthOverride ?? this._queue.length
});
};

frontendCommunicator.send("updateQueueLength", queue);

exports.events.emit("length-updated", queue);
}

runQueue() {
return new Promise(resolve => {
return new Promise((resolve) => {
if (this._queue.length === 0 || this.canceled || this._paused === true) {
return resolve();
}
Expand All @@ -55,15 +60,15 @@ class EffectQueue {

if (this.mode === "interval") {
effectRunner.runEffects(runEffectsContext)
.catch(err => {
.catch((err) => {
logger.warn(`Error while processing effects for queue ${this.id}`, err);
});
setTimeout(() => {
resolve(this.runQueue());
}, this.interval * 1000);
} else if (this.mode === "auto") {
effectRunner.runEffects(runEffectsContext)
.catch(err => {
.catch((err) => {
logger.warn(`Error while processing effects for queue ${this.id}`, err);
})
.finally(() => {
Expand All @@ -73,7 +78,7 @@ class EffectQueue {
});
} else if (this.mode === "custom") {
effectRunner.runEffects(runEffectsContext)
.catch(err => {
.catch((err) => {
logger.warn(`Error while processing effects for queue ${this.id}`, err);
});
setTimeout(() => {
Expand Down Expand Up @@ -203,6 +208,13 @@ function clearAllQueues() {
Object.keys(queues).forEach(queueId => removeQueue(queueId));
}

/**
* @type {import("tiny-typed-emitter").TypedEmitter<{
* "length-updated": (item: object) => void;
* }>}
*/
exports.events = new EventEmitter();

exports.addEffectsToQueue = addEffectsToQueue;
exports.getQueue = getQueue;
exports.updateQueueConfig = updateQueueConfig;
Expand Down
19 changes: 18 additions & 1 deletion src/backend/roles/custom-roles-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import frontendCommunicator from "../common/frontend-communicator";
import twitchApi from "../twitch-api/api";
import twitchRoleManager from "../../shared/twitch-roles";
import { BasicViewer } from "../../types/viewers";
import { TypedEmitter } from "tiny-typed-emitter";

type Events = {
"created-item": (item: object) => void;
"updated-item": (item: object) => void;
"deleted-item": (item: object) => void;
};

interface LegacyCustomRole {
id: string;
Expand All @@ -28,10 +35,12 @@ interface CustomRole {

const ROLES_FOLDER = "roles";

class CustomRolesManager {
class CustomRolesManager extends TypedEmitter<Events> {
private _customRoles: Record<string, CustomRole> = {};

constructor() {
super();

frontendCommunicator.onAsync("get-custom-roles", async () => this._customRoles);

frontendCommunicator.on("save-custom-role", (role: CustomRole) => {
Expand Down Expand Up @@ -214,13 +223,17 @@ class CustomRolesManager {
return;
}

const eventType = this._customRoles[role.id] == null ? "created-item" : "updated-item";

this._customRoles[role.id] = role;

try {
const rolesDb = this.getCustomRolesDb();

rolesDb.push(`/${role.id}`, role);

this.emit(eventType, role);

logger.debug(`Saved role ${role.id} to file.`);
} catch (error) {
logger.warn("There was an error saving a role.", error);
Expand Down Expand Up @@ -315,13 +328,17 @@ class CustomRolesManager {
return;
}

const role = this._customRoles[roleId];

delete this._customRoles[roleId];

try {
const rolesDb = this.getCustomRolesDb();

rolesDb.delete(`/${roleId}`);

this.emit("deleted-item", role);

logger.debug(`Deleted role: ${roleId}`);
} catch (error) {
logger.warn("There was an error deleting a role.", error);
Expand Down
6 changes: 3 additions & 3 deletions src/resources/overlay/index.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
<link href="css/main.css" rel='stylesheet' type='text/css'>

<% for(let cssDep of dependancies.css) { %>
<% for(let cssDep of dependencies.css) { %>
<link rel="stylesheet" type="text/css" href="<%-cssDep%>">
<% } %>

<% for(let globalStyle of dependancies.globalStyles) { %>
<% for(let globalStyle of dependencies.globalStyles) { %>
<style><%-globalStyle%></style>
<% } %>

Expand All @@ -40,7 +40,7 @@
<script src="js/main.js"></script>
<script src="https://www.youtube.com/iframe_api"></script>

<% for(let jsDep of dependancies.js) { %>
<% for(let jsDep of dependencies.js) { %>
<script src="<%-jsDep%>"></script>
<% } %>

Expand Down
Loading

0 comments on commit 74d2612

Please sign in to comment.