zapier-platform-core 15.18.0 → 15.19.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 +2 -2
- package/src/tools/create-dehydrator.js +9 -3
- package/src/tools/create-http-patch.js +9 -2
- package/src/tools/create-lambda-handler.js +15 -2
- package/src/tools/fetch-logger.js +107 -0
- package/src/tools/http.js +2 -0
- package/types/zapier.custom.d.ts +77 -11
- package/types/zapier.generated.d.ts +23 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zapier-platform-core",
|
|
3
|
-
"version": "15.
|
|
3
|
+
"version": "15.19.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/",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"node-fetch": "2.6.7",
|
|
54
54
|
"oauth-sign": "0.9.0",
|
|
55
55
|
"semver": "7.5.2",
|
|
56
|
-
"zapier-platform-schema": "15.
|
|
56
|
+
"zapier-platform-schema": "15.19.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/node-fetch": "^2.6.11",
|
|
@@ -9,19 +9,25 @@ const wrapHydrate = require('./wrap-hydrate');
|
|
|
9
9
|
const createDehydrator = (input, type = 'method') => {
|
|
10
10
|
const app = _.get(input, '_zapier.app');
|
|
11
11
|
|
|
12
|
-
return (func, inputData) => {
|
|
12
|
+
return (func, inputData, cacheExpiration) => {
|
|
13
13
|
inputData = inputData || {};
|
|
14
14
|
if (inputData.inputData) {
|
|
15
15
|
throw new DehydrateError(
|
|
16
16
|
'Oops! You passed a full `bundle` - really you should pass what you want under `inputData`!'
|
|
17
17
|
);
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
const payload = {
|
|
20
20
|
type,
|
|
21
21
|
method: resolveMethodPath(app, func),
|
|
22
22
|
// inputData vs. bundle is a legacy oddity
|
|
23
23
|
bundle: _.omit(inputData, 'environment'), // don't leak the environment
|
|
24
|
-
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (cacheExpiration) {
|
|
27
|
+
payload.cacheExpiration = cacheExpiration;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return wrapHydrate(payload);
|
|
25
31
|
};
|
|
26
32
|
};
|
|
27
33
|
|
|
@@ -48,7 +48,7 @@ const createHttpPatch = (event) => {
|
|
|
48
48
|
return originalRequest(options, callback);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
// Ignore requests made via the request client
|
|
51
|
+
// Ignore requests made via the request client (z.request)
|
|
52
52
|
if (_.get(options.headers, 'user-agent', []).indexOf('Zapier') !== -1) {
|
|
53
53
|
return originalRequest(options, callback);
|
|
54
54
|
}
|
|
@@ -112,7 +112,14 @@ const createHttpPatch = (event) => {
|
|
|
112
112
|
}
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
const originalEmit = response.emit;
|
|
116
|
+
|
|
117
|
+
response.emit = function (event, ...args) {
|
|
118
|
+
if (event === 'data') {
|
|
119
|
+
chunks.push(args[0]);
|
|
120
|
+
}
|
|
121
|
+
return originalEmit.apply(this, [event, ...args]);
|
|
122
|
+
};
|
|
116
123
|
response.on('end', logResponse);
|
|
117
124
|
response.on('error', logResponse);
|
|
118
125
|
|
|
@@ -17,6 +17,7 @@ const createLogger = require('./create-logger');
|
|
|
17
17
|
const createRpcClient = require('./create-rpc-client');
|
|
18
18
|
const environmentTools = require('./environment');
|
|
19
19
|
const schemaTools = require('./schema');
|
|
20
|
+
const wrapFetchWithLogger = require('./fetch-logger');
|
|
20
21
|
const ZapierPromise = require('./promise');
|
|
21
22
|
|
|
22
23
|
const isDefinedPrimitive = (value) => {
|
|
@@ -189,10 +190,15 @@ const createLambdaHandler = (appRawOrPath) => {
|
|
|
189
190
|
// Wait for all async events to complete before callback returns.
|
|
190
191
|
// This is not strictly necessary since this is the default now when
|
|
191
192
|
// using the callback; just putting it here to be explicit.
|
|
193
|
+
// In some cases, the code hangs and never exits because the event loop is not
|
|
194
|
+
// empty, so we can override the default behavior and exit after the app is done.
|
|
192
195
|
context.callbackWaitsForEmptyEventLoop = true;
|
|
196
|
+
if (event.skipWaitForAsync === true) {
|
|
197
|
+
context.callbackWaitsForEmptyEventLoop = false;
|
|
198
|
+
}
|
|
193
199
|
|
|
194
200
|
// replace native Promise with bluebird (works better with domains)
|
|
195
|
-
if (!event.calledFromCli) {
|
|
201
|
+
if (!event.calledFromCli || event.calledFromCliInvoke) {
|
|
196
202
|
ZapierPromise.patchGlobal();
|
|
197
203
|
}
|
|
198
204
|
|
|
@@ -253,10 +259,16 @@ const createLambdaHandler = (appRawOrPath) => {
|
|
|
253
259
|
|
|
254
260
|
const { skipHttpPatch } = appRaw.flags || {};
|
|
255
261
|
// Adds logging for _all_ kinds of http(s) requests, no matter the library
|
|
256
|
-
if (
|
|
262
|
+
if (
|
|
263
|
+
!skipHttpPatch &&
|
|
264
|
+
(!event.calledFromCli || event.calledFromCliInvoke)
|
|
265
|
+
) {
|
|
257
266
|
const httpPatch = createHttpPatch(event);
|
|
258
267
|
httpPatch(require('http'), logger);
|
|
259
268
|
httpPatch(require('https'), logger); // 'https' needs to be patched separately
|
|
269
|
+
if (global.fetch) {
|
|
270
|
+
global.fetch = wrapFetchWithLogger(global.fetch, logger);
|
|
271
|
+
}
|
|
260
272
|
}
|
|
261
273
|
|
|
262
274
|
// TODO: Avoid calling prepareApp(appRaw) repeatedly here as createApp()
|
|
@@ -264,6 +276,7 @@ const createLambdaHandler = (appRawOrPath) => {
|
|
|
264
276
|
const compiledApp = schemaTools.prepareApp(appRaw);
|
|
265
277
|
|
|
266
278
|
const input = createInput(compiledApp, event, logger, logBuffer, rpc);
|
|
279
|
+
|
|
267
280
|
return app(input);
|
|
268
281
|
})
|
|
269
282
|
.then((output) => {
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
|
|
3
|
+
const { ALLOWED_HTTP_DATA_CONTENT_TYPES } = require('./http');
|
|
4
|
+
|
|
5
|
+
const stringifyRequestData = (data) => {
|
|
6
|
+
// Be careful not to consume the data if it's a stream
|
|
7
|
+
if (typeof data === 'string') {
|
|
8
|
+
return data;
|
|
9
|
+
} else if (data instanceof URLSearchParams) {
|
|
10
|
+
return data.toString();
|
|
11
|
+
} else if (global.FormData && data instanceof global.FormData) {
|
|
12
|
+
return '<FormData>';
|
|
13
|
+
} else {
|
|
14
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#setting_a_body
|
|
15
|
+
// for other possible body types
|
|
16
|
+
return '<unsupported format>';
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const normalizeRequestInfo = (input, init) => {
|
|
21
|
+
const result = {
|
|
22
|
+
method: 'GET',
|
|
23
|
+
headers: {},
|
|
24
|
+
data: '',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
if (global.Request && input instanceof global.Request) {
|
|
28
|
+
result.url = input.url;
|
|
29
|
+
result.method = input.method || result.method;
|
|
30
|
+
result.headers =
|
|
31
|
+
Object.fromEntries(input.headers.entries()) || result.headers;
|
|
32
|
+
if (input.body) {
|
|
33
|
+
result.data = stringifyRequestData(input.body);
|
|
34
|
+
}
|
|
35
|
+
} else if (typeof input.toString === 'function') {
|
|
36
|
+
// This condition includes `typeof input === 'string'` as well
|
|
37
|
+
result.url = input.toString();
|
|
38
|
+
} else {
|
|
39
|
+
// Don't log if we don't recognize the input type
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (init) {
|
|
44
|
+
result.method = init.method || result.method;
|
|
45
|
+
result.headers = init.headers || result.headers;
|
|
46
|
+
if (init.body) {
|
|
47
|
+
result.data = stringifyRequestData(init.body);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Is this request made by z.request()?
|
|
55
|
+
const isZapierUserAgent = (headers) =>
|
|
56
|
+
_.get(headers, 'user-agent', []).indexOf('Zapier') !== -1;
|
|
57
|
+
|
|
58
|
+
const shouldIncludeResponseContent = (contentType) => {
|
|
59
|
+
for (const ctype of ALLOWED_HTTP_DATA_CONTENT_TYPES) {
|
|
60
|
+
if (contentType.includes(ctype)) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const stringifyResponseContent = async (response) => {
|
|
68
|
+
// Be careful not to consume the original response body, which is why we clone it
|
|
69
|
+
return response.clone().text();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Usage:
|
|
73
|
+
// global.fetch = wrapFetchWithLogger(global.fetch, logger);
|
|
74
|
+
const wrapFetchWithLogger = (fetchFunc, logger) => {
|
|
75
|
+
if (fetchFunc.patchedByZapier) {
|
|
76
|
+
return fetchFunc;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const newFetch = async function (input, init) {
|
|
80
|
+
const response = await fetchFunc(input, init);
|
|
81
|
+
const requestInfo = normalizeRequestInfo(input, init);
|
|
82
|
+
if (requestInfo && !isZapierUserAgent(requestInfo.headers)) {
|
|
83
|
+
const responseContentType = response.headers.get('content-type');
|
|
84
|
+
|
|
85
|
+
logger(`${response.status} ${requestInfo.method} ${requestInfo.url}`, {
|
|
86
|
+
log_type: 'http',
|
|
87
|
+
request_type: 'patched-devplatform-outbound',
|
|
88
|
+
request_url: requestInfo.url,
|
|
89
|
+
request_method: requestInfo.method,
|
|
90
|
+
request_headers: requestInfo.headers,
|
|
91
|
+
request_data: requestInfo.data,
|
|
92
|
+
request_via_client: false,
|
|
93
|
+
response_status_code: response.status,
|
|
94
|
+
response_headers: Object.fromEntries(response.headers.entries()),
|
|
95
|
+
response_content: shouldIncludeResponseContent(responseContentType)
|
|
96
|
+
? await stringifyResponseContent(response)
|
|
97
|
+
: '<unsupported format>',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return response;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
newFetch.patchedByZapier = true;
|
|
104
|
+
return newFetch;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
module.exports = wrapFetchWithLogger;
|
package/src/tools/http.js
CHANGED
|
@@ -7,6 +7,7 @@ const JSON_TYPE_UTF8 = 'application/json; charset=utf-8';
|
|
|
7
7
|
const BINARY_TYPE = 'application/octet-stream';
|
|
8
8
|
const HTML_TYPE = 'text/html';
|
|
9
9
|
const TEXT_TYPE = 'text/plain';
|
|
10
|
+
const TEXT_TYPE_UTF8 = 'text/plain; charset=utf-8';
|
|
10
11
|
const YAML_TYPE = 'application/yaml';
|
|
11
12
|
const XML_TYPE = 'application/xml';
|
|
12
13
|
const JSONAPI_TYPE = 'application/vnd.api+json';
|
|
@@ -17,6 +18,7 @@ const ALLOWED_HTTP_DATA_CONTENT_TYPES = new Set([
|
|
|
17
18
|
JSON_TYPE_UTF8,
|
|
18
19
|
HTML_TYPE,
|
|
19
20
|
TEXT_TYPE,
|
|
21
|
+
TEXT_TYPE_UTF8,
|
|
20
22
|
YAML_TYPE,
|
|
21
23
|
XML_TYPE,
|
|
22
24
|
JSONAPI_TYPE,
|
package/types/zapier.custom.d.ts
CHANGED
|
@@ -37,13 +37,78 @@ export interface Bundle<InputData = { [x: string]: any }> {
|
|
|
37
37
|
inputDataRaw: { [x: string]: string };
|
|
38
38
|
meta: {
|
|
39
39
|
isBulkRead: boolean;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* If true, this poll is being used to populate a dynamic dropdown.
|
|
43
|
+
* You only need to return the fields you specified (such as id and
|
|
44
|
+
* name), though returning everything is fine too.
|
|
45
|
+
*/
|
|
40
46
|
isFillingDynamicDropdown: boolean;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* If true, this run was initiated manually via the Zap Editor.
|
|
50
|
+
*/
|
|
41
51
|
isLoadingSample: boolean;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* If true, the results of this poll will be used to initialize the
|
|
55
|
+
* deduplication list rather than trigger a zap. You should grab as
|
|
56
|
+
* many items as possible.
|
|
57
|
+
*/
|
|
42
58
|
isPopulatingDedupe: boolean;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* (legacy property) If true, the poll was triggered by a user
|
|
62
|
+
* testing their account (via clicking “test” or during setup). We
|
|
63
|
+
* use this data to populate the auth label, but it’s mostly used to
|
|
64
|
+
* verify we made a successful authenticated request
|
|
65
|
+
*
|
|
66
|
+
* @deprecated
|
|
67
|
+
*/
|
|
43
68
|
isTestingAuth: boolean;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The number of items you should fetch. -1 indicates there’s no
|
|
72
|
+
* limit. Build this into your calls insofar as you are able.
|
|
73
|
+
*/
|
|
44
74
|
limit: number;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Used in paging to uniquely identify which page of results should
|
|
78
|
+
* be returned.
|
|
79
|
+
*/
|
|
45
80
|
page: number;
|
|
46
|
-
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* When a create is called as part of a search-or-create step,
|
|
84
|
+
* this will be the key of the search.
|
|
85
|
+
*/
|
|
86
|
+
withSearch?: string;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The timezone the user has configured for their account or
|
|
90
|
+
* specific automation. Received as TZ identifier, such as
|
|
91
|
+
* “America/New_York”.
|
|
92
|
+
*/
|
|
93
|
+
timezone: string | null;
|
|
94
|
+
|
|
95
|
+
/** @deprecated */
|
|
96
|
+
zap?: {
|
|
97
|
+
/** @deprecated */
|
|
98
|
+
id: string;
|
|
99
|
+
/** @deprecated */
|
|
100
|
+
user: {
|
|
101
|
+
/** @deprecated use meta.timezone instead. */
|
|
102
|
+
timezone: string;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Contains metadata about the input fields, optionally provided
|
|
108
|
+
* by the inputField.meta property. Useful for storing extra data
|
|
109
|
+
* in dynamically created input fields.
|
|
110
|
+
*/
|
|
111
|
+
inputFields: { [fieldKey: string]: { [metaKey: string]: string | number | boolean } };
|
|
47
112
|
};
|
|
48
113
|
rawRequest?: Partial<{
|
|
49
114
|
method: HttpMethod;
|
|
@@ -108,6 +173,8 @@ export interface HttpRequestOptions {
|
|
|
108
173
|
middlewareData?: Record<string, any>;
|
|
109
174
|
}
|
|
110
175
|
|
|
176
|
+
type HttpRequestOptionsWithUrl = HttpRequestOptions & { url: string };
|
|
177
|
+
|
|
111
178
|
interface BaseHttpResponse {
|
|
112
179
|
status: number;
|
|
113
180
|
headers: Headers;
|
|
@@ -133,7 +200,8 @@ export interface RawHttpResponse<T = any> extends BaseHttpResponse {
|
|
|
133
200
|
|
|
134
201
|
type DehydrateFunc = <T>(
|
|
135
202
|
func: (z: ZObject, bundle: Bundle<T>) => any,
|
|
136
|
-
inputData
|
|
203
|
+
inputData?: T,
|
|
204
|
+
cacheExpiration?: number
|
|
137
205
|
) => string;
|
|
138
206
|
|
|
139
207
|
export interface ZObject {
|
|
@@ -143,16 +211,14 @@ export interface ZObject {
|
|
|
143
211
|
url: string,
|
|
144
212
|
options: HttpRequestOptions & { raw: true }
|
|
145
213
|
): Promise<RawHttpResponse<T>>;
|
|
146
|
-
<T = any>(
|
|
147
|
-
|
|
148
|
-
|
|
214
|
+
<T = any>(options: HttpRequestOptionsWithUrl & { raw: true }): Promise<
|
|
215
|
+
RawHttpResponse<T>
|
|
216
|
+
>;
|
|
149
217
|
|
|
150
218
|
<T = any>(url: string, options?: HttpRequestOptions): Promise<
|
|
151
219
|
HttpResponse<T>
|
|
152
220
|
>;
|
|
153
|
-
<T = any>(options:
|
|
154
|
-
HttpResponse<T>
|
|
155
|
-
>;
|
|
221
|
+
<T = any>(options: HttpRequestOptionsWithUrl): Promise<HttpResponse<T>>;
|
|
156
222
|
};
|
|
157
223
|
|
|
158
224
|
console: Console;
|
|
@@ -213,7 +279,7 @@ export interface ZObject {
|
|
|
213
279
|
|
|
214
280
|
cache: {
|
|
215
281
|
get: (key: string) => Promise<any>;
|
|
216
|
-
set: (key: string, value: any, ttl?: number) => Promise<boolean>;
|
|
282
|
+
set: (key: string, value: any, ttl?: number, scope?: string[], nx?: boolean) => Promise<boolean|null>;
|
|
217
283
|
delete: (key: string) => Promise<boolean>;
|
|
218
284
|
};
|
|
219
285
|
}
|
|
@@ -230,10 +296,10 @@ export type PerformFunction<BI = Record<string, any>, R = any> = (
|
|
|
230
296
|
) => Promise<R>;
|
|
231
297
|
|
|
232
298
|
export type BeforeRequestMiddleware = (
|
|
233
|
-
request:
|
|
299
|
+
request: HttpRequestOptionsWithUrl,
|
|
234
300
|
z: ZObject,
|
|
235
301
|
bundle: Bundle
|
|
236
|
-
) =>
|
|
302
|
+
) => HttpRequestOptionsWithUrl | Promise<HttpRequestOptionsWithUrl>;
|
|
237
303
|
|
|
238
304
|
export type AfterResponseMiddleware = (
|
|
239
305
|
response: HttpResponse,
|
|
@@ -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: 15.
|
|
7
|
+
* zapier-platform-schema version: 15.19.0
|
|
8
8
|
* schema-to-ts compiler version: 0.1.0
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -187,6 +187,22 @@ export type FieldChoices =
|
|
|
187
187
|
| { [k: string]: unknown }
|
|
188
188
|
| (string | FieldChoiceWithLabel)[];
|
|
189
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Allows for additional metadata to be stored on the field.
|
|
192
|
+
*
|
|
193
|
+
* [Docs: FieldMetaSchema](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#FieldMetaSchema)
|
|
194
|
+
*/
|
|
195
|
+
export interface FieldMeta {
|
|
196
|
+
/**
|
|
197
|
+
* Only string, integer or boolean values are allowed.
|
|
198
|
+
*
|
|
199
|
+
* This interface was referenced by `FieldMetaSchema`'s JSON-Schema
|
|
200
|
+
* definition
|
|
201
|
+
* via the `patternProperty` "[^\s]+".
|
|
202
|
+
*/
|
|
203
|
+
[k: string]: string | number | boolean;
|
|
204
|
+
}
|
|
205
|
+
|
|
190
206
|
/**
|
|
191
207
|
* Defines a field an app either needs as input, or gives as output.
|
|
192
208
|
* In addition to the requirements below, the following keys are
|
|
@@ -323,6 +339,12 @@ export interface Field {
|
|
|
323
339
|
* "https://{{input}}.yourdomain.com").
|
|
324
340
|
*/
|
|
325
341
|
inputFormat?: string;
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Allows for additional metadata to be stored on the field.
|
|
345
|
+
* Supports simple key-values only (no sub-objects or arrays).
|
|
346
|
+
*/
|
|
347
|
+
meta?: FieldMeta;
|
|
326
348
|
}
|
|
327
349
|
|
|
328
350
|
/**
|