zapier-platform-core 11.2.0 → 11.3.2
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.
- package/package.json +3 -3
- package/src/http-middlewares/before/add-query-params.js +18 -1
- package/src/tools/cleaner.js +2 -2
- package/src/tools/create-http-patch.js +11 -9
- package/src/tools/create-lambda-handler.js +24 -29
- package/src/tools/create-legacy-scripting-runner.js +7 -4
- package/src/tools/create-logger.js +106 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zapier-platform-core",
|
|
3
|
-
"version": "11.2
|
|
3
|
+
"version": "11.3.2",
|
|
4
4
|
"description": "The core SDK for CLI apps in the Zapier Developer Platform.",
|
|
5
5
|
"repository": "zapier/zapier-platform",
|
|
6
6
|
"homepage": "https://platform.zapier.com/",
|
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
"form-data": "4.0.0",
|
|
48
48
|
"lodash": "4.17.21",
|
|
49
49
|
"mime-types": "2.1.34",
|
|
50
|
-
"node-fetch": "2.6.
|
|
50
|
+
"node-fetch": "2.6.7",
|
|
51
51
|
"oauth-sign": "0.9.0",
|
|
52
52
|
"semver": "7.3.5",
|
|
53
|
-
"zapier-platform-schema": "11.2
|
|
53
|
+
"zapier-platform-schema": "11.3.2"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"adm-zip": "0.5.5",
|
|
@@ -14,7 +14,24 @@ const addQueryParams = (req) => {
|
|
|
14
14
|
|
|
15
15
|
normalizeEmptyParamFields(req);
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
let stringifiedParams = querystring.stringify(req.params);
|
|
18
|
+
|
|
19
|
+
// it goes against spec, but for compatibility, some APIs want certain
|
|
20
|
+
// characters (mostly $) unencoded
|
|
21
|
+
if (req.skipEncodingChars) {
|
|
22
|
+
for (let i = 0; i < req.skipEncodingChars.length; i++) {
|
|
23
|
+
const char = req.skipEncodingChars.charAt(i);
|
|
24
|
+
const valToReplace = querystring.escape(char);
|
|
25
|
+
if (valToReplace === char) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
// no replaceAll in JS yet, coming in a node version soon!
|
|
29
|
+
stringifiedParams = stringifiedParams.replace(
|
|
30
|
+
new RegExp(valToReplace, 'g'),
|
|
31
|
+
char
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
18
35
|
|
|
19
36
|
if (stringifiedParams) {
|
|
20
37
|
req.url += `${splitter}${stringifiedParams}`;
|
package/src/tools/cleaner.js
CHANGED
|
@@ -158,7 +158,7 @@ const isEmptyQueryParam = (value) =>
|
|
|
158
158
|
value === '' ||
|
|
159
159
|
value === null ||
|
|
160
160
|
value === undefined ||
|
|
161
|
-
|
|
161
|
+
(typeof value === 'string' && value.search(isCurlies) >= 0);
|
|
162
162
|
|
|
163
163
|
const normalizeEmptyParamFields = normalizeEmptyRequestFields.bind(
|
|
164
164
|
null,
|
|
@@ -167,7 +167,7 @@ const normalizeEmptyParamFields = normalizeEmptyRequestFields.bind(
|
|
|
167
167
|
);
|
|
168
168
|
const normalizeEmptyBodyFields = normalizeEmptyRequestFields.bind(
|
|
169
169
|
null,
|
|
170
|
-
(v) =>
|
|
170
|
+
(v) => typeof v === 'string' && v.search(isCurlies) >= 0,
|
|
171
171
|
'body'
|
|
172
172
|
);
|
|
173
173
|
|
|
@@ -3,13 +3,13 @@ const _ = require('lodash');
|
|
|
3
3
|
const constants = require('../constants');
|
|
4
4
|
|
|
5
5
|
const createHttpPatch = (event) => {
|
|
6
|
-
const
|
|
7
|
-
const logBuffer = [];
|
|
8
|
-
const logger = createLogger(event, { logBuffer });
|
|
9
|
-
|
|
10
|
-
const httpPatch = (object) => {
|
|
6
|
+
const httpPatch = (object, logger) => {
|
|
11
7
|
const originalRequest = object.request;
|
|
12
8
|
|
|
9
|
+
// Important not to reuse logger between calls, because we always destroy
|
|
10
|
+
// the logger at the end of a Lambda call.
|
|
11
|
+
object.zapierLogger = logger;
|
|
12
|
+
|
|
13
13
|
// Avoids multiple patching and memory leaks (mostly when running tests locally)
|
|
14
14
|
if (object.patchedByZapier) {
|
|
15
15
|
return;
|
|
@@ -21,13 +21,15 @@ const createHttpPatch = (event) => {
|
|
|
21
21
|
object.request = (options, callback) => {
|
|
22
22
|
// `options` can be an object or a string. If options is a string, it is
|
|
23
23
|
// automatically parsed with url.parse().
|
|
24
|
-
// See https://nodejs.org/docs/latest-
|
|
24
|
+
// See https://nodejs.org/docs/latest-v14.x/api/http.html#http_http_request_options_callback
|
|
25
25
|
let requestUrl;
|
|
26
26
|
if (typeof options === 'string') {
|
|
27
27
|
requestUrl = options;
|
|
28
28
|
} else if (typeof options.url === 'string') {
|
|
29
|
-
// XXX: Somehow options.url is available for some requests although
|
|
30
|
-
// Without this else-if, many
|
|
29
|
+
// XXX: Somehow options.url is available for some requests although
|
|
30
|
+
// http.request doesn't really accept it. Without this else-if, many
|
|
31
|
+
// HTTP requests don't work. Should take a deeper look at this
|
|
32
|
+
// weirdness.
|
|
31
33
|
requestUrl = options.url;
|
|
32
34
|
} else {
|
|
33
35
|
requestUrl =
|
|
@@ -67,7 +69,7 @@ const createHttpPatch = (event) => {
|
|
|
67
69
|
response_content: responseBody,
|
|
68
70
|
};
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
object.zapierLogger(
|
|
71
73
|
`${logData.response_status_code} ${logData.request_method} ${logData.request_url}`,
|
|
72
74
|
logData
|
|
73
75
|
);
|
|
@@ -147,37 +147,36 @@ const createLambdaHandler = (appRawOrPath) => {
|
|
|
147
147
|
|
|
148
148
|
environmentTools.cleanEnvironment();
|
|
149
149
|
|
|
150
|
+
// Copy bundle environment into process.env *before* creating the logger and
|
|
151
|
+
// loading app code, so that the logger gets the endpoint from process.env,
|
|
152
|
+
// and top level app code can get bundle environment vars via process.env.
|
|
153
|
+
environmentTools.applyEnvironment(event);
|
|
154
|
+
|
|
150
155
|
// Create logger outside of domain, so we can use in both error and run callbacks.
|
|
151
156
|
const logBuffer = [];
|
|
152
157
|
const logger = createLogger(event, { logBuffer });
|
|
153
158
|
|
|
154
159
|
let isCallbackCalled = false;
|
|
155
160
|
const callbackOnce = (err, resp) => {
|
|
156
|
-
|
|
157
|
-
isCallbackCalled
|
|
158
|
-
|
|
159
|
-
|
|
161
|
+
logger.end().finally(() => {
|
|
162
|
+
if (!isCallbackCalled) {
|
|
163
|
+
isCallbackCalled = true;
|
|
164
|
+
callback(err, resp);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
160
167
|
};
|
|
161
168
|
|
|
162
169
|
const logErrorAndCallbackOnce = (logMsg, logData, err) => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
err.message
|
|
174
|
-
) {
|
|
175
|
-
err.message += `\n\nConsole logs:\n${logBuffer
|
|
176
|
-
.map((s) => ` ${s.message}`)
|
|
177
|
-
.join('')}`;
|
|
178
|
-
}
|
|
179
|
-
callbackOnce(err);
|
|
180
|
-
});
|
|
170
|
+
logger(logMsg, logData);
|
|
171
|
+
|
|
172
|
+
// Check for `.message` in case someone did `throw "My Error"`
|
|
173
|
+
if (!constants.IS_TESTING && err && !err.doNotContextify && err.message) {
|
|
174
|
+
err.message += `\n\nConsole logs:\n${logBuffer
|
|
175
|
+
.map((s) => ` ${s.message}`)
|
|
176
|
+
.join('')}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
callbackOnce(err);
|
|
181
180
|
};
|
|
182
181
|
|
|
183
182
|
const handlerDomain = domain.create();
|
|
@@ -191,10 +190,6 @@ const createLambdaHandler = (appRawOrPath) => {
|
|
|
191
190
|
});
|
|
192
191
|
|
|
193
192
|
handlerDomain.run(() => {
|
|
194
|
-
// Copy bundle environment into process.env *before* loading app code,
|
|
195
|
-
// so that top level app code can get bundle environment vars via process.env.
|
|
196
|
-
environmentTools.applyEnvironment(event);
|
|
197
|
-
|
|
198
193
|
const rpc = createRpcClient(event);
|
|
199
194
|
|
|
200
195
|
return loadApp(event, rpc, appRawOrPath)
|
|
@@ -203,10 +198,10 @@ const createLambdaHandler = (appRawOrPath) => {
|
|
|
203
198
|
|
|
204
199
|
const { skipHttpPatch } = appRaw.flags || {};
|
|
205
200
|
// Adds logging for _all_ kinds of http(s) requests, no matter the library
|
|
206
|
-
if (!skipHttpPatch) {
|
|
201
|
+
if (!skipHttpPatch && !event.calledFromCli) {
|
|
207
202
|
const httpPatch = createHttpPatch(event);
|
|
208
|
-
httpPatch(require('http'));
|
|
209
|
-
httpPatch(require('https')); // 'https' needs to be patched separately
|
|
203
|
+
httpPatch(require('http'), logger);
|
|
204
|
+
httpPatch(require('https'), logger); // 'https' needs to be patched separately
|
|
210
205
|
}
|
|
211
206
|
|
|
212
207
|
// TODO: Avoid calling prepareApp(appRaw) repeatedly here as createApp()
|
|
@@ -8,8 +8,11 @@ const semver = require('semver');
|
|
|
8
8
|
const createLegacyScriptingRunner = (z, input) => {
|
|
9
9
|
const app = _.get(input, '_zapier.app');
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
// once we have node 14 everywhere, this can be:
|
|
12
|
+
// let source = _.get(app, 'legacy.scriptingSource') ?? app.legacyScriptingSource;
|
|
13
|
+
let source = _.get(app, 'legacy.scriptingSource');
|
|
14
|
+
source = source === undefined ? app.legacyScriptingSource : source;
|
|
15
|
+
|
|
13
16
|
if (source === undefined) {
|
|
14
17
|
// Don't initialize z.legacyScripting for a pure CLI app
|
|
15
18
|
return null;
|
|
@@ -26,8 +29,8 @@ const createLegacyScriptingRunner = (z, input) => {
|
|
|
26
29
|
let LegacyScriptingRunner, version;
|
|
27
30
|
try {
|
|
28
31
|
LegacyScriptingRunner = require('zapier-platform-legacy-scripting-runner');
|
|
29
|
-
version =
|
|
30
|
-
.version;
|
|
32
|
+
version =
|
|
33
|
+
require('zapier-platform-legacy-scripting-runner/package.json').version;
|
|
31
34
|
} catch (e) {
|
|
32
35
|
// Find it in cwd, in case we're developing legacy-scripting-runner itself
|
|
33
36
|
const cwd = process.cwd();
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { Transform } = require('stream');
|
|
4
|
+
|
|
3
5
|
const _ = require('lodash');
|
|
4
6
|
|
|
5
7
|
const request = require('./request-client-internal');
|
|
6
8
|
const { simpleTruncate, recurseReplace } = require('./data');
|
|
7
|
-
const ZapierPromise = require('./promise');
|
|
8
9
|
const {
|
|
9
10
|
DEFAULT_LOGGING_HTTP_API_KEY,
|
|
10
11
|
DEFAULT_LOGGING_HTTP_ENDPOINT,
|
|
@@ -19,6 +20,10 @@ const {
|
|
|
19
20
|
// not really a public function, but it came from here originally
|
|
20
21
|
const { isUrlWithSecrets } = require('@zapier/secret-scrubber/lib/convenience');
|
|
21
22
|
|
|
23
|
+
// The payload size per request to stream logs. This should be slighly lower
|
|
24
|
+
// than the limit (16 MB) on the server side.
|
|
25
|
+
const LOG_STREAM_BYTES_LIMIT = 15 * 1024 * 1024;
|
|
26
|
+
|
|
22
27
|
const isUrl = (url) => {
|
|
23
28
|
try {
|
|
24
29
|
// eslint-disable-next-line no-new
|
|
@@ -121,7 +126,75 @@ const buildSensitiveValues = (event, data) => {
|
|
|
121
126
|
];
|
|
122
127
|
};
|
|
123
128
|
|
|
124
|
-
|
|
129
|
+
class LogStream extends Transform {
|
|
130
|
+
constructor(options) {
|
|
131
|
+
super(options);
|
|
132
|
+
this.bytesWritten = 0;
|
|
133
|
+
this.request = this._newRequest(options.url, options.token);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
_newRequest(url, token) {
|
|
137
|
+
const httpOptions = {
|
|
138
|
+
url,
|
|
139
|
+
method: 'POST',
|
|
140
|
+
headers: {
|
|
141
|
+
'Content-Type': 'application/x-ndjson',
|
|
142
|
+
'X-Token': token,
|
|
143
|
+
},
|
|
144
|
+
body: this,
|
|
145
|
+
};
|
|
146
|
+
return request(httpOptions).catch((err) => {
|
|
147
|
+
// Swallow logging errors. This will show up in AWS logs at least.
|
|
148
|
+
console.error(
|
|
149
|
+
'Error making log request:',
|
|
150
|
+
err,
|
|
151
|
+
'http options:',
|
|
152
|
+
httpOptions
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
_transform(chunk, encoding, callback) {
|
|
158
|
+
this.push(chunk);
|
|
159
|
+
this.bytesWritten += Buffer.byteLength(chunk, encoding);
|
|
160
|
+
callback();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Implements singleton for LogStream. The goal is for every sendLog() call we
|
|
165
|
+
// reuse the same request until the request body grows too big and exceeds
|
|
166
|
+
// LOG_STREAM_BYTES_LIMIT.
|
|
167
|
+
class LogStreamFactory {
|
|
168
|
+
constructor() {
|
|
169
|
+
this._logStream = null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
getOrCreate(url, token) {
|
|
173
|
+
if (this._logStream) {
|
|
174
|
+
if (this._logStream.bytesWritten < LOG_STREAM_BYTES_LIMIT) {
|
|
175
|
+
// Reuse the same request for efficiency
|
|
176
|
+
return this._logStream;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// End this one before creating another
|
|
180
|
+
this._logStream.end();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
this._logStream = new LogStream({ url, token });
|
|
184
|
+
return this._logStream;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async end() {
|
|
188
|
+
if (this._logStream) {
|
|
189
|
+
this._logStream.end();
|
|
190
|
+
const response = await this._logStream.request;
|
|
191
|
+
this._logStream = null;
|
|
192
|
+
return response;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const sendLog = async (logStreamFactory, options, event, message, data) => {
|
|
125
198
|
data = _.extend({}, data || {}, event.logExtra || {});
|
|
126
199
|
data.log_type = data.log_type || 'console';
|
|
127
200
|
|
|
@@ -148,20 +221,6 @@ const sendLog = (options, event, message, data) => {
|
|
|
148
221
|
safeData.request_headers = formatHeaders(safeData.request_headers);
|
|
149
222
|
safeData.response_headers = formatHeaders(safeData.response_headers);
|
|
150
223
|
|
|
151
|
-
const body = {
|
|
152
|
-
message: safeMessage,
|
|
153
|
-
data: safeData,
|
|
154
|
-
token: options.token,
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const httpOptions = {
|
|
158
|
-
url: options.endpoint,
|
|
159
|
-
method: 'POST',
|
|
160
|
-
headers: { 'Content-Type': 'application/json' },
|
|
161
|
-
body: JSON.stringify(body),
|
|
162
|
-
timeout: 3000,
|
|
163
|
-
};
|
|
164
|
-
|
|
165
224
|
if (event.logToStdout) {
|
|
166
225
|
toStdout(event, message, unsafeData);
|
|
167
226
|
}
|
|
@@ -172,24 +231,36 @@ const sendLog = (options, event, message, data) => {
|
|
|
172
231
|
}
|
|
173
232
|
|
|
174
233
|
if (options.token) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
});
|
|
185
|
-
} else {
|
|
186
|
-
return ZapierPromise.resolve();
|
|
234
|
+
const logStream = logStreamFactory.getOrCreate(
|
|
235
|
+
options.endpoint,
|
|
236
|
+
options.token
|
|
237
|
+
);
|
|
238
|
+
logStream.write(
|
|
239
|
+
// JSON Lines format: It's important the serialized JSON object itself has
|
|
240
|
+
// no line breaks, and after an object it ends with a line break.
|
|
241
|
+
JSON.stringify({ message: safeMessage, data: safeData }) + '\n'
|
|
242
|
+
);
|
|
187
243
|
}
|
|
188
244
|
};
|
|
189
245
|
|
|
190
246
|
/*
|
|
191
247
|
Creates low level logging function that POSTs to endpoint (GL by default).
|
|
192
248
|
Use internally; do not expose to devs.
|
|
249
|
+
|
|
250
|
+
Usage:
|
|
251
|
+
|
|
252
|
+
const logger = createLogger(event, options);
|
|
253
|
+
|
|
254
|
+
// These will reuse the same request to the log server
|
|
255
|
+
logger('log message here', { log_type: 'console' });
|
|
256
|
+
logger('another log', { log_type: 'console' });
|
|
257
|
+
logger('200 GET https://example.com', { log_type: 'http' });
|
|
258
|
+
|
|
259
|
+
// After an invocation, the Lambda handler MUST call logger.end() to close
|
|
260
|
+
// the log stream. Otherwise, it will hang!
|
|
261
|
+
logger.end().finally(() => {
|
|
262
|
+
// anything else you want to do to finish an invocation
|
|
263
|
+
});
|
|
193
264
|
*/
|
|
194
265
|
const createLogger = (event, options) => {
|
|
195
266
|
options = options || {};
|
|
@@ -201,7 +272,13 @@ const createLogger = (event, options) => {
|
|
|
201
272
|
token: process.env.LOGGING_TOKEN || event.token,
|
|
202
273
|
});
|
|
203
274
|
|
|
204
|
-
|
|
275
|
+
const logStreamFactory = new LogStreamFactory();
|
|
276
|
+
const logger = sendLog.bind(undefined, logStreamFactory, options, event);
|
|
277
|
+
|
|
278
|
+
logger.end = async () => {
|
|
279
|
+
return logStreamFactory.end();
|
|
280
|
+
};
|
|
281
|
+
return logger;
|
|
205
282
|
};
|
|
206
283
|
|
|
207
284
|
module.exports = createLogger;
|