Skip to content

Commit 8fb4a4d

Browse files
authored
Merge pull request #81 from FlowFuse/271-proxy-support
Proxy support
2 parents 47b6c79 + 73206a7 commit 8fb4a4d

File tree

7 files changed

+8376
-1186
lines changed

7 files changed

+8376
-1186
lines changed

.eslintrc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
"extends": [
1010
"standard"
1111
],
12+
"plugins": [ "eslint-plugin-html", "no-only-tests" ],
1213
"parserOptions": {
1314
"ecmaVersion": 12
1415
},
1516
"rules": {
1617
"indent": ["error", 4],
17-
"object-shorthand": ["error"]
18+
"object-shorthand": ["error"],
19+
"no-only-tests/no-only-tests": "error"
1820
}
1921
}

lib/utils.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
const proxyFromEnv = require('proxy-from-env')
2+
3+
module.exports = {
4+
getWSProxyAgent,
5+
getHTTPProxyAgent
6+
}
7+
8+
/**
9+
* Get a specific proxy agent for a WebSocket connection. This should be applied to the `wsOptions.agent` property
10+
*
11+
* NOTE: This utility function is specifically designed for the MQTT instances where the proxy is set based on the http based EndPoint
12+
* that the instance will use to make a connection. As such, the proxy URL is determined based on the `wsEndPoint` provided in
13+
* conjunction with env vars `http_proxy`, `https_proxy` and `no_proxy`.
14+
*
15+
* More Info:
16+
* `wsOptions.agent` is expected to be an HTTP or HTTPS agent based on the request protocol
17+
* http/ws requests use env var `http_proxy` and the HttpProxyAgent
18+
* https/wss requests use env var `https_proxy` and the HttpsProxyAgent
19+
* REF: https://github.com/TooTallNate/proxy-agents/tree/main/packages/proxy-agent#maps-proxy-protocols-to-httpagent-implementations
20+
*
21+
* @param {String} url - WebSocket url
22+
* @param {import('http').AgentOptions} proxyOptions - proxy options
23+
* @returns {import('https-proxy-agent').HttpsProxyAgent | import('http-proxy-agent').HttpProxyAgent | null}
24+
*/
25+
function getWSProxyAgent (url, proxyOptions) {
26+
if (!url) {
27+
return null
28+
}
29+
const _url = new URL(url)
30+
const isHTTPBased = _url.protocol === 'ws:' || _url.protocol === 'http:'
31+
const isHTTPSBased = _url.protocol === 'wss:' || _url.protocol === 'https:'
32+
if (!isHTTPBased && !isHTTPSBased) {
33+
return null
34+
}
35+
36+
// replace ^ws with http so that getProxyForUrl can return the correct http*_proxy for ws/wss
37+
const proxyUrl = proxyFromEnv.getProxyForUrl(url.replace(/^ws/, 'http'))
38+
39+
if (proxyUrl && isHTTPSBased) {
40+
const HttpsAgent = require('https-proxy-agent').HttpsProxyAgent
41+
return new HttpsAgent(proxyUrl, proxyOptions)
42+
}
43+
if (proxyUrl && isHTTPBased) {
44+
const HttpAgent = require('http-proxy-agent').HttpProxyAgent
45+
return new HttpAgent(proxyUrl, proxyOptions)
46+
}
47+
return null
48+
}
49+
50+
/**
51+
* Get proxy agent for HTTP or HTTPS got instance. This should be applied to the `agent` property of the got instance options
52+
*
53+
* NOTE: This utility function is specifically designed for the GOT instances where the proxy is set based on the `httpEndPoint`
54+
* that the instance will use to make requests. As such, the proxy URL is determined based on the `httpEndPoint` provided
55+
* in conjunction with env vars `http_proxy`, `https_proxy` and `no_proxy`.
56+
* @param {String} url - http or https URL
57+
* @param {import('http').AgentOptions} proxyOptions - proxy options
58+
* @returns {{http: import('http-proxy-agent').HttpProxyAgent | undefined, https: import('https-proxy-agent').HttpsProxyAgent | undefined}}
59+
*/
60+
function getHTTPProxyAgent (url, proxyOptions) {
61+
const agent = {}
62+
if (url) {
63+
const _url = new URL(url)
64+
const proxyUrl = proxyFromEnv.getProxyForUrl(url)
65+
if (proxyUrl && _url.protocol === 'http:') {
66+
const HttpAgent = require('http-proxy-agent').HttpProxyAgent
67+
agent.http = new HttpAgent(proxyUrl, proxyOptions)
68+
}
69+
if (proxyUrl && _url.protocol === 'https:') {
70+
const HttpsAgent = require('https-proxy-agent').HttpsProxyAgent
71+
agent.https = new HttpsAgent(proxyUrl, proxyOptions)
72+
}
73+
}
74+
return agent
75+
}

nodes/project-link.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ module.exports = function (RED) {
88

99
// Imports
1010
const crypto = require('crypto')
11-
const got = require('got')
11+
const { default: GOT } = require('got')
1212
const MQTT = require('mqtt')
1313
const urlModule = require('url')
14+
const utils = require('../lib/utils.js')
1415

1516
// Constants
1617
const API_VERSION = 'v1'
@@ -631,6 +632,14 @@ module.exports = function (RED) {
631632
const parsedURL = urlModule.parse(brokerURL)
632633
const newURL = new URL(brokerURL)
633634
parsedURL.hostname = newURL.hostname
635+
636+
// wsOptions.agent is expected to be an HTTP or HTTPS agent based on the request protocol
637+
if (process.env.all_proxy || process.env.http_proxy || process.env.https_proxy) {
638+
options.wsOptions = {
639+
agent: utils.getWSProxyAgent(brokerURL)
640+
}
641+
}
642+
634643
client = MQTT.connect(parsedURL, options)
635644
clients.push(client) // add to clients array for containment and auto cleanup of multiple clients
636645
on('connect', onConnect)
@@ -1056,6 +1065,10 @@ module.exports = function (RED) {
10561065
}
10571066
RED.nodes.registerType('project link call', ProjectLinkCallNode)
10581067

1068+
const got = GOT.extend({
1069+
agent: utils.getHTTPProxyAgent(RED.settings.flowforge.forgeURL, { timeout: 4000 })
1070+
})
1071+
10591072
// Endpoint for querying list of projects in node UI
10601073
RED.httpAdmin.get('/nr-project-link/projects', RED.auth.needsPermission('flows.write'), async function (_req, res) {
10611074
const url = `${RED.settings.flowforge.forgeURL}/api/${API_VERSION}/teams/${RED.settings.flowforge.teamID}/projects`

0 commit comments

Comments
 (0)