UNPKG

node-pluginsmanager-plugin

Version:
650 lines (649 loc) 33.3 kB
"use strict"; // deps var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); // natives const node_url_1 = require("node:url"); const node_os_1 = require("node:os"); // externals const uniqid_1 = __importDefault(require("uniqid")); // locals const checkInteger_1 = require("../checkers/TypeError/checkInteger"); const checkNonEmptyObject_1 = require("../checkers/RangeError/checkNonEmptyObject"); const checkNonEmptyString_1 = require("../checkers/RangeError/checkNonEmptyString"); const extractPattern_1 = __importDefault(require("../utils/descriptor/extractPattern")); const extractParams_1 = __importDefault(require("../utils/descriptor/extractParams")); const extractSchemaType_1 = __importDefault(require("../utils/descriptor/extractSchemaType")); const extractBody_1 = __importDefault(require("../utils/request/extractBody")); const extractIp_1 = __importDefault(require("../utils/request/extractIp")); const extractCookies_1 = __importDefault(require("../utils/request/extractCookies")); const extractMime_1 = __importDefault(require("../utils/request/extractMime")); const jsonParser_1 = __importDefault(require("../utils/jsonParser")); const send_1 = __importDefault(require("../utils/send")); const cleanSendedError_1 = __importDefault(require("../utils/cleanSendedError")); const MediatorUser_1 = __importDefault(require("./MediatorUser")); const UnauthorizedError_1 = __importDefault(require("./errors/UnauthorizedError")); const NotFoundError_1 = __importDefault(require("./errors/NotFoundError")); const LockedError_1 = __importDefault(require("./errors/LockedError")); const serverCodes_1 = __importDefault(require("../utils/serverCodes")); // consts const WEBSOCKET_STATE_OPEN = 1; // module // Please note the fact that "init" and "release" method MUST NOT be re-writted. Each child has is own init logic. class Server extends MediatorUser_1.default { // constructor constructor(opt) { super(opt); this._socketServer = null; this._checkParameters = true; this._checkResponse = false; this._cors = false; } // protected _serverType() { if (!this._socketServer) { return "NO_SERVER"; } else if (this._socketServer.clients && "function" === typeof this._socketServer.clients.forEach) { return "WEBSOCKET"; } else if (this._socketServer.sockets && "function" === typeof this._socketServer.sockets.emit) { return "SOCKETIO"; } else { return "UNKNOWN"; } } _getUsableSocketIOClient(clientId) { var _a, _b; if ("SOCKETIO" === this._serverType()) { let socket = null; if ("function" !== typeof ((_b = (_a = this._socketServer.sockets) === null || _a === void 0 ? void 0 : _a.sockets) === null || _b === void 0 ? void 0 : _b.has)) { // SocketIO V2 for (const key in this._socketServer.sockets.sockets) { if (key === clientId) { socket = this._socketServer.sockets.sockets[key]; break; } } } else if (this._socketServer.sockets.sockets.has(clientId)) { // SocketIO V3&4 socket = this._socketServer.sockets.sockets.get(clientId); } if (socket && socket.connected) { return socket; } } return undefined; } // public disableCheckParameters() { this._checkParameters = false; return this; } enableCheckParameters() { this._checkParameters = true; return this; } disableCheckResponse() { this._checkResponse = false; return this; } enableCheckResponse() { this._checkResponse = true; return this; } disableCors() { this._cors = false; return this; } enableCors() { this._cors = true; return this; } appMiddleware(req, res, next) { if (!this._Descriptor) { return next(); } if (!this._Descriptor.paths) { return next(); } this.checkDescriptor().then(() => { var _a, _b, _c; // parse const { pathname, query } = (0, node_url_1.parse)(req.url, true); req.method = req.method ? req.method.toLowerCase() : "get"; req.pattern = null === (0, checkNonEmptyString_1.checkNonEmptyStringSync)("pattern", req.pattern) ? req.pattern : (0, extractPattern_1.default)(this._Descriptor.paths, pathname, req.method); if (!req.pattern) { return next(); } req.validatedIp = null === (0, checkNonEmptyString_1.checkNonEmptyStringSync)("ip", req.validatedIp) ? req.validatedIp : (0, extractIp_1.default)(req); // url req.params = null === (0, checkNonEmptyObject_1.checkNonEmptyObjectSync)("params", req.params) ? req.params : (0, extractParams_1.default)(req.pattern, pathname); req.query = null === (0, checkNonEmptyObject_1.checkNonEmptyObjectSync)("query", req.query) ? req.query : query !== null && query !== void 0 ? query : {}; if (null !== (0, checkNonEmptyObject_1.checkNonEmptyObjectSync)("headers", req.headers)) { req.headers = null === (0, checkNonEmptyObject_1.checkNonEmptyObjectSync)("header", req.header) ? req.header : {}; } if (null !== (0, checkNonEmptyObject_1.checkNonEmptyObjectSync)("cookies", req.cookies)) { req.cookies = null === (0, checkNonEmptyObject_1.checkNonEmptyObjectSync)("header", req.cookie) ? req.cookie : (0, extractCookies_1.default)(req); } // ensure content length formate if ("undefined" === typeof req.headers["content-length"]) { req.headers["content-length"] = 0; } else if ("string" === typeof req.headers["content-length"]) { req.headers["content-length"] = parseInt(req.headers["content-length"], 10); } if (!req.pattern || !this._Descriptor.paths[req.pattern] || !this._Descriptor.paths[req.pattern][req.method]) { return next(); } const { operationId } = this._Descriptor.paths[req.pattern][req.method]; const apiVersion = this._Descriptor.info.version; const contentType = (_b = (_a = req.headers["content-type"]) !== null && _a !== void 0 ? _a : req.headers["Content-Type"]) !== null && _b !== void 0 ? _b : ""; const responses = this._Descriptor.paths[req.pattern][req.method].responses; this._log("info", "" + "=> [" + req.validatedIp + "] " + req.url + " (" + req.method.toUpperCase() + ")" + (operationId ? node_os_1.EOL + "operationId : " + operationId : "") + (req.headers["content-type"] ? node_os_1.EOL + "content-type : " + req.headers["content-type"] : "") + ("get" !== req.method && req.headers["content-length"] && 4 < req.headers["content-length"] ? node_os_1.EOL + "content-length : " + req.headers["content-length"] : "")); // get descriptor if ("/" + this._Descriptor.info.title + "/api/descriptor" === req.pattern && "get" === req.method) { // add current server const port = res.socket && res.socket.localPort ? res.socket.localPort : ((_c = res.socket) === null || _c === void 0 ? void 0 : _c.address()).port; const descriptor = Object.assign({}, this._Descriptor); descriptor.servers.push({ "url": req.validatedIp + ":" + port, "description": "Actual current server" }); const content = JSON.stringify(descriptor); this._log("info", "<= [" + req.validatedIp + "] " + content); return (0, send_1.default)(req, res, serverCodes_1.default.OK, content, { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCodes_1.default.OK, responses) }).catch((err) => { this._log("error", err); const result = JSON.stringify({ "code": "INTERNAL_SERVER_ERROR", "message": (0, cleanSendedError_1.default)(err) }); this._log("error", "<= [" + req.validatedIp + "] " + result); return (0, send_1.default)(req, res, serverCodes_1.default.INTERNAL_SERVER_ERROR, result, { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCodes_1.default.INTERNAL_SERVER_ERROR, responses) }); }); } // get plugin status else if ("/" + this._Descriptor.info.title + "/api/status" === req.pattern && "get" === req.method) { const initialized = this.initialized && this._Mediator.initialized; const status = initialized ? "INITIALIZED" : "ENABLED"; this._log("info", "<= [" + req.validatedIp + "] " + status); return (0, send_1.default)(req, res, serverCodes_1.default.OK, JSON.stringify(status), { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCodes_1.default.OK, responses) }); } // missing operationId else if (!operationId) { const result = JSON.stringify({ "code": "NOT_IMPLEMENTED", "message": "Missing \"operationId\" in the Descriptor for this request" }); this._log("error", "<= [" + req.validatedIp + "] " + result); return (0, send_1.default)(req, res, serverCodes_1.default.NOT_IMPLEMENTED, result, { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCodes_1.default.NOT_IMPLEMENTED, responses) }); } // not implemented operationId else if ("function" !== typeof this._Mediator[operationId]) { const result = JSON.stringify({ "code": "NOT_IMPLEMENTED", "message": "Unknown Mediator's \"operationId\" method for this request" }); this._log("error", "<= [" + req.validatedIp + "] " + result); return (0, send_1.default)(req, res, serverCodes_1.default.NOT_IMPLEMENTED, result, { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCodes_1.default.NOT_IMPLEMENTED, responses) }); } // no "Content-Length" header found else if ("get" !== req.method && null !== (0, checkInteger_1.checkIntegerSync)("headers[\"content-length\"]", req.headers["content-length"])) { const result = JSON.stringify({ "code": "MISSING_HEADER", "message": "No valid \"Content-Length\" header found" }); this._log("error", "<= [" + req.validatedIp + "] " + result); return (0, send_1.default)(req, res, serverCodes_1.default.MISSING_HEADER, result, { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCodes_1.default.MISSING_HEADER, responses) }); } else { // force formate for path parameters return Promise.resolve().then(() => { const keys = Object.keys(req.params); return !keys.length ? Promise.resolve() : Promise.resolve().then(() => { const docParameters = this._Descriptor.paths[req.pattern][req.method].parameters.filter((p) => { return "path" === p.in; }); return !docParameters.length ? Promise.resolve() : Promise.resolve().then(() => { var _a, _b; let err = null; for (let i = 0; i < keys.length; ++i) { const key = keys[i]; const schema = (_b = (_a = docParameters.find((dp) => { return dp.name === key; })) === null || _a === void 0 ? void 0 : _a.schema) !== null && _b !== void 0 ? _b : null; if (!schema) { err = new ReferenceError("Unknown parameter: request.params['" + key + "']"); break; } switch ((0, extractSchemaType_1.default)(schema, this._Descriptor.components.schemas)) { case "boolean": if ("boolean" !== typeof req.params[key]) { if ("true" === req.params[key]) { req.params[key] = true; } else if ("false" === req.params[key]) { req.params[key] = false; } else { err = new TypeError("Error while validating request: request.params['" + key + "'] should be boolean"); break; } } break; case "integer": // error returned, not an integer if (null !== (0, checkInteger_1.checkIntegerSync)("request.params['" + key + "']", req.params[key])) { const value = parseInt(req.params[key], 10); if (!Number.isNaN(value)) { req.params[key] = value; } else { err = new TypeError("Error while validating request: request.params['" + key + "'] should be integer"); break; } } break; case "number": if ("number" !== typeof req.params[key]) { const value = parseFloat(req.params[key]); if (!Number.isNaN(value)) { req.params[key] = value; } else { err = new TypeError("Error while validating request: request.params['" + key + "'] should be number"); break; } } break; default: // nothing to do here break; } } return err ? Promise.reject(err) : Promise.resolve(); }); }); // extract body }).then(() => { if ("get" === req.method.toLowerCase()) { return Promise.resolve(""); } else if (!(0, checkNonEmptyString_1.checkNonEmptyStringSync)("body", req.body)) { return Promise.resolve(req.body); } else { return (0, extractBody_1.default)(req).then((body) => { if (body.length) { this._log("log", body); req.body = (0, jsonParser_1.default)(body); } else { req.body = ""; } return Promise.resolve(req.body); }); } // formate data }).then((body) => { const parsed = { "url": { "path": req.params, "query": req.query, "headers": req.headers, "cookies": req.cookies }, body }; // check parameters return Promise.resolve().then(() => { return this._checkParameters ? this._Mediator.checkParameters(operationId, parsed.url, parsed.body) : Promise.resolve(); // execute Mediator method }).then(() => { return this._Mediator[operationId](parsed.url, parsed.body); }); // send response }).then((content) => { // no content if ("undefined" === typeof content || null === content) { this._log("success", "<= [" + req.validatedIp + "] no content"); const serverCode = "put" === req.method ? serverCodes_1.default.OK_PUT : serverCodes_1.default.OK_NO_CONTENT; return (0, send_1.default)(req, res, serverCode, "", { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCode, responses) }); } else { if (Buffer.isBuffer(content)) { this._log("success", "<= [" + req.validatedIp + "] <" + contentType + "> Buffer"); } else if ("application/json" === contentType) { this._log("success", "<= [" + req.validatedIp + "] <" + contentType + "> " + JSON.stringify(content)); } else if (["application/xml", "text/plain"].includes(contentType)) { this._log("success", "<= [" + req.validatedIp + "] <" + contentType + "> " + content); } else { this._log("success", "<= [" + req.validatedIp + "] <" + contentType + "> ..."); } const serverCode = "put" === req.method ? serverCodes_1.default.OK_PUT : serverCodes_1.default.OK; return (0, send_1.default)(req, res, serverCode, content, { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCode, responses) }); } }).catch((err) => { if (err instanceof ReferenceError) { const result = JSON.stringify({ "code": "MISSING_PARAMETER", "message": (0, cleanSendedError_1.default)(err) }); this._log("error", "<= [" + req.validatedIp + "] " + result); return (0, send_1.default)(req, res, serverCodes_1.default.MISSING_PARAMETER, result, { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCodes_1.default.MISSING_PARAMETER, responses) }); } else if (err instanceof TypeError) { const result = JSON.stringify({ "code": "WRONG_TYPE_PARAMETER", "message": (0, cleanSendedError_1.default)(err) }); this._log("error", "<= [" + req.validatedIp + "] " + result); return (0, send_1.default)(req, res, serverCodes_1.default.WRONG_TYPE_PARAMETER, result, { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCodes_1.default.WRONG_TYPE_PARAMETER, responses) }); } else if (err instanceof RangeError) { const result = JSON.stringify({ "code": "EMPTY_OR_RANGE_OR_ENUM_PARAMETER", "message": (0, cleanSendedError_1.default)(err) }); this._log("error", "<= [" + req.validatedIp + "] " + result); return (0, send_1.default)(req, res, serverCodes_1.default.EMPTY_OR_RANGE_OR_ENUM_PARAMETER, result, { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCodes_1.default.EMPTY_OR_RANGE_OR_ENUM_PARAMETER, responses) }); } else if (err instanceof SyntaxError) { const result = JSON.stringify({ "code": "JSON_PARSE", "message": (0, cleanSendedError_1.default)(err) }); this._log("error", "<= [" + req.validatedIp + "] " + result); return (0, send_1.default)(req, res, serverCodes_1.default.JSON_PARSE, result, { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCodes_1.default.JSON_PARSE, responses) }); } else if (err instanceof UnauthorizedError_1.default) { const result = JSON.stringify({ "code": "UNAUTHORIZED", "message": (0, cleanSendedError_1.default)(err) }); this._log("error", "<= [" + req.validatedIp + "] " + result); return (0, send_1.default)(req, res, serverCodes_1.default.UNAUTHORIZED, result, { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCodes_1.default.UNAUTHORIZED, responses) }); } else if (err instanceof NotFoundError_1.default) { const result = JSON.stringify({ "code": "NOT_FOUND", "message": (0, cleanSendedError_1.default)(err) }); this._log("error", "<= [" + req.validatedIp + "] " + result); return (0, send_1.default)(req, res, serverCodes_1.default.NOT_FOUND, result, { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCodes_1.default.NOT_FOUND, responses) }); } else if (err instanceof LockedError_1.default) { const result = JSON.stringify({ "code": "LOCKED", "message": (0, cleanSendedError_1.default)(err) }); this._log("error", "<= [" + req.validatedIp + "] " + result); return (0, send_1.default)(req, res, serverCodes_1.default.LOCKED, result, { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCodes_1.default.LOCKED, responses) }); } else { this._log("error", err); const result = JSON.stringify({ "code": "INTERNAL_SERVER_ERROR", "message": (0, cleanSendedError_1.default)(err) }); this._log("error", "<= [" + req.validatedIp + "] " + result); return (0, send_1.default)(req, res, serverCodes_1.default.INTERNAL_SERVER_ERROR, result, { "apiVersion": apiVersion, "cors": this._cors, "mime": (0, extractMime_1.default)(contentType, serverCodes_1.default.INTERNAL_SERVER_ERROR, responses) }); } // check response }).then(() => { return this._checkResponse ? this._Mediator.checkResponse(operationId, res) : Promise.resolve(); }); } }).then(() => { // nothing to do here // must not be blocking in http server or express app }).catch((err) => { this._log("error", err); }); } socketMiddleware(socketServer) { this._socketServer = socketServer; } push(command, data, log = true) { const serverType = this._serverType(); if (!["WEBSOCKET", "SOCKETIO"].includes(serverType)) { return this; } // valid descriptor && formate data this.checkDescriptor().then(() => { const result = { "id": (0, uniqid_1.default)(), "plugin": this._Descriptor.info.title, command }; if ("undefined" !== typeof data) { result.data = (0, cleanSendedError_1.default)(data); } return Promise.resolve(JSON.stringify(result)); // log & send data }).then((result) => { if (log) { this._log("info", "<= [PUSH] " + result, true); } switch (serverType) { case "WEBSOCKET": this._socketServer.clients.forEach((client) => { if (!client.id) { client.id = (0, uniqid_1.default)(); } if (WEBSOCKET_STATE_OPEN === client.readyState) { client.send(result); } }); break; case "SOCKETIO": this._socketServer.sockets.emit("message", result); break; default: // nothing to do here break; } }).catch((err) => { this._log("error", err); }); return this; } getClients() { var _a, _b; switch (this._serverType()) { case "WEBSOCKET": { const result = []; this._socketServer.clients.forEach((s) => { if (!s.id) { s.id = (0, uniqid_1.default)(); } return { "id": s.id, "status": WEBSOCKET_STATE_OPEN === s.readyState ? "CONNECTED" : "DISCONNECTED" }; }); return result; } case "SOCKETIO": { const result = []; if ("function" !== typeof ((_b = (_a = this._socketServer.sockets) === null || _a === void 0 ? void 0 : _a.sockets) === null || _b === void 0 ? void 0 : _b.has)) { // SocketIO V2 for (const key in this._socketServer.sockets.sockets) { const s = this._socketServer.sockets.sockets[key]; result.push({ "id": s.id, "status": s.connected ? "CONNECTED" : "DISCONNECTED" }); } } else { this._socketServer.sockets.sockets.forEach((s) => { result.push({ "id": s.id, "status": s.connected ? "CONNECTED" : "DISCONNECTED" }); }); } return result; } default: return []; } } pushClient(clientId, command, data, log = true) { const serverType = this._serverType(); if (!["WEBSOCKET", "SOCKETIO"].includes(serverType)) { return this; } // valid descriptor && formate data this.checkDescriptor().then(() => { const result = { "id": (0, uniqid_1.default)(), "plugin": this._Descriptor.info.title, command }; if ("undefined" !== typeof data) { result.data = (0, cleanSendedError_1.default)(data); } return Promise.resolve(JSON.stringify(result)); // log & send data }).then((result) => { switch (serverType) { case "WEBSOCKET": this._socketServer.clients.forEach((client) => { if (!client.id) { client.id = (0, uniqid_1.default)(); } else if (client.id === clientId && WEBSOCKET_STATE_OPEN === client.readyState) { if (log) { this._log("info", "<= [PUSH|" + clientId + "] " + result); } client.send(result); } }); break; case "SOCKETIO": { const socket = this._getUsableSocketIOClient(clientId); if (socket) { if (log) { this._log("info", "<= [PUSH|" + clientId + "] " + result); } socket.emit("message", result); } } break; default: // nothing to do here break; } }).catch((err) => { this._log("error", err); }); return this; } // init / release init(...data) { return this.checkDescriptor().then(() => { return this.checkMediator(); }).then(() => { return this._initWorkSpace(...data); }).then(() => { this.initialized = true; this.emit("initialized", ...data); }); } release(...data) { return this._releaseWorkSpace(...data).then(() => { // can only be released by Orchestrator if (this._Descriptor) { this._Descriptor = null; } // can only be released by Orchestrator if (this._Mediator) { this._Mediator = null; } this._externalRessourcesDirectory = ""; this._socketServer = null; this.initialized = false; this.emit("released", ...data); }).then(() => { this.removeAllListeners(); }); } } exports.default = Server;