-
Notifications
You must be signed in to change notification settings - Fork 126
/
outgoing-request-hook.js
130 lines (111 loc) · 4.67 KB
/
outgoing-request-hook.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
/*
* Copyright (c) 2022, Salesforce, Inc.
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import http from 'http'
import https from 'https'
let HTTP_AGENT, HTTPS_AGENT
const KEEPALIVE_AGENT_OPTIONS = {
keepAlive: true
}
/**
* Returns the http and https agent singletons.
*
* @private
* @returns {object} -
*/
const getKeepAliveAgents = () => {
if (!HTTP_AGENT || !HTTPS_AGENT) {
HTTP_AGENT = new http.Agent(KEEPALIVE_AGENT_OPTIONS)
HTTPS_AGENT = new https.Agent(KEEPALIVE_AGENT_OPTIONS)
}
return {
httpAgent: HTTP_AGENT,
httpsAgent: HTTPS_AGENT
}
}
export const outgoingRequestHook = (wrapped, options) => {
return function () {
// Get the app hostname. If we can't, then just pass
// the call through to the wrapped function. We'll also
// do that if there's no access key.
const accessKey = process.env.X_MOBIFY_ACCESS_KEY
const {appHostname, proxyKeepAliveAgent} = options || {}
if (!(appHostname && accessKey)) {
return wrapped.apply(this, arguments) // eslint-disable-line prefer-rest-params
}
// request and get can be called with (options[, callback])
// or (url[, options][, callback]).
let workingUrl = ''
let workingOptions
let workingCallback
const args = arguments // eslint-disable-line prefer-rest-params
// The options will be in the first 'object' argument
for (let i = 0; i < args.length; i++) {
const arg = args[i]
switch (typeof arg) {
case 'object': {
// Assume this arg is the options
// We want to clone any options to avoid modifying
// the original object.
workingOptions = {...arg}
break
}
case 'string': {
// Assume this arg is the URL
workingUrl = arg
break
}
default:
// Assume this is the callback
workingCallback = arg
break
}
}
if (!workingOptions) {
// No options were supplied, so we add them
workingOptions = {headers: {}}
}
// We need to identify loopback requests: requests that are
// to the appHost (irrespective of protocol).
// The workingUrl value may be partial (the docs are very
// imprecise on permitted values). We check everywhere that
// might give us what we need. If this is not a loopback
// request, we just pass it through unmodified.
const isLoopback =
// Either hostname or host are allowed in the options. The docs
// say that 'hostname' is an alias for 'host', but that's not
// exactly true - host can include a port but hostname doesn't
// always. So we need to compare both.
workingOptions.host === appHostname ||
workingOptions.hostname === appHostname ||
(workingUrl && workingUrl.includes(`//${appHostname}`))
if (!isLoopback) {
return wrapped.apply(this, arguments) // eslint-disable-line prefer-rest-params
}
// We must inject the 'x-mobify-access-key' header into the
// request.
workingOptions.headers = workingOptions.headers ? {...workingOptions.headers} : {}
// Inject the access key.
workingOptions.headers['x-mobify-access-key'] = accessKey
// Create and add keep-alive agent to options for loop-back connection.
if (proxyKeepAliveAgent) {
const {httpAgent, httpsAgent} = getKeepAliveAgents()
// Add default agent to global connection reuse.
workingOptions.agent =
workingUrl.startsWith('http:') || workingOptions?.protocol === 'http:'
? httpAgent
: httpsAgent
// `node-fetch` and potentially other libraries add connection: close headers
// remove them to keep the connection alive. NOTE: There are variations in
// whether or not the connection header is upper or lower case, so handle both.
delete workingOptions?.headers?.connection
delete workingOptions?.headers?.Connection
}
// Build the args, omitting any undefined values
const workingArgs = [workingUrl, workingOptions, workingCallback].filter((arg) => !!arg)
return wrapped.apply(this, workingArgs)
}
}