From 67db41cfcb04a3d934a166444ee68df38dd0db9e Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Fri, 8 May 2026 19:45:47 +0200 Subject: [PATCH] refactor: rewrite NodeHelper as an ES6 class (#4147) This PR rewrites `node_helper.js` to use a native ES6 `class` instead of `Class.extend()` - a manual inheritance helper from 2008, written back when `class` syntax didn't exist yet. Node.js has supported native classes since v6, so there's no reason to keep the workaround around. The public API is unchanged - module authors still write the same `NodeHelper.create({...})` they always have. Outcome: same behavior, normal modern JavaScript, better stack traces, and `node_helper.js` no longer pulls in `class.js` on the server side. Removing `class.js` altogether is a follow-up (it's still used on browser side). --- js/node_helper.js | 95 +++++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/js/node_helper.js b/js/node_helper.js index 506f1d85..833c90aa 100644 --- a/js/node_helper.js +++ b/js/node_helper.js @@ -1,20 +1,19 @@ const express = require("express"); const Log = require("logger"); -const Class = require("./class"); const { replaceSecretPlaceholder } = require("#server_functions"); -const NodeHelper = Class.extend({ +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` @@ -23,7 +22,7 @@ const NodeHelper = Class.extend({ */ stop () { Log.log(`Stopping module helper: ${this.name}`); - }, + } /** * This method is called when a socket notification arrives. @@ -32,7 +31,7 @@ const NodeHelper = Class.extend({ */ socketNotificationReceived (notification, payload) { Log.log(`${this.name} received a socket notification: ${notification} - Payload: ${payload}`); - }, + } /** * Set the module name. @@ -40,7 +39,7 @@ const NodeHelper = Class.extend({ */ setName (name) { this.name = name; - }, + } /** * Set the module path. @@ -48,7 +47,7 @@ const NodeHelper = Class.extend({ */ setPath (path) { this.path = path; - }, + } /* * sendSocketNotification(notification, payload) @@ -59,7 +58,7 @@ const NodeHelper = Class.extend({ */ sendSocketNotification (notification, payload) { this.io.of(this.name).emit(notification, payload); - }, + } /* * setExpressApp(app) @@ -72,7 +71,7 @@ const NodeHelper = Class.extend({ this.expressApp = app; app.use(`/${this.name}`, express.static(`${this.path}/public`)); - }, + } /* * setSocketIO(io) @@ -103,38 +102,54 @@ const NodeHelper = Class.extend({ }); }); } -}); -NodeHelper.checkFetchStatus = function (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 - */ -NodeHelper.checkFetchError = function (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"; + /** + * 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); } } - return error_type; -}; -NodeHelper.create = function (moduleDefinition) { - return NodeHelper.extend(moduleDefinition); -}; + /** + * 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;