diff --git a/lib/hooks.js b/lib/hooks.js index 540e739a..c9f1069a 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -1,88 +1,129 @@ -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 DEFAULT_TIMEOUT = 5000; // Default timeout for HTTP requests (in milliseconds) +// Post-processing hooks for specific events +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); +/** + * 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]) return Q.reject("Hook '"+hook+"' doesn't exists"); + if (!HOOKS[hook]) { + throw new Error(`Hook '${hook}' does not exist`); + } - return Q() - .then(function() { - var handler = HOOKS[hook]; + let result; + const handler = HOOKS[hook]; - if (_.isFunction(handler)) { - return Q(handler(data)); - } else if (_.isString(handler)) { - var d = Q.defer(); + 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(), DEFAULT_TIMEOUT); - // Do http requests - request.post(handler, { - 'body': { - 'id': BOXID, - 'data': data, - 'hook': hook - }, - 'headers': { - 'Authorization': SECRET_TOKEN + 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"; + clearTimeout(timeout); // Clear the timeout after the request completes + + if (!response.ok) { + const errBody = await response.text(); + throw new Error(`Error with ${hook} webhook: ${errBody}`); + } + + result = await response.json(); + } catch (error) { + 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}`); } - }) - .then(function(data) { - if (POSTHOOKS[hook]) { - return POSTHOOKS[hook](data); + } else { + throw new Error(`Invalid hook type for '${hook}'`); + } + + // Post-processing hook + if (POSTHOOKS[hook]) { + 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 data; - }) - .fail(function(err) { - logger.error("Error with hook:"); - logger.exception(err, false); + } - return Q.reject(err); - }); + return result; }; -// Init hook system -var 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'); + } + + 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; + + logger.log(`Hooks initialized with ID: '${BOXID}'`); }; module.exports = { - init: init, - use: use + init, + use, }; - 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, };