zapier-platform-core 17.0.2 → 17.0.4

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": "17.0.2",
3
+ "version": "17.0.4",
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/",
@@ -62,7 +62,7 @@
62
62
  "node-fetch": "2.7.0",
63
63
  "oauth-sign": "0.9.0",
64
64
  "semver": "7.7.1",
65
- "zapier-platform-schema": "17.0.2"
65
+ "zapier-platform-schema": "17.0.4"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/node-fetch": "^2.6.11",
package/src/constants.js CHANGED
@@ -23,7 +23,11 @@ const RENDER_ONLY_METHODS = [
23
23
  'authentication.oauth1Config.authorizeUrl',
24
24
  ];
25
25
 
26
- const REQUEST_OBJECT_SHORTHAND_OPTIONS = { isShorthand: true, replace: true };
26
+ const REPLACE_CURLIES = Symbol('replaceCurlies');
27
+
28
+ const REQUEST_OBJECT_SHORTHAND_OPTIONS = {
29
+ [REPLACE_CURLIES]: true,
30
+ };
27
31
 
28
32
  const DEFAULT_LOGGING_HTTP_ENDPOINT = 'https://httplogger.zapier.com/input';
29
33
  const DEFAULT_LOGGING_HTTP_API_KEY = 'R24hzu86v3jntwtX2DtYECeWAB'; // It's ok, this isn't PROD
@@ -65,13 +69,14 @@ module.exports = {
65
69
  IS_TESTING,
66
70
  KILL_MAX_LIMIT,
67
71
  KILL_MIN_LIMIT,
72
+ NON_STREAM_UPLOAD_MAX_SIZE,
68
73
  PACKAGE_NAME,
69
74
  PACKAGE_VERSION,
70
75
  RENDER_ONLY_METHODS,
76
+ REPLACE_CURLIES,
71
77
  REQUEST_OBJECT_SHORTHAND_OPTIONS,
72
78
  RESPONSE_SIZE_LIMIT,
73
79
  SAFE_LOG_KEYS,
74
80
  STATUSES,
75
81
  UPLOAD_MAX_SIZE,
76
- NON_STREAM_UPLOAD_MAX_SIZE,
77
82
  };
@@ -10,7 +10,6 @@ const executeRequest = (input) => {
10
10
  if (!options.url) {
11
11
  throw new Error('Missing url for request');
12
12
  }
13
- options.replace = true;
14
13
  return input.z.request(options).then(responseCleaner);
15
14
  };
16
15
 
package/src/execute.js CHANGED
@@ -10,18 +10,15 @@ const prepareRequest = require('./http-middlewares/before/prepare-request');
10
10
  const constants = require('./constants');
11
11
 
12
12
  const executeHttpRequest = (input, options) => {
13
- options = _.extend(
14
- {},
13
+ options = {
15
14
  // shorthand requests should always throw _unless_ the object specifically opts out
16
15
  // this covers godzilla devs who use shorthand requests (most of them) that rely on the throwing behavior
17
16
  // when we set the app-wide skip for everyone, we don't want their behavior to change
18
17
  // so, this line takes precedence over the global setting, but not the local one (`options`)
19
- {
20
- skipThrowForStatus: false,
21
- },
22
- options,
23
- constants.REQUEST_OBJECT_SHORTHAND_OPTIONS,
24
- );
18
+ skipThrowForStatus: false,
19
+ ...options,
20
+ ...constants.REQUEST_OBJECT_SHORTHAND_OPTIONS,
21
+ };
25
22
  return input.z.request(options).then((response) => {
26
23
  if (response.data === undefined) {
27
24
  throw new Error(
@@ -83,11 +80,10 @@ const execute = (app, input) => {
83
80
  } else if (_.isObject(method) && method.url) {
84
81
  const options = method;
85
82
  if (isRenderOnly(methodName)) {
86
- const requestWithInput = _.extend(
87
- {},
88
- injectInput(input)(options),
89
- constants.REQUEST_OBJECT_SHORTHAND_OPTIONS,
90
- );
83
+ const requestWithInput = {
84
+ ...injectInput(input)(options),
85
+ ...constants.REQUEST_OBJECT_SHORTHAND_OPTIONS,
86
+ };
91
87
  const preparedRequest = addQueryParams(prepareRequest(requestWithInput));
92
88
  return preparedRequest.url;
93
89
  }
@@ -1,7 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const _ = require('lodash');
4
-
5
3
  /*
6
4
  Creates HTTP before middleware that adds some app context
7
5
  to HTTP request options, including the app and event.
@@ -9,7 +7,9 @@ const _ = require('lodash');
9
7
  Useful for HTTP middlewares that need stuff from the app or event.
10
8
  */
11
9
  const injectInput = (input) => {
12
- return (req) => _.extend({}, req, { input });
10
+ return (req) => {
11
+ return { ...req, input };
12
+ };
13
13
  };
14
14
 
15
15
  module.exports = injectInput;
@@ -16,6 +16,8 @@ const {
16
16
  JSON_TYPE_UTF8,
17
17
  } = require('../../tools/http');
18
18
 
19
+ const { REPLACE_CURLIES } = require('../../constants');
20
+
19
21
  const isStream = (obj) => obj instanceof stream.Stream;
20
22
  const isPromise = (obj) => obj && typeof obj.then === 'function';
21
23
 
@@ -50,6 +52,7 @@ const coerceBody = (req) => {
50
52
 
51
53
  // auto coerce form if header says so
52
54
  if (contentType === FORM_TYPE && req.body && !_.isString(req.body)) {
55
+ normalizeEmptyBodyFields(req);
53
56
  req.body = querystring.stringify(req.body).replace(/%20/g, '+');
54
57
  }
55
58
 
@@ -108,8 +111,9 @@ const throwForCurlies = (value, path) => {
108
111
  if (/{{\s*(bundle|process)\.[^}]*}}/.test(value)) {
109
112
  throw new Error(
110
113
  'z.request() no longer supports {{bundle.*}} or {{process.*}} as of v17 ' +
111
- "unless it's used in a shorthand request. " +
112
- 'Use JavaScript template literals instead. ' +
114
+ "unless it's used in a shorthand request defined by the integration. " +
115
+ 'Zapier Customers: Remove "{{curly braces}}" from your request. ' +
116
+ 'Developers: Use JavaScript template literals instead. ' +
113
117
  `Value in violation: "${value}" in attribute "${path.join('.')}".`,
114
118
  );
115
119
  }
@@ -145,33 +149,44 @@ const prepareRequest = function (req) {
145
149
 
146
150
  req = sugarBody(req);
147
151
 
148
- // apply app requestTemplate to request
149
- if (req.merge) {
150
- const requestTemplate = (input._zapier?.app || {}).requestTemplate;
151
- req = requestMerge(requestTemplate, req);
152
- }
153
-
154
- const replaceable = {
155
- url: req.url,
156
- headers: req.headers,
157
- params: req.params,
158
- body: req.body,
159
- };
160
-
161
- if (req.replace) {
162
- // replace {{curlies}} in the request
152
+ if (req[REPLACE_CURLIES] || req.merge) {
163
153
  const bank = createBundleBank(
164
- input._zapier.app,
165
- input._zapier.event,
154
+ input?._zapier?.event || {},
166
155
  req.serializeValueForCurlies,
167
156
  );
168
- req = {
169
- ...req,
170
- ...recurseReplaceBank(replaceable, bank),
157
+
158
+ const requestReplaceable = {
159
+ url: req.url,
160
+ headers: req.headers,
161
+ params: req.params,
162
+ body: req.body,
171
163
  };
172
- } else {
173
- // throw if there's {{curlies}} in the request
174
- throwForCurlies(replaceable);
164
+ if (req[REPLACE_CURLIES]) {
165
+ // replace {{curlies}} in the request
166
+ req = {
167
+ ...req,
168
+ ...recurseReplaceBank(requestReplaceable, bank),
169
+ };
170
+ } else {
171
+ // throw if there's {{curlies}} in the request
172
+ throwForCurlies(requestReplaceable);
173
+ }
174
+
175
+ if (req.merge) {
176
+ // Always replace {{curlies}} in reqeustTemplate regardless of
177
+ // req[REPLACE_CURLIES]
178
+ const requestTemplate = input._zapier?.app?.requestTemplate || {};
179
+ const templateReplaceable = {
180
+ url: requestTemplate.url,
181
+ headers: requestTemplate.headers,
182
+ params: requestTemplate.params,
183
+ body: requestTemplate.body,
184
+ };
185
+ const renderedTemplate = recurseReplaceBank(templateReplaceable, bank);
186
+
187
+ // Apply app.requestTemplate to request
188
+ req = requestMerge(renderedTemplate, req);
189
+ }
175
190
  }
176
191
 
177
192
  req = coerceBody(req);
@@ -43,66 +43,96 @@ const recurseCleanFuncs = (obj, path) => {
43
43
  return obj;
44
44
  };
45
45
 
46
+ const findNextCurlies = (str) => {
47
+ const start = str.indexOf('{{');
48
+ if (start < 0) {
49
+ return {
50
+ start: -1,
51
+ end: -1,
52
+ };
53
+ }
54
+
55
+ const end = str.indexOf('}}', start + 2);
56
+ if (end < 0) {
57
+ return {
58
+ start: -1,
59
+ end: -1,
60
+ };
61
+ }
62
+
63
+ return {
64
+ start,
65
+ end: end + 2,
66
+ };
67
+ };
68
+
46
69
  // Recurse a nested object replace all instances of keys->vals in the bank.
47
70
  const recurseReplaceBank = (obj, bank = {}) => {
48
- const matchesCurlies = /({{.*?}})/;
49
- const matchesKeyRegexMap = Object.keys(bank).reduce((acc, key) => {
50
- // Escape characters (ex. {{foo}} => \\{\\{foo\\}\\} )
51
- acc[key] = new RegExp(key.replace(/[-[\]/{}()\\*+?.^$|]/g, '\\$&'), 'g');
52
- return acc;
53
- }, {});
54
- const replacer = (out) => {
55
- if (!['string', 'number'].includes(typeof out)) {
56
- return out;
71
+ const replacer = (input) => {
72
+ if (typeof input !== 'string') {
73
+ return input;
57
74
  }
58
75
 
59
- // whatever leaves this function replaces values in the calling object
60
- // so, we don't want to return a different data type unless it's a censored string
61
- const originalValue = out;
62
- const originalValueStr = String(out);
63
- let maybeChangedString = originalValueStr;
64
-
65
- Object.keys(bank).forEach((key) => {
66
- const matchesKey = matchesKeyRegexMap[key];
67
- // RegExp.test modifies internal state of the regex object
68
- // since we're re-using regexes, we have to reset that state between calls
69
- // or the second time in a row that the key should match, it misses instead
70
- // see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex
71
- matchesKey.lastIndex = 0;
72
- if (!matchesKey.test(maybeChangedString)) {
73
- return;
76
+ const outputItems = [];
77
+ let inputString = input;
78
+
79
+ // 1000 iterations is just a static upper bound to make infinite loops
80
+ // impossible. Who would have 1000 {{curlies}} in a string... right?
81
+ const MAX_ITERATIONS = 1000;
82
+ for (let i = 0; i < MAX_ITERATIONS; i++) {
83
+ const { start, end } = findNextCurlies(inputString);
84
+ if (start < 0) {
85
+ outputItems.push(inputString);
86
+ break;
87
+ }
88
+
89
+ const head = inputString.slice(0, start);
90
+ const key = inputString.slice(start, end);
91
+ const tail = inputString.slice(end);
92
+
93
+ if (head) {
94
+ outputItems.push(head);
74
95
  }
75
96
 
76
- const valueParts = maybeChangedString
77
- .split(matchesCurlies)
78
- .filter(Boolean);
79
97
  const replacementValue = bank[key];
80
- const isPartOfString =
81
- !matchesCurlies.test(maybeChangedString) || valueParts.length > 1;
82
- const shouldThrowTypeError =
83
- isPartOfString &&
84
- (Array.isArray(replacementValue) || _.isPlainObject(replacementValue));
85
-
86
- if (shouldThrowTypeError) {
87
- const bareKey = _.trimEnd(_.trimStart(key, '{'), '}');
88
- throw new TypeError(
89
- 'Cannot reliably interpolate objects or arrays into a string. ' +
90
- `Variable \`${bareKey}\` is an ${getObjectType(
91
- replacementValue,
92
- )}:\n"${replacementValue}"`,
93
- );
98
+ if (replacementValue == null) {
99
+ // No match in the bank
100
+ outputItems.push(key);
101
+ } else {
102
+ const isPartOfString = head || tail;
103
+ if (
104
+ isPartOfString &&
105
+ (Array.isArray(replacementValue) || _.isPlainObject(replacementValue))
106
+ ) {
107
+ const bareKey = key.slice(2, -2); // '{{key}}' -> 'key'
108
+ throw new TypeError(
109
+ 'Cannot reliably interpolate objects or arrays into a string. ' +
110
+ `Variable \`${bareKey}\` is an ${getObjectType(
111
+ replacementValue,
112
+ )}:\n"${replacementValue}"`,
113
+ );
114
+ } else {
115
+ outputItems.push(replacementValue);
116
+ }
94
117
  }
95
118
 
96
- maybeChangedString = isPartOfString
97
- ? valueParts.join('').replace(matchesKey, replacementValue)
98
- : replacementValue;
99
- });
119
+ inputString = tail;
120
+ if (!inputString) {
121
+ break;
122
+ }
123
+
124
+ if (i === MAX_ITERATIONS - 1) {
125
+ // The input string does have more than 1000 {{curlies}}, just return
126
+ // the rest of the string without replacing.
127
+ outputItems.push(inputString);
128
+ }
129
+ }
100
130
 
101
- if (originalValueStr === maybeChangedString) {
102
- // we didn't censor or replace the value, so return the original
103
- return originalValue;
131
+ if (outputItems.length === 1 && typeof outputItems[0] !== 'string') {
132
+ return outputItems[0];
133
+ } else {
134
+ return outputItems.join('');
104
135
  }
105
- return maybeChangedString;
106
136
  };
107
137
  return recurseReplace(obj, replacer);
108
138
  };
@@ -113,7 +143,7 @@ const finalizeBundle = pipe(
113
143
  );
114
144
 
115
145
  // Takes a raw app and bundle and composes a bank of {{key}}->val
116
- const createBundleBank = (appRaw, event = {}, serializeFunc = (x) => x) => {
146
+ const createBundleBank = (event = {}, serializeFunc = (x) => x) => {
117
147
  const bank = {
118
148
  bundle: finalizeBundle(event.bundle),
119
149
  process: {
@@ -110,7 +110,7 @@ const createHttpPatch = (event) => {
110
110
  } else {
111
111
  const responseBody = _.map(chunks, (chunk) =>
112
112
  chunk.toString(),
113
- ).join('\n');
113
+ ).join('');
114
114
  sendToLogger(responseBody);
115
115
  }
116
116
  };
@@ -3,6 +3,7 @@
3
3
  const _ = require('lodash');
4
4
 
5
5
  const requestClean = require('./request-clean');
6
+ const { REPLACE_CURLIES } = require('../constants');
6
7
 
7
8
  // Do a merge with case-insensitive keys in the .header, and drop empty .header keys
8
9
  const caseInsensitiveMerge = (requestOne, requestTwo, requestThree) => {
@@ -14,6 +15,12 @@ const caseInsensitiveMerge = (requestOne, requestTwo, requestThree) => {
14
15
  // This is a very quick & efficient merge for all of request's properties
15
16
  const mergedRequest = _.merge(requestOne, requestTwo, requestThree);
16
17
 
18
+ // _.merge() ignores symbols. REPLACE_CURLIES is a symbol, so we need to add
19
+ // it back
20
+ if (requestThree[REPLACE_CURLIES]) {
21
+ mergedRequest[REPLACE_CURLIES] = requestThree[REPLACE_CURLIES];
22
+ }
23
+
17
24
  // Now to cleanup headers, we start on the last request (the one with priority) and work backwards to add the keys that don't already exist
18
25
  // NOTE: This is done "manually" instead of a _.merge or Object.assign() because we need case-insensitivity
19
26
  const mergedRequestHeaders = requestThree.headers || {};
@@ -4,7 +4,7 @@
4
4
  * files, and/or the schema-to-ts tool and run its CLI to regenerate
5
5
  * these typings.
6
6
  *
7
- * zapier-platform-schema version: 17.0.1
7
+ * zapier-platform-schema version: 17.0.3
8
8
  * schema-to-ts compiler version: 0.1.0
9
9
  */
10
10
  import type {