-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtulip_tables.js
175 lines (152 loc) · 5.57 KB
/
tulip_tables.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
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);
});
};