From 4c0b448f00cd05f48f0aa709964adb068f56c455 Mon Sep 17 00:00:00 2001 From: Krishprajapati15 Date: Tue, 6 May 2025 12:11:12 +0530 Subject: [PATCH 1/3] refactor: modernize hooks system with async/await and native fetch --- lib/hooks.js | 144 ++++++++++++++++++++++++++++----------------------- 1 file changed, 78 insertions(+), 66 deletions(-) diff --git a/lib/hooks.js b/lib/hooks.js index 540e739a..10dd01b1 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -1,88 +1,100 @@ -var Q = require('q'); -var _ = require('lodash'); -var request = require('request'); - -var logger = require("./utils/logger")("hooks"); - -var BOXID = null; -var HOOKS = {}; -var POSTHOOKS = { - 'users.auth': function(data) { - // Valid data - if (!_.has(data, "id") || !_.has(data, "name") - || !_.has(data, "token") || !_.has(data, "email")) { - throw "Invalid authentication data"; - } +const _ = require("lodash"); +const logger = require("./utils/logger")("hooks"); + +let BOXID = null; +let HOOKS = {}; +let SECRET_TOKEN = null; +const POSTHOOKS = { + "users.auth": function (data) { + if ( + !_.has(data, "id") || + !_.has(data, "name") || + !_.has(data, "token") || + !_.isString(data.email) + ) { + throw new Error("Invalid authentication data"); + } return data; - } + }, }; -var SECRET_TOKEN = null; // Call hook -var use = function(hook, data) { - logger.log("call hook ", hook); - - if (!HOOKS[hook]) return Q.reject("Hook '"+hook+"' doesn't exists"); - - return Q() - .then(function() { - var handler = HOOKS[hook]; - - if (_.isFunction(handler)) { - return Q(handler(data)); - } else if (_.isString(handler)) { - var d = Q.defer(); - - // Do http requests - request.post(handler, { - 'body': { - 'id': BOXID, - 'data': data, - 'hook': hook - }, - 'headers': { - 'Authorization': SECRET_TOKEN +const use = async function (hook, data) { + logger.log("call hook", hook); + + if (!HOOKS[hook]) { + throw new Error(`Hook '${hook}' doesn't exist`); + } + + let result; + + const handler = HOOKS[hook]; + + if (_.isFunction(handler)) { + result = await handler(data); + } else if (_.isString(handler)) { + // Remote HTTP hook + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 5000); // 5 sec timeout + + try { + const response = await fetch(handler, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: SECRET_TOKEN, }, - 'json': true, - }, function (error, response, body) { - if (!error && response.statusCode == 200) { - d.resolve(body); - } else { - d.reject(new Error("Error with "+hook+" webhook: "+(body ? (body.error || body) : error.message))); - } + body: JSON.stringify({ + id: BOXID, + data, + hook, + }), + signal: controller.signal, }); - return d.promise; - } else { - throw "Not a valid hook"; - } - }) - .then(function(data) { - if (POSTHOOKS[hook]) { - return POSTHOOKS[hook](data); + clearTimeout(timeout); + + if (!response.ok) { + const errBody = await response.text(); + throw new Error(`Error with ${hook} webhook: ${errBody}`); + } + + result = await response.json(); + } catch (error) { + logger.error(`Error calling webhook ${hook}:`, error.message); + throw new Error(`Error with ${hook} webhook: ${error.message}`); } - return data; - }) - .fail(function(err) { - logger.error("Error with hook:"); - logger.exception(err, false); + } else { + throw new Error("Not a valid hook type"); + } + + // Post-processing hook + if (POSTHOOKS[hook]) { + result = POSTHOOKS[hook](result); + } - return Q.reject(err); - }); + return result; }; // Init hook system -var init = function(options) { +const init = function (options) { logger.log("init hooks"); + if (!_.isObject(options) || !_.isObject(options.hooks)) { + throw new Error('Invalid options: "hooks" must be an object'); + } + + if (!_.isString(options.id) || !_.isString(options.secret)) { + throw new Error('Invalid options: "id" and "secret" must be strings'); + } + BOXID = options.id; HOOKS = options.hooks; SECRET_TOKEN = options.secret; }; module.exports = { - init: init, - use: use + init, + use, }; - From 121f0de00d24572532b2d382fd1bcfa065fb2a79 Mon Sep 17 00:00:00 2001 From: Krishprajapati15 Date: Tue, 6 May 2025 12:14:25 +0530 Subject: [PATCH 2/3] Refactor socket module for improved readability and robustness --- lib/socket.js | 87 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 31 deletions(-) diff --git a/lib/socket.js b/lib/socket.js index 6cfcf497..2800a32f 100644 --- a/lib/socket.js +++ b/lib/socket.js @@ -1,66 +1,91 @@ -var Q = require('q'); -var _ = require('lodash'); -var sockjs = require('sockjs'); -var events = require('events'); +const Q = require("q"); +const _ = require("lodash"); +const sockjs = require("sockjs"); +const events = require("events"); -var logger = require('./utils/logger')("socket"); +const logger = require("./utils/logger")("socket"); -var services = {}; +const services = {}; -var init = function(server, config) { - var socket = sockjs.createServer({ - log: logger.log.bind(logger) +/** + * Initialize the socket server. + * @param {Object} server - The HTTP server instance. + * @param {Object} config - Configuration object. + */ +const init = (server, config) => { + const socket = sockjs.createServer({ + log: logger.log.bind(logger), }); - socket.on('connection', function(conn) { - var service = (conn.pathname.split("/"))[2]; - logger.log("connection to service '"+service+"'"); + socket.on("connection", (conn) => { + const service = conn.pathname.split("/")[2]; // Extract service name from URL. + logger.log(`Connection to service '${service}'`); + // Check if the service exists. if (!services[service]) { conn.close(404, "Service not found"); - return logger.error("invalid service '"+service+"'"); + return logger.error(`Invalid service '${service}'`); } - conn.do = function(method, data) { - this.write(JSON.stringify({ - 'method': method, - 'data': data - })); - }.bind(conn); + // Attach a helper method to send data. + conn.do = (method, data) => { + conn.write( + JSON.stringify({ + method, + data, + }) + ); + }; - conn.on("data", function(data) { + conn.on("data", (data) => { + // Parse incoming data. try { data = JSON.parse(data); - } catch(e) { - logger.error("error parsing data:", data); - return; + } catch (e) { + logger.error("Error parsing data:", data, e.message); + return conn.do("error", { message: "Invalid JSON format" }); } + // Check if the data contains a method. if (data.method) { - conn.emit("do."+data.method, data.data || {}); + conn.emit(`do.${data.method}`, data.data || {}); } else { conn.emit("message", data); } }); + // Call the service handler. services[service].handler(conn); }); + // Install socket handlers with a proper regex prefix. socket.installHandlers(server, { - prefix: '^/socket/(\\w+)' + prefix: "/socket/\\w+", }); }; -var addService = function(name, handler) { - logger.log("add service", name); +/** + * Add a new service to the socket server. + * @param {string} name - The name of the service. + * @param {Function} handler - The handler function for the service. + */ +const addService = (name, handler) => { + if ( + !name || + typeof name !== "string" || + !handler || + typeof handler !== "function" + ) { + throw new Error("Invalid service name or handler function."); + } + logger.log("Adding service:", name); services[name] = { - handler: handler + handler, }; }; - module.exports = { - init: init, - service: addService + init, + service: addService, }; From d13f0dc4a545401ed18b96b43e3c8c784986d382 Mon Sep 17 00:00:00 2001 From: Krishprajapati15 Date: Tue, 6 May 2025 12:16:08 +0530 Subject: [PATCH 3/3] Enhance hook system with improved error handling and modern JavaScript --- lib/hooks.js | 55 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/lib/hooks.js b/lib/hooks.js index 10dd01b1..c9f1069a 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -5,6 +5,9 @@ let BOXID = null; let HOOKS = {}; let SECRET_TOKEN = null; +const DEFAULT_TIMEOUT = 5000; // Default timeout for HTTP requests (in milliseconds) + +// Post-processing hooks for specific events const POSTHOOKS = { "users.auth": function (data) { if ( @@ -19,24 +22,30 @@ const POSTHOOKS = { }, }; -// Call hook -const use = async function (hook, data) { - logger.log("call hook", hook); +/** + * Call a hook. + * @param {string} hook - The name of the hook to call. + * @param {Object} data - The data to pass to the hook. + * @returns {Promise} - Resolves with the result of the hook. + * @throws {Error} - Throws an error if the hook does not exist or fails. + */ +const use = async (hook, data) => { + logger.log(`Calling hook: '${hook}'`); if (!HOOKS[hook]) { - throw new Error(`Hook '${hook}' doesn't exist`); + throw new Error(`Hook '${hook}' does not exist`); } let result; - const handler = HOOKS[hook]; if (_.isFunction(handler)) { + // Local function hook result = await handler(data); } else if (_.isString(handler)) { // Remote HTTP hook const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 5000); // 5 sec timeout + const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT); try { const response = await fetch(handler, { @@ -53,7 +62,7 @@ const use = async function (hook, data) { signal: controller.signal, }); - clearTimeout(timeout); + clearTimeout(timeout); // Clear the timeout after the request completes if (!response.ok) { const errBody = await response.text(); @@ -62,24 +71,42 @@ const use = async function (hook, data) { result = await response.json(); } catch (error) { - logger.error(`Error calling webhook ${hook}:`, error.message); + clearTimeout(timeout); // Ensure timeout is cleared on error + logger.error(`Error calling webhook '${hook}':`, error.message); throw new Error(`Error with ${hook} webhook: ${error.message}`); } } else { - throw new Error("Not a valid hook type"); + throw new Error(`Invalid hook type for '${hook}'`); } // Post-processing hook if (POSTHOOKS[hook]) { - result = POSTHOOKS[hook](result); + try { + result = POSTHOOKS[hook](result); + } catch (error) { + logger.error( + `Error in post-processing for hook '${hook}':`, + error.message + ); + throw new Error( + `Post-processing error for '${hook}': ${error.message}` + ); + } } return result; }; -// Init hook system -const init = function (options) { - logger.log("init hooks"); +/** + * Initialize the hook system. + * @param {Object} options - Configuration options. + * @param {string} options.id - The unique ID for the system. + * @param {Object} options.hooks - An object defining all hooks. + * @param {string} options.secret - The secret token for authorization. + * @throws {Error} - Throws an error if the options are invalid. + */ +const init = (options) => { + logger.log("Initializing hooks"); if (!_.isObject(options) || !_.isObject(options.hooks)) { throw new Error('Invalid options: "hooks" must be an object'); @@ -92,6 +119,8 @@ const init = function (options) { BOXID = options.id; HOOKS = options.hooks; SECRET_TOKEN = options.secret; + + logger.log(`Hooks initialized with ID: '${BOXID}'`); }; module.exports = {