@inteli.city/node-red-contrib-http-plus 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,140 @@
1
+ <!--
2
+ Copyright JS Foundation and other contributors, http://js.foundation
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ -->
16
+
17
+ <script type="text/html" data-template-name="http proxy+">
18
+ <div class="form-row">
19
+ <label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
20
+ <input type="text" id="node-config-input-name">
21
+ </div>
22
+ <div class="form-row">
23
+ <label for="node-config-input-url"><i class="fa fa-globe"></i> <span data-i18n="httpin.label.url"></span></label>
24
+ <input type="text" id="node-config-input-url" placeholder="http://hostname:port">
25
+ </div>
26
+ <div class="form-row">
27
+ <input type="checkbox" id="node-config-input-useAuth" style="display: inline-block; width: auto; vertical-align: top;">
28
+ <label for="node-config-input-useAuth" style="width: 70%;"><span data-i18n="httpin.use-proxyauth"></span></label>
29
+ <div style="margin-left: 20px" class="node-config-input-useAuth-row hide">
30
+ <div class="form-row">
31
+ <label for="node-config-input-username"><i class="fa fa-user"></i> <span data-i18n="common.label.username"></span></label>
32
+ <input type="text" id="node-config-input-username">
33
+ </div>
34
+ <div class="form-row">
35
+ <label for="node-config-input-password"><i class="fa fa-lock"></i> <span data-i18n="common.label.password"></span></label>
36
+ <input type="password" id="node-config-input-password">
37
+ </div>
38
+ </div>
39
+ </div>
40
+ <div class="form-row" style="margin-bottom:0;">
41
+ <label><i class="fa fa-list"></i> <span data-i18n="httpin.noproxy-hosts"></span></label>
42
+ </div>
43
+ <div class="form-row node-config-input-noproxy-container-row">
44
+ <ol id="node-config-input-noproxy-container"></ol>
45
+ </div>
46
+ </script>
47
+
48
+ <script type="text/javascript">
49
+ RED.nodes.registerType('http proxy+', {
50
+ category: 'config',
51
+ color: "#9575CD",
52
+ defaults: {
53
+ name: {value:''},
54
+ url: {
55
+ value:'',
56
+ validate:function(v, opt) {
57
+ if ((v && (v.indexOf('://') !== -1) &&
58
+ (v.trim().indexOf('http') === 0))) {
59
+ return true;
60
+ }
61
+ return RED._("node-red:httpin.errors.invalid-url");
62
+ }
63
+ },
64
+ noproxy: {value:[]}
65
+ },
66
+ credentials: {
67
+ username: {type:'text'},
68
+ password: {type:'password'}
69
+ },
70
+ label: function() {
71
+ return this.name || this.url || ('http proxy+:' + this.id);
72
+ },
73
+ oneditprepare: function() {
74
+ $('#node-config-input-useAuth').on("change", function() {
75
+ if ($(this).is(":checked")) {
76
+ $('.node-config-input-useAuth-row').show();
77
+ } else {
78
+ $('.node-config-input-useAuth-row').hide();
79
+ $('#node-config-input-username').val('');
80
+ $('#node-config-input-password').val('');
81
+ }
82
+ });
83
+ if (this.credentials.username || this.credentials.has_password) {
84
+ $('#node-config-input-useAuth').prop('checked', true);
85
+ } else {
86
+ $('#node-config-input-useAuth').prop('checked', false);
87
+ }
88
+ $('#node-config-input-useAuth').change();
89
+
90
+ var hostList = $('#node-config-input-noproxy-container')
91
+ .css({'min-height':'150px','min-width':'450px'})
92
+ .editableList({
93
+ addItem: function(container, index, data) {
94
+ var row = $('<div/>')
95
+ .css({overflow: 'hidden',whiteSpace: 'nowrap'})
96
+ .appendTo(container);
97
+
98
+ var hostField = $('<input/>',{class:'node-config-input-host',type:'text',placeholder:'hostname'})
99
+ .css({width:'100%'})
100
+ .appendTo(row);
101
+ if (data.host) {
102
+ hostField.val(data.host);
103
+ }
104
+ },
105
+ sortable: true,
106
+ removable: true
107
+ });
108
+ if (this.noproxy) {
109
+ for (var i in this.noproxy) {
110
+ hostList.editableList('addItem', {host:this.noproxy[i]});
111
+ }
112
+ }
113
+ if (hostList.editableList('items').length == 0) {
114
+ hostList.editableList('addItem', {host:''});
115
+ }
116
+ },
117
+ oneditsave: function() {
118
+ var hosts = $('#node-config-input-noproxy-container').editableList('items');
119
+ var node = this;
120
+ node.noproxy = [];
121
+ hosts.each(function(i) {
122
+ var host = $(this).find('.node-config-input-host').val().trim();
123
+ if (host) {
124
+ node.noproxy.push(host);
125
+ }
126
+ });
127
+ },
128
+ oneditresize: function(size) {
129
+ var rows = $('#node-config-dialog-edit-form>div:not(.node-config-input-noproxy-container-row)');
130
+ var height = size.height;
131
+ for (var i = 0; i < rows.length; i++) {
132
+ height -= $(rows[i]).outerHeight(true);
133
+ }
134
+
135
+ var editorRow = $('#node-config-dialog-edit-form>div.node-config-input-noproxy-container-row');
136
+ height -= (parseInt(editorRow.css('margin-top')) + parseInt(editorRow.css('margin-bottom')));
137
+ $('#node-config-input-noproxy-container').editableList('height',height);
138
+ }
139
+ });
140
+ </script>
package/httpproxy+.js ADDED
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Copyright JS Foundation and other contributors, http://js.foundation
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ **/
16
+
17
+ /*
18
+ * Proxy helper logic is based on the work of Rob Wu <rob@robwu.nl>
19
+ * (MIT License) — modified for the Node-RED runtime environment.
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ // ── Proxy helper ──────────────────────────────────────────────────────────────
25
+
26
+ function parseUrl(url) {
27
+ var parsedUrl = { protocol: null, host: null, port: null, hostname: null, query: null, href: null };
28
+ try {
29
+ if (!url) { return parsedUrl; }
30
+ parsedUrl = new URL(url);
31
+ } catch (e) { /* ignore */ }
32
+ return parsedUrl;
33
+ }
34
+
35
+ var DEFAULT_PORTS = { ftp: 21, gopher: 70, http: 80, https: 443, ws: 80, wss: 443, mqtt: 1880, mqtts: 8883 };
36
+
37
+ var modeOverride = getEnv('NR_PROXY_MODE', {});
38
+
39
+ function getProxyForUrl(url, options) {
40
+ url = url || '';
41
+ var defaultOptions = { mode: 'strict', lowerCaseOnly: false, favourUpperCase: false, excludeNpm: false };
42
+ options = Object.assign({}, defaultOptions, options);
43
+
44
+ if (modeOverride === 'legacy' || modeOverride === 'strict') { options.mode = modeOverride; }
45
+
46
+ if (options.mode === 'legacy') {
47
+ return legacyGetProxyForUrl(url, options.env || process.env);
48
+ }
49
+
50
+ var parsedUrl = typeof url === 'string' ? parseUrl(url) : url || {};
51
+ var proto = parsedUrl.protocol;
52
+ var hostname = parsedUrl.host;
53
+ var port = parsedUrl.port;
54
+ if (typeof hostname !== 'string' || !hostname || typeof proto !== 'string') { return ''; }
55
+
56
+ proto = proto.split(':', 1)[0];
57
+ hostname = hostname.replace(/:\d*$/, '');
58
+ port = parseInt(port) || DEFAULT_PORTS[proto] || 0;
59
+ if (!shouldProxy(hostname, port, options)) { return ''; }
60
+
61
+ var proxy =
62
+ getEnv('npm_config_' + proto + '_proxy', options) ||
63
+ getEnv(proto + '_proxy', options) ||
64
+ getEnv('npm_config_proxy', options) ||
65
+ getEnv('all_proxy', options);
66
+ if (proxy && proxy.indexOf('://') === -1) { proxy = proto + '://' + proxy; }
67
+ return proxy;
68
+ }
69
+
70
+ function legacyGetProxyForUrl(url, env) {
71
+ env = env || process.env;
72
+ var prox, noprox;
73
+ if (env.http_proxy) { prox = env.http_proxy; }
74
+ if (env.HTTP_PROXY) { prox = env.HTTP_PROXY; }
75
+ if (env.no_proxy) { noprox = env.no_proxy.split(','); }
76
+ if (env.NO_PROXY) { noprox = env.NO_PROXY.split(','); }
77
+
78
+ var noproxy = false;
79
+ if (noprox) {
80
+ for (var i in noprox) {
81
+ if (url.indexOf(noprox[i].trim()) !== -1) { noproxy = true; }
82
+ }
83
+ }
84
+ if (prox && !noproxy) { return prox; }
85
+ return '';
86
+ }
87
+
88
+ function shouldProxy(hostname, port, options) {
89
+ var NO_PROXY = (getEnv('npm_config_no_proxy', options) || getEnv('no_proxy', options)).toLowerCase();
90
+ if (!NO_PROXY) { return true; }
91
+ if (NO_PROXY === '*') { return false; }
92
+
93
+ return NO_PROXY.split(/[,\s]/).every(function(proxy) {
94
+ if (!proxy) { return true; }
95
+ var parsedProxy = proxy.match(/^(.+):(\d+)$/);
96
+ var parsedProxyHostname = parsedProxy ? parsedProxy[1] : proxy;
97
+ var parsedProxyPort = parsedProxy ? parseInt(parsedProxy[2]) : 0;
98
+ if (parsedProxyPort && parsedProxyPort !== port) { return true; }
99
+ if (!/^[.*]/.test(parsedProxyHostname)) { return hostname !== parsedProxyHostname; }
100
+ if (parsedProxyHostname.charAt(0) === '*') { parsedProxyHostname = parsedProxyHostname.slice(1); }
101
+ return !hostname.endsWith(parsedProxyHostname);
102
+ });
103
+ }
104
+
105
+ function getEnv(key, options) {
106
+ var env = (options && options.env) || process.env;
107
+ if (options && options.excludeNpm === true) {
108
+ if (key.indexOf('npm_config_') === 0) { return ''; }
109
+ }
110
+ if (options && options.lowerCaseOnly === true) {
111
+ return env[key.toLowerCase()] || '';
112
+ } else if (options && options.favourUpperCase === true) {
113
+ return env[key.toUpperCase()] || env[key.toLowerCase()] || '';
114
+ }
115
+ return env[key.toLowerCase()] || env[key.toUpperCase()] || '';
116
+ }
117
+
118
+ // ── Node-RED config node ───────────────────────────────────────────────────────
119
+
120
+ function register(RED) {
121
+ function HTTPProxyConfig(n) {
122
+ RED.nodes.createNode(this, n);
123
+ this.name = n.name;
124
+ this.url = n.url;
125
+ this.noproxy = n.noproxy;
126
+ }
127
+
128
+ RED.nodes.registerType('http proxy+', HTTPProxyConfig, {
129
+ credentials: {
130
+ username: { type: 'text' },
131
+ password: { type: 'password' }
132
+ }
133
+ });
134
+ }
135
+
136
+ module.exports = register;
137
+ module.exports.getProxyForUrl = getProxyForUrl;
138
+ module.exports.parseUrl = parseUrl;
@@ -0,0 +1,233 @@
1
+ <!--
2
+ Copyright JS Foundation and other contributors, http://js.foundation
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ -->
16
+
17
+ <script type="text/html" data-help-name="http.request+">
18
+ <p>Makes an HTTP request and puts the response in <code>msg.payload</code>.</p>
19
+
20
+ <h3>Inputs</h3>
21
+ <dl class="message-properties">
22
+ <dt>payload <span class="property-type">object | string | buffer</span></dt>
23
+ <dd>GET / DELETE: sent as query parameters. POST / PUT / PATCH: sent as request body.</dd>
24
+ <dt>url <span class="property-type">string</span></dt>
25
+ <dd>Target URL. Overrides the node URL. Supports Mustache syntax.</dd>
26
+ <dt>headers <span class="property-type">object</span></dt>
27
+ <dd>Additional headers merged with any headers configured in the node. Node headers take precedence.</dd>
28
+ <dt>stop <span class="property-type">boolean</span></dt>
29
+ <dd>Set to <code>true</code> to cancel all running and queued requests immediately.</dd>
30
+ </dl>
31
+
32
+ <h3>Outputs</h3>
33
+ <dl class="message-properties">
34
+ <dt>payload <span class="property-type">string | object | buffer</span></dt>
35
+ <dd>Response body. Format controlled by the <b>Return</b> setting.</dd>
36
+ <dt>statusCode <span class="property-type">number</span></dt>
37
+ <dd>HTTP status code.</dd>
38
+ <dt>headers <span class="property-type">object</span></dt>
39
+ <dd>Response headers.</dd>
40
+ <dt>responseUrl <span class="property-type">string</span></dt>
41
+ <dd>Final URL after any redirects.</dd>
42
+ </dl>
43
+ </script>
44
+
45
+ <style>
46
+ #node-kill-btn:hover { color: #d9534f !important; }
47
+ #node-kill-btn.is-executing { color: #d9534f !important; }
48
+ </style>
49
+
50
+ <script type="text/html" data-template-name="http.request+">
51
+ <div class="form-row" style="position:relative;">
52
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
53
+ <div style="display:inline-block; width:calc(100% - 140px);">
54
+ <input type="text" id="node-input-name" style="width:100%;" placeholder="Name">
55
+ </div>
56
+ <button id="node-kill-btn"
57
+ title="Kill running requests"
58
+ style="position:absolute; right:5px; top:50%; transform:translateY(-50%); z-index:5;
59
+ background:transparent; border:none; padding:6px 8px; cursor:pointer;
60
+ color:#888; font-size:13px; line-height:1;">
61
+ <i class="fa fa-stop"></i>
62
+ </button>
63
+ </div>
64
+
65
+ <div class="form-row">
66
+ <label for="node-input-method"><i class="fa fa-exchange"></i> Method</label>
67
+ <select id="node-input-method" style="width:70%;">
68
+ <option value="GET">GET</option>
69
+ <option value="POST">POST</option>
70
+ <option value="PUT">PUT</option>
71
+ <option value="PATCH">PATCH</option>
72
+ <option value="DELETE">DELETE</option>
73
+ <option value="use">use msg.method</option>
74
+ </select>
75
+ </div>
76
+
77
+ <div class="form-row">
78
+ <label for="node-input-url"><i class="fa fa-globe"></i> URL</label>
79
+ <input id="node-input-url" type="text" placeholder="https://...">
80
+ </div>
81
+
82
+ <div style="font-size:12px; color:#666; margin: -4px 0 10px 110px; line-height:1.5;">
83
+ GET / DELETE → <code>msg.payload</code> sent as query params<br>
84
+ POST / PUT / PATCH → <code>msg.payload</code> sent as body
85
+ </div>
86
+
87
+ <div class="form-row">
88
+ <label for="node-input-queue"><i class="fa fa-sign-out"></i> Queue</label>
89
+ <input type="number" id="node-input-queue" min="1" step="1" style="width:60px;" placeholder="1">
90
+ <span style="margin-left:8px; font-size:11px; color:#999;">max parallel requests</span>
91
+ </div>
92
+
93
+ <div class="form-row">
94
+ <label for="node-input-headers" style="vertical-align:top; margin-top:4px;"><i class="fa fa-list"></i> Headers</label>
95
+ <textarea id="node-input-headers" rows="3"
96
+ style="width:70%; font-family:monospace; font-size:12px; resize:vertical;"
97
+ placeholder='{"Authorization": "Bearer ..."}'>
98
+ </textarea>
99
+ </div>
100
+
101
+ <div class="form-row body-row">
102
+ <label style="width:auto; margin-left:110px;">
103
+ <input type="checkbox" id="node-input-payloadAsBody" style="display:inline-block; width:auto; vertical-align:top; margin-right:6px;">
104
+ Use <code>msg.payload</code> as body
105
+ </label>
106
+ </div>
107
+ <div class="form-row body-row static-body-row">
108
+ <label for="node-input-body" style="vertical-align:top; margin-top:4px;"><i class="fa fa-code"></i> Body</label>
109
+ <textarea id="node-input-body" rows="4"
110
+ style="width:70%; font-family:monospace; font-size:12px; resize:vertical;"
111
+ placeholder="{}">
112
+ </textarea>
113
+ </div>
114
+ <div class="body-row static-body-row" style="font-size:12px; color:#666; margin: -4px 0 10px 110px; line-height:1.5;">
115
+ Static body sent with every request.
116
+ </div>
117
+
118
+ <div class="form-row">
119
+ <label for="node-input-ret"><i class="fa fa-arrow-left"></i> Return</label>
120
+ <select id="node-input-ret" style="width:70%;">
121
+ <option value="txt">UTF-8 string</option>
122
+ <option value="obj">JSON object</option>
123
+ <option value="bin">Binary buffer</option>
124
+ </select>
125
+ </div>
126
+ </script>
127
+
128
+ <script type="text/javascript">
129
+ (function() {
130
+ RED.nodes.registerType('http.request+', {
131
+ category: 'network',
132
+ color: "#9575CD",
133
+ defaults: {
134
+ name: { value: "" },
135
+ method: { value: "GET" },
136
+ ret: { value: "txt" },
137
+ url: { value: "" },
138
+ headers: { value: "{}" },
139
+ body: { value: "{}" },
140
+ payloadAsBody: { value: false },
141
+ queue: { value: 1 }
142
+ },
143
+ inputs: 1,
144
+ outputs: 1,
145
+ icon: "white-globe.svg",
146
+ label: function() {
147
+ return this.name || "http.request+";
148
+ },
149
+ labelStyle: function() {
150
+ return this.name ? "node_label_italic" : "";
151
+ },
152
+ outputLabels: function() {
153
+ return { txt: "UTF-8 string", obj: "JSON object", bin: "Binary buffer" }[this.ret];
154
+ },
155
+ oneditprepare: function() {
156
+ var that = this;
157
+ var nodeId = this.id;
158
+
159
+ // --- kill button ---
160
+ setTimeout(function() {
161
+ function refreshKillBtn() {
162
+ $.get("http-request-plus/" + nodeId + "/status", function(data) {
163
+ var executing = parseInt(data.executing, 10) || 0;
164
+ $("#node-kill-btn").toggleClass("is-executing", executing > 0);
165
+ });
166
+ }
167
+ refreshKillBtn();
168
+ var killBtnInterval = setInterval(refreshKillBtn, 1000);
169
+
170
+ $("#node-kill-btn").on("click", function(e) {
171
+ e.preventDefault();
172
+ e.stopPropagation();
173
+
174
+ function doKill() {
175
+ $.post("http-request-plus/" + nodeId + "/kill");
176
+ setTimeout(refreshKillBtn, 300);
177
+ }
178
+
179
+ $.get("http-request-plus/" + nodeId + "/status", function(data) {
180
+ var executing = parseInt(data.executing, 10) || 0;
181
+
182
+ if (executing > 0) {
183
+ var $dlg = $("<div>")
184
+ .html("Kill <strong>" + executing + "</strong> running request" + (executing > 1 ? "s" : "") + "?")
185
+ .dialog({
186
+ modal: true,
187
+ title: "Kill running requests",
188
+ closeOnEscape: true,
189
+ width: 320,
190
+ buttons: {
191
+ "Cancel": function() { $(this).dialog("close"); },
192
+ "Kill": function() { $(this).dialog("close"); doKill(); }
193
+ },
194
+ close: function() { $(this).dialog("destroy").remove(); }
195
+ });
196
+ $dlg.closest(".ui-dialog").css("z-index", 9999);
197
+ } else {
198
+ doKill();
199
+ }
200
+ }).fail(function() {
201
+ doKill();
202
+ });
203
+ });
204
+
205
+ that._killBtnInterval = killBtnInterval;
206
+ }, 0);
207
+ // ---
208
+
209
+ function isBodyMethod() {
210
+ var m = $("#node-input-method").val();
211
+ return m === "POST" || m === "PUT" || m === "PATCH";
212
+ }
213
+ function toggleBodySection() {
214
+ $(".body-row").toggle(isBodyMethod());
215
+ toggleStaticBody();
216
+ }
217
+ function toggleStaticBody() {
218
+ var usePayload = $("#node-input-payloadAsBody").is(":checked");
219
+ $(".static-body-row").toggle(isBodyMethod() && !usePayload);
220
+ }
221
+ $("#node-input-method").on("change", toggleBodySection);
222
+ $("#node-input-payloadAsBody").on("change", toggleStaticBody);
223
+ toggleBodySection();
224
+ },
225
+ oneditsave: function() {
226
+ if (this._killBtnInterval) { clearInterval(this._killBtnInterval); delete this._killBtnInterval; }
227
+ },
228
+ oneditcancel: function() {
229
+ if (this._killBtnInterval) { clearInterval(this._killBtnInterval); delete this._killBtnInterval; }
230
+ }
231
+ });
232
+ })();
233
+ </script>