zapier-platform-core 12.0.3 → 12.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zapier-platform-core",
3
- "version": "12.0.3",
3
+ "version": "12.2.0",
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/",
@@ -40,17 +40,18 @@
40
40
  },
41
41
  "engineStrict": true,
42
42
  "dependencies": {
43
- "@zapier/secret-scrubber": "^1.0.3",
43
+ "@zapier/secret-scrubber": "^1.0.7",
44
44
  "bluebird": "3.7.2",
45
45
  "content-disposition": "0.5.3",
46
46
  "dotenv": "9.0.2",
47
47
  "form-data": "4.0.0",
48
48
  "lodash": "4.17.21",
49
49
  "mime-types": "2.1.34",
50
+ "node-abort-controller": "3.0.1",
50
51
  "node-fetch": "2.6.7",
51
52
  "oauth-sign": "0.9.0",
52
53
  "semver": "7.3.5",
53
- "zapier-platform-schema": "12.0.3"
54
+ "zapier-platform-schema": "12.2.0"
54
55
  },
55
56
  "devDependencies": {
56
57
  "adm-zip": "0.5.5",
@@ -1,9 +1,11 @@
1
1
  'use strict';
2
2
 
3
+ const { promisify } = require('util');
3
4
  const { Transform } = require('stream');
4
5
  const { parse: querystringParse } = require('querystring');
5
6
 
6
7
  const _ = require('lodash');
8
+ const { AbortController } = require('node-abort-controller');
7
9
 
8
10
  const request = require('./request-client-internal');
9
11
  const { simpleTruncate, recurseReplace, truncateData } = require('./data');
@@ -28,6 +30,10 @@ const {
28
30
  // than the limit (16 MB) on the server side.
29
31
  const LOG_STREAM_BYTES_LIMIT = 15 * 1024 * 1024;
30
32
 
33
+ const DEFAULT_LOGGER_TIMEOUT = 200;
34
+
35
+ const sleep = promisify(setTimeout);
36
+
31
37
  const isUrl = (url) => {
32
38
  try {
33
39
  // eslint-disable-next-line no-new
@@ -167,6 +173,7 @@ class LogStream extends Transform {
167
173
  constructor(options) {
168
174
  super(options);
169
175
  this.bytesWritten = 0;
176
+ this.controller = new AbortController();
170
177
  this.request = this._newRequest(options.url, options.token);
171
178
  }
172
179
 
@@ -179,15 +186,20 @@ class LogStream extends Transform {
179
186
  'X-Token': token,
180
187
  },
181
188
  body: this,
189
+ signal: this.controller.signal,
182
190
  };
183
191
  return request(httpOptions).catch((err) => {
192
+ if (err.name === 'AbortError') {
193
+ return {
194
+ status: 200,
195
+ content: 'aborted',
196
+ };
197
+ }
198
+
184
199
  // Swallow logging errors. This will show up in AWS logs at least.
185
- console.error(
186
- 'Error making log request:',
187
- err,
188
- 'http options:',
189
- httpOptions
190
- );
200
+ // Don't need to log for AbortError because that happens when we abort
201
+ // on purpose.
202
+ console.error('Error making log request:', err);
191
203
  });
192
204
  }
193
205
 
@@ -196,6 +208,10 @@ class LogStream extends Transform {
196
208
  this.bytesWritten += Buffer.byteLength(chunk, encoding);
197
209
  callback();
198
210
  }
211
+
212
+ abort() {
213
+ this.controller.abort();
214
+ }
199
215
  }
200
216
 
201
217
  // Implements singleton for LogStream. The goal is for every sendLog() call we
@@ -222,17 +238,35 @@ class LogStreamFactory {
222
238
  return this._logStream;
223
239
  }
224
240
 
225
- async end() {
241
+ // Ends the logger and gets a response from the log server. Optionally takes
242
+ // timeoutToAbort to specify how many milliseconds we want to wait before
243
+ // force aborting the connection to the log server.
244
+ async end(timeoutToAbort = DEFAULT_LOGGER_TIMEOUT) {
226
245
  // Mark the factory as ended. This suggests that any logStream.write() that
227
246
  // follows should end() right away.
228
247
  this.ended = true;
248
+ let response;
229
249
 
230
250
  if (this._logStream) {
231
251
  this._logStream.end();
232
- const response = await this._logStream.request;
252
+
253
+ const clock =
254
+ timeoutToAbort > 0 ? sleep(timeoutToAbort) : Promise.resolve(undefined);
255
+ const responsePromise = this._logStream.request;
256
+
257
+ const result = await Promise.race([clock, responsePromise]);
258
+ const isTimeout = !result;
259
+ if (isTimeout) {
260
+ this._logStream.abort();
261
+ // Expect to get a `{content: 'aborted'}` response
262
+ response = await responsePromise;
263
+ } else {
264
+ response = result;
265
+ }
266
+
233
267
  this._logStream = null;
234
- return response;
235
268
  }
269
+ return response;
236
270
  }
237
271
  }
238
272
 
@@ -294,7 +328,7 @@ const sendLog = async (logStreamFactory, options, event, message, data) => {
294
328
  // (bad) callback that is still running after the Lambda handler returns?
295
329
  // We need to make sure the bad callback ends the logger as well.
296
330
  // Otherwise, it will hang!
297
- logStreamFactory.end();
331
+ logStreamFactory.end(DEFAULT_LOGGER_TIMEOUT);
298
332
  }
299
333
  }
300
334
  };
@@ -331,8 +365,8 @@ const createLogger = (event, options) => {
331
365
  const logStreamFactory = new LogStreamFactory();
332
366
  const logger = sendLog.bind(undefined, logStreamFactory, options, event);
333
367
 
334
- logger.end = async () => {
335
- return logStreamFactory.end();
368
+ logger.end = async (timeoutToAbort = DEFAULT_LOGGER_TIMEOUT) => {
369
+ return logStreamFactory.end(timeoutToAbort);
336
370
  };
337
371
  return logger;
338
372
  };
@@ -14,7 +14,7 @@ const convertResourceDos = (appRaw) => {
14
14
  const triggers = {};
15
15
  const searches = {};
16
16
  const creates = {};
17
- const searchOrCreates = {};
17
+ const searchCreates = {};
18
18
 
19
19
  _.each(appRaw.resources, (resource) => {
20
20
  let search, create, trigger;
@@ -54,8 +54,7 @@ const convertResourceDos = (appRaw) => {
54
54
  }
55
55
 
56
56
  if (search && create && isVisible(search) && isVisible(create)) {
57
- const searchOrCreate = {
58
- // key: `${resource.key}SearchOrCreate`,
57
+ searchCreates[search.key] = {
59
58
  key: `${search.key}`, // For now this is a Zapier editor limitation (has to match search)
60
59
  display: {
61
60
  label: `Find or Create ${resource.noun}`,
@@ -64,11 +63,19 @@ const convertResourceDos = (appRaw) => {
64
63
  search: search.key,
65
64
  create: create.key,
66
65
  };
67
- searchOrCreates[searchOrCreate.key] = searchOrCreate;
68
66
  }
69
67
  });
70
68
 
71
- return { triggers, searches, creates, searchOrCreates };
69
+ const extras = { triggers, searches, creates };
70
+
71
+ // searchAndCreates is an alias for searchOrCreates. Schema validation makes sure only one of them is defined.
72
+ if (appRaw.searchAndCreates) {
73
+ extras.searchAndCreates = searchCreates;
74
+ } else {
75
+ extras.searchOrCreates = searchCreates;
76
+ }
77
+
78
+ return extras;
72
79
  };
73
80
 
74
81
  /* When a trigger/search/create (action) links to a resource, we walk up to
@@ -106,7 +113,13 @@ const compileApp = (appRaw) => {
106
113
  appRaw = schemaTools.findSourceRequireFunctions(appRaw);
107
114
  const extras = convertResourceDos(appRaw);
108
115
 
109
- const actions = ['triggers', 'searches', 'creates', 'searchOrCreates'];
116
+ const actions = [
117
+ 'triggers',
118
+ 'searches',
119
+ 'creates',
120
+ 'searchOrCreates',
121
+ 'searchAndCreates',
122
+ ];
110
123
  let problemKeys = [];
111
124
 
112
125
  actions.forEach((a) => {
@@ -132,11 +145,21 @@ const compileApp = (appRaw) => {
132
145
  appRaw.triggers = _.extend({}, extras.triggers, appRaw.triggers || {});
133
146
  appRaw.searches = _.extend({}, extras.searches, appRaw.searches || {});
134
147
  appRaw.creates = _.extend({}, extras.creates, appRaw.creates || {});
135
- appRaw.searchOrCreates = _.extend(
136
- {},
137
- extras.searchOrCreates,
138
- appRaw.searchOrCreates || {}
139
- );
148
+
149
+ // searchAndCreates is an alias for searchOrCreates. Schema validation makes sure only one of them is defined.
150
+ // If the searchAndCreates key exists, we use it and avoid adding a searchOrCreates key to the appRaw object.
151
+ // Otherwise, we add a searchOrCreates object to the appRaw object, which defaults to an empty object.
152
+ if (appRaw.searchAndCreates) {
153
+ appRaw.searchAndCreates = {
154
+ ...extras.searchAndCreates,
155
+ ...appRaw.searchAndCreates,
156
+ };
157
+ } else {
158
+ appRaw.searchOrCreates = {
159
+ ...extras.searchOrCreates,
160
+ ...appRaw.searchOrCreates,
161
+ };
162
+ }
140
163
 
141
164
  _.each(appRaw.triggers, (trigger) => {
142
165
  appRaw.triggers[trigger.key] = copyPropertiesFromResource(