zapier-platform-cli 16.3.0 → 16.4.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.
@@ -2,8 +2,231 @@ const _ = require('lodash');
2
2
  const path = require('path');
3
3
 
4
4
  const { findCorePackageDir } = require('./misc');
5
+ const { BASE_ENDPOINT } = require('../constants');
5
6
 
6
- const getLocalAppHandler = ({ reload = false, baseEvent = {} } = {}) => {
7
+ /**
8
+ * Wraps Node's http.request() / https.request() so that all requests go via a relay URL.
9
+ * It decides whether to use the http or https module based on the relay URL's protocol.
10
+ *
11
+ * @param {Function} originalHttpRequest - The original http.request function.
12
+ * @param {Function} originalHttpsRequest - The original https.request function.
13
+ * @param {string} relayUrl - The base URL to which we relay. (e.g., 'http://my-relay.test')
14
+ * @param {Object} relayHeaders - Extra headers to add to each request sent to the relay.
15
+ * @returns {Function} A function with the same signature as http(s).request that relays instead.
16
+ *
17
+ * Usage:
18
+ * const http = require('http');
19
+ * const https = require('https');
20
+ *
21
+ * // Replace https.request with our wrapped version:
22
+ * https.request = wrapHttpRequestFuncWithRelay(
23
+ * http.request,
24
+ * https.request,
25
+ * 'https://my-relay.test',
26
+ * { 'X-Relayed-By': 'MyRelayProxy' }
27
+ * );
28
+ *
29
+ * // Now, calling https.request('https://example.com/hello') will actually
30
+ * // send a request to "https://my-relay.test/example.com/hello"
31
+ * // with X-Relayed-By header attached.
32
+ */
33
+ function wrapHttpRequestFuncWithRelay(
34
+ originalHttpRequest,
35
+ originalHttpsRequest,
36
+ relayUrl,
37
+ relayHeaders,
38
+ ) {
39
+ const parsedRelayUrl = new URL(relayUrl);
40
+
41
+ // Decide if the relay itself is HTTP or HTTPS
42
+ const isRelayHttps = parsedRelayUrl.protocol === 'https:';
43
+
44
+ // Pick which request function to use to talk to the relay
45
+ const relayRequestFunc = isRelayHttps
46
+ ? originalHttpsRequest
47
+ : originalHttpRequest;
48
+
49
+ /**
50
+ * The actual wrapped request function.
51
+ * Accepts the same arguments as http(s).request:
52
+ * (options[, callback]) or (url[, options][, callback])
53
+ */
54
+ return function wrappedRequest(originalOptions, originalCallback) {
55
+ let options;
56
+ let callback;
57
+
58
+ // 1. Normalize arguments (string URL vs. options object)
59
+ if (typeof originalOptions === 'string') {
60
+ // Called like request(urlString, [...])
61
+ try {
62
+ const parsedOriginalUrl = new URL(originalOptions);
63
+
64
+ if (typeof originalCallback === 'object') {
65
+ // request(urlString, optionsObject, callback)
66
+ options = { ...originalCallback };
67
+ callback = arguments[2];
68
+ } else {
69
+ // request(urlString, callback) or request(urlString)
70
+ options = {};
71
+ callback = originalCallback;
72
+ }
73
+
74
+ // Merge in the URL parts if not explicitly set in options
75
+ options.protocol = options.protocol || parsedOriginalUrl.protocol;
76
+ options.hostname = options.hostname || parsedOriginalUrl.hostname;
77
+ options.port = options.port || parsedOriginalUrl.port;
78
+ options.path =
79
+ options.path || parsedOriginalUrl.pathname + parsedOriginalUrl.search;
80
+ } catch (err) {
81
+ // If it's not a valid absolute URL, treat it as a path
82
+ // or re-throw if you prefer strictness.
83
+ options = {};
84
+ callback = originalCallback;
85
+ options.path = originalOptions;
86
+ }
87
+ } else {
88
+ // Called like request(optionsObject, [callback])
89
+ options = { ...originalOptions };
90
+ callback = originalCallback;
91
+ }
92
+
93
+ // 2. Default method and headers
94
+ if (!options.method) {
95
+ options.method = 'GET';
96
+ }
97
+ if (!options.headers) {
98
+ options.headers = {};
99
+ }
100
+
101
+ // 3. Decide whether to relay or not
102
+ // If the request is being sent to the same host:port as our relay,
103
+ // we do NOT want to relay again. We just call the original request.
104
+ const targetHost = (options.hostname || options.host)
105
+ .replaceAll('lcurly-', '{{')
106
+ .replaceAll('-rcurly', '}}');
107
+ const targetPort = options.port ? String(options.port) : '';
108
+ const relayHost = parsedRelayUrl.hostname;
109
+ const relayPort = parsedRelayUrl.port ? String(parsedRelayUrl.port) : '';
110
+
111
+ const isAlreadyRelay =
112
+ targetHost === relayHost &&
113
+ // If no port was specified, assume default port comparison as needed
114
+ (targetPort === relayPort || (!targetPort && !relayPort));
115
+
116
+ if (isAlreadyRelay) {
117
+ // Just call the original function; do *not* re-relay
118
+ const originalFn =
119
+ options.protocol === 'https:'
120
+ ? originalHttpsRequest
121
+ : originalHttpRequest;
122
+ return originalFn(options, callback);
123
+ }
124
+
125
+ // 4. Otherwise, build the path we want to relay to
126
+ let finalHost = targetHost;
127
+ if (targetPort && targetPort !== '443') {
128
+ finalHost += `:${targetPort}`;
129
+ }
130
+ const combinedPath = `${parsedRelayUrl.pathname}/${finalHost}${options.path}`;
131
+
132
+ // 5. Build final options for the relay request
133
+ const relayedOptions = {
134
+ protocol: parsedRelayUrl.protocol,
135
+ hostname: relayHost,
136
+ port: relayPort,
137
+ path: combinedPath,
138
+ method: options.method,
139
+ headers: {
140
+ ...options.headers,
141
+ ...relayHeaders,
142
+ },
143
+ };
144
+
145
+ // 6. Make the relay request
146
+ const relayReq = relayRequestFunc(relayedOptions, callback);
147
+ return relayReq;
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Wraps a fetch function so that all requests get relayed to a specified relay URL.
153
+ * The final relay URL includes: relayUrl + "/" + originalHost + originalPath
154
+ *
155
+ * @param {Function} fetchFunc - The original fetch function (e.g., global.fetch).
156
+ * @param {string} relayUrl - The base URL to which we relay. (e.g. 'https://my-relay.test')
157
+ * @param {Object} relayHeaders - Extra headers to add to each request sent to the relay.
158
+ * @returns {Function} A function with the same signature as `fetch(url, options)`.
159
+ *
160
+ * Usage:
161
+ * const wrappedFetch = wrapFetchWithRelay(
162
+ * fetch,
163
+ * 'https://my-relay.test',
164
+ * { 'X-Relayed-By': 'MyRelayProxy' },
165
+ * );
166
+ *
167
+ * // Now when you do:
168
+ * // wrappedFetch('https://example.com/api/user?id=123', { method: 'POST' });
169
+ * // it actually sends a request to:
170
+ * // https://my-relay.test/example.com/api/user?id=123
171
+ * // with "X-Relayed-By" header included.
172
+ */
173
+ function wrapFetchWithRelay(fetchFunc, relayUrl, relayHeaders) {
174
+ const parsedRelayUrl = new URL(relayUrl);
175
+
176
+ return async function wrappedFetch(originalUrl, originalOptions = {}) {
177
+ // Attempt to parse the originalUrl as an absolute URL
178
+ const parsedOriginalUrl = new URL(originalUrl);
179
+
180
+ // Build the portion that includes the original host (and port if present)
181
+ let host = parsedOriginalUrl.hostname;
182
+ // If there's a port that isn't 443 (for HTTPS) or 80 (for HTTP), append it
183
+ // (Adjust to your preferences; here we loosely check for 443 only.)
184
+ if (parsedOriginalUrl.port && parsedOriginalUrl.port.toString() !== '443') {
185
+ host += `:${parsedOriginalUrl.port}`;
186
+ }
187
+
188
+ const isAlreadyRelay =
189
+ parsedOriginalUrl.hostname === parsedRelayUrl.hostname &&
190
+ parsedOriginalUrl.port === parsedRelayUrl.port;
191
+ if (isAlreadyRelay) {
192
+ // Just call the original fetch function; do *not* re-relay
193
+ return fetchFunc(originalUrl, originalOptions);
194
+ }
195
+
196
+ // Combine the relay's pathname with the "host + path" from the original
197
+ // For example: relayUrl = http://my-relay.test
198
+ // => parsedRelayUrl.pathname might be '/'
199
+ // => combinedPath = '/example.com:8080/some/path'
200
+ const combinedPath = `${parsedRelayUrl.pathname}/${host}${parsedOriginalUrl.pathname}`;
201
+
202
+ // Merge in the search strings: the relay's own search (if any) plus the original URL's search
203
+ const finalUrl = `${parsedRelayUrl.origin}${combinedPath}${parsedRelayUrl.search}${parsedOriginalUrl.search}`;
204
+
205
+ // Merge the user's headers with the relayHeaders
206
+ const mergedHeaders = {
207
+ ...(originalOptions.headers || {}),
208
+ ...relayHeaders,
209
+ };
210
+
211
+ const finalOptions = {
212
+ ...originalOptions,
213
+ headers: mergedHeaders,
214
+ };
215
+
216
+ // Call the real fetch with our new URL and merged options
217
+ return fetchFunc(finalUrl, finalOptions);
218
+ };
219
+ }
220
+
221
+ const getLocalAppHandler = ({
222
+ reload = false,
223
+ baseEvent = {},
224
+ appId = null,
225
+ deployKey = null,
226
+ relayAuthenticationId = null,
227
+ beforeRequest = null,
228
+ afterResponse = null,
229
+ } = {}) => {
7
230
  const entryPath = `${process.cwd()}/index`;
8
231
  const rootPath = path.dirname(require.resolve(entryPath));
9
232
  const corePackageDir = findCorePackageDir();
@@ -24,6 +247,41 @@ const getLocalAppHandler = ({ reload = false, baseEvent = {} } = {}) => {
24
247
  // maybe we could do require('syntax-error') in the future
25
248
  return (event, ctx, callback) => callback(err);
26
249
  }
250
+
251
+ if (beforeRequest) {
252
+ appRaw.beforeRequest = [...(appRaw.beforeRequest || []), ...beforeRequest];
253
+ }
254
+ if (afterResponse) {
255
+ appRaw.afterResponse = [...afterResponse, ...(appRaw.afterResponse || [])];
256
+ }
257
+
258
+ if (appId && deployKey && relayAuthenticationId) {
259
+ const relayUrl = `${BASE_ENDPOINT}/api/platform/cli/apps/${appId}/relay`;
260
+ const relayHeaders = {
261
+ 'x-relay-authentication-id': relayAuthenticationId,
262
+ 'x-deploy-key': deployKey,
263
+ };
264
+
265
+ const http = require('http');
266
+ const https = require('https');
267
+ const origHttpRequest = http.request;
268
+ const origHttpsRequest = https.request;
269
+ http.request = wrapHttpRequestFuncWithRelay(
270
+ origHttpRequest,
271
+ origHttpsRequest,
272
+ relayUrl,
273
+ relayHeaders,
274
+ );
275
+ https.request = wrapHttpRequestFuncWithRelay(
276
+ origHttpRequest,
277
+ origHttpsRequest,
278
+ relayUrl,
279
+ relayHeaders,
280
+ );
281
+
282
+ global.fetch = wrapFetchWithRelay(global.fetch, relayUrl, relayHeaders);
283
+ }
284
+
27
285
  const handler = zapier.createAppHandler(appRaw);
28
286
  return (event, ctx, callback) => {
29
287
  event = _.merge(
@@ -40,7 +298,13 @@ const getLocalAppHandler = ({ reload = false, baseEvent = {} } = {}) => {
40
298
 
41
299
  // Runs a local app command (./index.js) like {command: 'validate'};
42
300
  const localAppCommand = (event) => {
43
- const handler = getLocalAppHandler();
301
+ const handler = getLocalAppHandler({
302
+ appId: event.appId,
303
+ deployKey: event.deployKey,
304
+ relayAuthenticationId: event.relayAuthenticationId,
305
+ beforeRequest: event.beforeRequest,
306
+ afterResponse: event.afterResponse,
307
+ });
44
308
  return new Promise((resolve, reject) => {
45
309
  handler(event, {}, (err, resp) => {
46
310
  if (err) {