const express = require("express"); const Log = require("logger"); const { replaceSecretPlaceholder } = require("#server_functions"); class NodeHelper { init () { Log.log("Initializing new module helper ..."); } loaded () { Log.log(`Module helper loaded: ${this.name}`); } start () { Log.log(`Starting module helper: ${this.name}`); } /** * Called when the MagicMirror² server receives a `SIGINT` * Close any open connections, stop any sub-processes and * gracefully exit the module. */ stop () { Log.log(`Stopping module helper: ${this.name}`); } /** * This method is called when a socket notification arrives. * @param {string} notification The identifier of the notification. * @param {object} payload The payload of the notification. */ socketNotificationReceived (notification, payload) { Log.log(`${this.name} received a socket notification: ${notification} - Payload: ${payload}`); } /** * Set the module name. * @param {string} name Module name. */ setName (name) { this.name = name; } /** * Set the module path. * @param {string} path Module path. */ setPath (path) { this.path = path; } /* * sendSocketNotification(notification, payload) * Send a socket notification to the node helper. * * argument notification string - The identifier of the notification. * argument payload mixed - The payload of the notification. */ sendSocketNotification (notification, payload) { this.io.of(this.name).emit(notification, payload); } /* * setExpressApp(app) * Sets the express app object for this module. * This allows you to host files from the created webserver. * * argument app Express app - The Express app object. */ setExpressApp (app) { this.expressApp = app; app.use(`/${this.name}`, express.static(`${this.path}/public`)); } /* * setSocketIO(io) * Sets the socket io object for this module. * Binds message receiver. * * argument io Socket.io - The Socket io object. */ setSocketIO (io) { this.io = io; Log.log(`Connecting socket for: ${this.name}`); io.of(this.name).on("connection", (socket) => { // register catch all. socket.onAny((notification, payload) => { if (config?.hideConfigSecrets && payload && typeof payload === "object") { try { const payloadStr = replaceSecretPlaceholder(JSON.stringify(payload)); this.socketNotificationReceived(notification, JSON.parse(payloadStr)); } catch (e) { Log.error("Error substituting variables in payload: ", e); this.socketNotificationReceived(notification, payload); } } else { this.socketNotificationReceived(notification, payload); } }); }); } /** * Check the status of a fetch response. * @param {Response} response The fetch response. * @returns {Response} The fetch response if ok. */ static checkFetchStatus (response) { // response.status >= 200 && response.status < 300 if (response.ok) { return response; } else { throw Error(response.statusText); } } /** * Look at the specified error and return an appropriate error type, that * can be translated to a detailed error message * @param {Error} error the error from fetching something * @returns {string} the string of the detailed error message in the translations */ static checkFetchError (error) { let error_type = "MODULE_ERROR_UNSPECIFIED"; if (error.code === "EAI_AGAIN") { error_type = "MODULE_ERROR_NO_CONNECTION"; } else { const message = typeof error.message === "string" ? error.message.toLowerCase() : ""; if (message.includes("unauthorized") || message.includes("http 401") || message.includes("http 403")) { error_type = "MODULE_ERROR_UNAUTHORIZED"; } } return error_type; } /** * Create a new NodeHelper subclass with the given module definition. * @param {object} moduleDefinition Methods and properties for the helper. * @returns {typeof NodeHelper} A new subclass of NodeHelper. */ static create (moduleDefinition) { return class extends NodeHelper { constructor () { super(); Object.assign(this, moduleDefinition); this.init(); } }; } } module.exports = NodeHelper;