zapier-platform-core 15.8.0 → 15.9.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": "15.8.0",
3
+ "version": "15.9.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/",
@@ -52,7 +52,7 @@
52
52
  "node-fetch": "2.6.7",
53
53
  "oauth-sign": "0.9.0",
54
54
  "semver": "7.5.2",
55
- "zapier-platform-schema": "15.8.0"
55
+ "zapier-platform-schema": "15.9.0"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/node-fetch": "^2.6.11",
@@ -2,19 +2,28 @@
2
2
 
3
3
  const constants = require('../../constants');
4
4
  const cleaner = require('../../tools/cleaner');
5
+ const responseStasher = require('../../tools/create-response-stasher');
5
6
 
6
- /*
7
- TODO: Some services _do not_ enjoy having 6mb+ responses returned, so
8
- we may need to user/request pre-signed S3 URLs (or somewhere else?)
9
- to stash the large response, and return a pointer.
10
- */
11
- const largeResponseCachePointer = (output) => {
12
- const size = JSON.stringify(cleaner.maskOutput(output)).length;
13
- if (size > constants.RESPONSE_SIZE_LIMIT) {
14
- console.log(
15
- `Oh no! Payload is ${size}, which is larger than ${constants.RESPONSE_SIZE_LIMIT}.`
16
- );
17
- // TODO: use envelope feature and to build RPC to get signed S3 upload URL.
7
+ const largeResponseCachePointer = async (output) => {
8
+ const response = cleaner.maskOutput(output);
9
+
10
+ const autostashLimit = output.input._zapier.event.autostashPayloadOutputLimit;
11
+
12
+ const payload = JSON.stringify(response.results);
13
+ const size = payload.length;
14
+
15
+ // If autostash limit is defined, and is within the range, stash the response
16
+ // If it is -1, stash the response regardless of size
17
+ // If the limit is defined and is out of range, let lambda deal with it
18
+ if (
19
+ (autostashLimit &&
20
+ size >= constants.RESPONSE_SIZE_LIMIT &&
21
+ size <= autostashLimit) ||
22
+ autostashLimit === -1
23
+ ) {
24
+ const url = await responseStasher(output.input, payload, size);
25
+ output.resultsUrl = url;
26
+ output.results = Array.isArray(output.results) ? [] : {};
18
27
  }
19
28
  return output;
20
29
  };
@@ -29,7 +29,10 @@ const injectZObject = (input) => {
29
29
  generateCallbackUrl: createCallbackHigherOrderFunction(input),
30
30
  hash: hashing.hashify,
31
31
  JSON: createJSONtool(),
32
- require: (moduleName) => require(moduleName),
32
+ require: (moduleName) =>
33
+ require(require.resolve(moduleName, {
34
+ paths: module.paths.concat([process.cwd()]),
35
+ })),
33
36
  stashFile: createFileStasher(input),
34
37
  };
35
38
 
@@ -130,7 +130,8 @@ const createBundleBank = (appRaw, event = {}, serializeFunc = (x) => x) => {
130
130
  }, {});
131
131
  };
132
132
 
133
- const maskOutput = (output) => _.pick(output, 'results', 'status');
133
+ const maskOutput = (output) =>
134
+ _.pick(output, 'results', 'status', 'resultsUrl');
134
135
 
135
136
  // These normalize functions are called after the initial before middleware that
136
137
  // cleans the request. The reason is that we need to know why a value is empty
@@ -9,15 +9,11 @@ const { randomBytes } = require('crypto');
9
9
 
10
10
  const _ = require('lodash');
11
11
  const contentDisposition = require('content-disposition');
12
- const FormData = require('form-data');
12
+
13
13
  const mime = require('mime-types');
14
14
 
15
- const request = require('./request-client-internal');
16
15
  const { UPLOAD_MAX_SIZE, NON_STREAM_UPLOAD_MAX_SIZE } = require('../constants');
17
-
18
- const LENGTH_ERR_MESSAGE =
19
- 'We could not calculate the length of your file - please ' +
20
- 'pass a knownLength like z.stashFile(f, knownLength)';
16
+ const uploader = require('./uploader');
21
17
 
22
18
  const DEFAULT_FILE_NAME = 'unnamedfile';
23
19
  const DEFAULT_CONTENT_TYPE = 'application/octet-stream';
@@ -175,64 +171,6 @@ const resolveToBufferStringStream = async (responseOrData) => {
175
171
  );
176
172
  };
177
173
 
178
- const uploader = async (
179
- signedPostData,
180
- bufferStringStream,
181
- knownLength,
182
- filename,
183
- contentType
184
- ) => {
185
- filename = path.basename(filename).replace('"', '');
186
-
187
- const fields = {
188
- ...signedPostData.fields,
189
- 'Content-Disposition': contentDisposition(filename),
190
- 'Content-Type': contentType,
191
- };
192
-
193
- const form = new FormData();
194
-
195
- Object.entries(fields).forEach(([key, value]) => {
196
- form.append(key, value);
197
- });
198
-
199
- form.append('file', bufferStringStream, {
200
- knownLength,
201
- contentType,
202
- filename,
203
- });
204
-
205
- // Try to catch the missing length early, before upload to S3 fails.
206
- try {
207
- form.getLengthSync();
208
- } catch (err) {
209
- throw new Error(LENGTH_ERR_MESSAGE);
210
- }
211
-
212
- // Send to S3 with presigned request.
213
- const response = await request({
214
- url: signedPostData.url,
215
- method: 'POST',
216
- body: form,
217
- });
218
-
219
- if (response.status === 204) {
220
- return new URL(signedPostData.fields.key, signedPostData.url).href;
221
- }
222
-
223
- if (
224
- response.content &&
225
- response.content.includes &&
226
- response.content.includes(
227
- 'You must provide the Content-Length HTTP header.'
228
- )
229
- ) {
230
- throw new Error(LENGTH_ERR_MESSAGE);
231
- }
232
-
233
- throw new Error(`Got ${response.status} - ${response.content}`);
234
- };
235
-
236
174
  const ensureUploadMaxSizeNotExceeded = (streamOrData, length) => {
237
175
  let uploadMaxSize = NON_STREAM_UPLOAD_MAX_SIZE;
238
176
  let uploadMethod = 'non-streaming';
@@ -242,7 +180,9 @@ const ensureUploadMaxSizeNotExceeded = (streamOrData, length) => {
242
180
  }
243
181
 
244
182
  if (length && length > uploadMaxSize) {
245
- throw new Error(`${length} bytes is too big, ${uploadMaxSize} is the max for ${uploadMethod} data.`);
183
+ throw new Error(
184
+ `${length} bytes is too big, ${uploadMaxSize} is the max for ${uploadMethod} data.`
185
+ );
246
186
  }
247
187
  };
248
188
 
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const uploader = require('./uploader');
5
+ const crypto = require('crypto');
6
+
7
+ const withRetry = async (fn, retries = 3, delay = 100, attempt = 0) => {
8
+ try {
9
+ return await fn();
10
+ } catch (error) {
11
+ if (attempt >= retries) {
12
+ throw error;
13
+ }
14
+
15
+ await new Promise((resolve) => setTimeout(resolve, delay));
16
+ return withRetry(fn, retries, delay, attempt + 1);
17
+ }
18
+ };
19
+
20
+ // responseStasher uploads the data and returns the URL that points to that data.
21
+ const stashResponse = async (input, response, size) => {
22
+ const rpc = _.get(input, '_zapier.rpc');
23
+
24
+ if (!rpc) {
25
+ throw new Error('rpc is not available');
26
+ }
27
+ const signedPostData = await rpc('get_presigned_upload_post_data');
28
+ return withRetry(
29
+ _.partial(
30
+ uploader,
31
+ signedPostData,
32
+ response.toString(), // accept JSON string to send to uploader.
33
+ size,
34
+ crypto.randomUUID() + '.txt',
35
+ 'text/plain'
36
+ )
37
+ );
38
+ };
39
+
40
+ module.exports = stashResponse;
@@ -0,0 +1,69 @@
1
+ const path = require('path');
2
+
3
+ const FormData = require('form-data');
4
+ const contentDisposition = require('content-disposition');
5
+
6
+ const request = require('./request-client-internal');
7
+ const LENGTH_ERR_MESSAGE =
8
+ 'We could not calculate the length of your file - please ' +
9
+ 'pass a knownLength like z.stashFile(f, knownLength)';
10
+
11
+ const uploader = async (
12
+ signedPostData,
13
+ bufferStringStream,
14
+ knownLength,
15
+ filename,
16
+ contentType
17
+ ) => {
18
+ filename = path.basename(filename).replace('"', '');
19
+
20
+ const fields = {
21
+ ...signedPostData.fields,
22
+ 'Content-Disposition': contentDisposition(filename),
23
+ 'Content-Type': contentType,
24
+ };
25
+
26
+ const form = new FormData();
27
+
28
+ Object.entries(fields).forEach(([key, value]) => {
29
+ form.append(key, value);
30
+ });
31
+
32
+ form.append('file', bufferStringStream, {
33
+ knownLength,
34
+ contentType,
35
+ filename,
36
+ });
37
+
38
+ // Try to catch the missing length early, before upload to S3 fails.
39
+ try {
40
+ form.getLengthSync();
41
+ } catch (err) {
42
+ throw new Error(LENGTH_ERR_MESSAGE);
43
+ }
44
+
45
+ // Send to S3 with presigned request.
46
+ const response = await request({
47
+ url: signedPostData.url,
48
+ method: 'POST',
49
+ body: form,
50
+ });
51
+
52
+ if (response.status === 204) {
53
+ return new URL(signedPostData.fields.key, signedPostData.url).href;
54
+ }
55
+
56
+ if (
57
+ response.content &&
58
+ response.content.includes &&
59
+ response.content.includes(
60
+ 'You must provide the Content-Length HTTP header.'
61
+ )
62
+ ) {
63
+ throw new Error(LENGTH_ERR_MESSAGE);
64
+ }
65
+
66
+ throw new Error(`Got ${response.status} - ${response.content}`);
67
+ };
68
+
69
+ module.exports = uploader;