UNPKG

@tulip/node-red-tulip-api

Version:

Node-RED nodes for sending data to the Tulip API

176 lines (152 loc) 5.71 kB
module.exports = function (RED) { 'use strict'; const tulipTables = require('./static/tulip_tables_common'); const { doHttpRequest, getHttpAgent, getHttpLib } = require('./utils'); // Tulip API node function TablesNode(config) { RED.nodes.createNode(this, config); // Set node properties this.name = config.name; this.apiAuth = RED.nodes.getNode(config.apiAuth); this.config = config; // Legacy nodes did not allow retaining msg props, so if property is missing default to false this.retainMsgProps = 'retainMsgProps' in config ? config.retainMsgProps : false; // Use http or https depending on the factory protocol const httpLib = getHttpLib(this.apiAuth.protocol); this.agent = getHttpAgent(httpLib, config.keepAlive, config.keepAliveMsecs); const node = this; const queryInfo = tulipTables.TABLE_QUERY_TYPES[config.queryType]; const hasBody = queryInfo.method == 'POST' || queryInfo.method == 'PUT' || queryInfo.method == 'PATCH'; // Handle node inputs node.on('input', function (msg, send, done) { try { // Get all relevant parameters, overriding config value with msg if set const pathParams = {}; const queryParams = {}; for (const p of queryInfo.pathParams) { pathParams[p] = getParamVal(p, msg); } for (const p of queryInfo.queryParams) { queryParams[p] = getParamVal(p, msg); } // Create URL const reqUrl = getApiUrl( node.apiAuth.protocol, node.apiAuth.hostname, node.apiAuth.port, pathParams, queryParams, ); // Configure request with auth const options = { method: queryInfo.method, auth: `${node.apiAuth.credentials.apiKey}:${node.apiAuth.credentials.apiSecret}`, agent: node.agent, headers: getHeaders(hasBody, msg.headers), }; let body; if (hasBody) { // Send the message body const rawBody = getParamVal('body', msg); body = JSON.stringify(rawBody); } // Decide whether to pass the input msg params to the output msg const sendMsg = node.retainMsgProps ? (newMsg) => { send({ ...msg, ...newMsg, }); } : send; // Create, send, handle, and close HTTP request doHttpRequest(httpLib, reqUrl, options, body, node.error.bind(node), sendMsg, done); } catch (err) { // Catch unhandled errors so node-red doesn't crash done(err); } }); // Returns the headers object; if a request with a body sets the // content-type to application/json const getHeaders = function (hasBody, headers) { if (!headers) { // Initialize headers object if none exist headers = {}; } if (hasBody) { // Set content-type to some form of application/json if not already const oldContentType = headers['content-type']; if (oldContentType && !oldContentType.includes('application/json')) { node.warn( `Overriding header 'content-type'='${oldContentType}'; must be 'application/json'`, ); headers['content-type'] = 'application/json'; } else if (!oldContentType) { headers['content-type'] = 'application/json'; } } return headers; }; const getApiUrl = function (protocol, hostname, port, pathParams, queryParams) { // start with valid protocol & host const baseUrl = `${protocol}://${hostname}`; const url = new URL(baseUrl); // build the path with encoded path params const encodedPathParams = { ...pathParams }; Object.entries(encodedPathParams).forEach( ([key, value]) => (encodedPathParams[key] = encodeURIComponent(value)), ); const encodedPath = '/api/v3' + queryInfo.pathConstructor(encodedPathParams); // build the url url.port = port; url.pathname = encodedPath; for (const queryParam in queryParams) { const queryParamVal = queryParams[queryParam]; if (queryParamVal != undefined) { url.searchParams.set(queryParam, queryParamVal); } } return url; }; const getParamVal = function (p, msg) { const msgVal = msg[p]; const configVal = node.config[p]; // Take parameter from msg if (msgVal != undefined) { if (Array.isArray(msgVal)) { // encode array as string return JSON.stringify(msgVal); } else { return msgVal; } // Take parameter from config } else if (configVal != undefined && configVal != null) { if (p == 'sortBy' && configVal == 'other') { // sortBy is special case, if 'other' get the value return node.config['sortByFieldId']; } else if (Array.isArray(configVal)) { // encode array as string return JSON.stringify(configVal); } else if (p == 'body') { // Convert JSON string to object return JSON.parse(configVal); } else { return configVal; } } else { return undefined; } }; } // Register the node RED.nodes.registerType('tulip-tables', TablesNode); // Host files in /static/ at /node-red-tulip-edge/js/ so they are accessible by browser code RED.httpAdmin.get('/node-red-tulip-edge/js/*', function (req, res) { const options = { root: __dirname + '/static/', dotfiles: 'deny', }; res.sendFile(req.params[0], options); }); };