@zapier/zapier-sdk 0.25.0 → 0.25.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/CHANGELOG.md +12 -0
- package/README.md +95 -26
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +24 -27
- package/dist/api/client.test.js +78 -0
- package/dist/index.cjs +64 -38
- package/dist/index.mjs +64 -38
- package/dist/plugins/fetch/index.d.ts.map +1 -1
- package/dist/plugins/fetch/index.js +26 -7
- package/dist/plugins/fetch/index.test.js +93 -0
- package/dist/plugins/fetch/schemas.d.ts +1 -1
- package/dist/plugins/fetch/schemas.d.ts.map +1 -1
- package/dist/plugins/fetch/schemas.js +2 -1
- package/dist/utils/type-guard-utils.d.ts +7 -0
- package/dist/utils/type-guard-utils.d.ts.map +1 -0
- package/dist/utils/type-guard-utils.js +11 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @zapier/zapier-sdk
|
|
2
2
|
|
|
3
|
+
## 0.25.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 642713b: Update README with more detail and more attention on fetch.
|
|
8
|
+
|
|
9
|
+
## 0.25.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- aacaf22: Improved `fetch` to automatically set the correct `Content-Type` header based on the request body type. `FormData`, `Blob`, and other binary bodies are now handled correctly instead of being misidentified as JSON.
|
|
14
|
+
|
|
3
15
|
## 0.25.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Table of Contents
|
|
4
4
|
|
|
5
|
+
- [Closed Beta](#closed-beta)
|
|
6
|
+
- [Documentation](#documentation)
|
|
5
7
|
- [Installation](#installation)
|
|
6
8
|
- [Quick Start](#quick-start)
|
|
7
9
|
- [Available Functions](#available-functions)
|
|
@@ -31,10 +33,26 @@
|
|
|
31
33
|
- [HTTP Requests](#http-requests)
|
|
32
34
|
- [`fetch`](#fetch)
|
|
33
35
|
|
|
36
|
+
## Closed Beta
|
|
37
|
+
|
|
38
|
+
At the time of publishing this package, the Zapier SDK requires an invite for most features. If you want to try it out, let us know at the following page!
|
|
39
|
+
|
|
40
|
+
https://sdk-beta.zapier.app/signup
|
|
41
|
+
|
|
42
|
+
## Documentation
|
|
43
|
+
|
|
44
|
+
The official documentation will soon be available at:
|
|
45
|
+
|
|
46
|
+
https://docs.zapier.com/sdk
|
|
47
|
+
|
|
48
|
+
While the dust settles, that documentation may be incomplete, and this README may have additional documentation.
|
|
49
|
+
|
|
50
|
+
Agents are sometimes blocked from viewing docs on npm, so you may want to provide them a link to the official docs or copy the docs here into a prompt.
|
|
51
|
+
|
|
34
52
|
## Installation
|
|
35
53
|
|
|
36
54
|
```bash
|
|
37
|
-
# If starting a new project:
|
|
55
|
+
# If you're starting a new project:
|
|
38
56
|
cd your-project-dir
|
|
39
57
|
npm init -y
|
|
40
58
|
npx tsc --init
|
|
@@ -46,20 +64,52 @@ npm install -D @zapier/zapier-sdk-cli @types/node typescript
|
|
|
46
64
|
|
|
47
65
|
## Quick Start
|
|
48
66
|
|
|
67
|
+
Assuming you've installed the CLI package into your project (see instructions above), you (or an agent) can start using it right away. This is useful for introspecting actions, authentications, etc. that you want to use in code, but you can also use it to directly use integrations.
|
|
68
|
+
|
|
49
69
|
```bash
|
|
50
|
-
#
|
|
70
|
+
# See all available commands
|
|
71
|
+
npx zapier-sdk --help
|
|
72
|
+
|
|
73
|
+
# Login to Zapier.
|
|
51
74
|
npx zapier-sdk login
|
|
52
75
|
|
|
53
|
-
#
|
|
54
|
-
npx zapier-sdk
|
|
76
|
+
# Search from thousands of supported apps.
|
|
77
|
+
npx zapier-sdk list-apps --search "gmail"
|
|
78
|
+
# The output will show you the valid keys next to the app title like this:
|
|
79
|
+
# 1. Gmail (GoogleMailV2CLIAPI, gmail)
|
|
80
|
+
|
|
81
|
+
# Run any action for the app, using one of the app keys.
|
|
82
|
+
npx zapier-sdk run-action gmail
|
|
83
|
+
# This will ask you for the type of action you want to run.
|
|
84
|
+
# `search` or `write` are typically great for testing.
|
|
85
|
+
# Note that you usually need an authentication (connection) to the app to run
|
|
86
|
+
# the action. If you don't already have one, you can create a new one at:
|
|
87
|
+
# https://zapier.com/app/assets/connections
|
|
88
|
+
|
|
89
|
+
# List authentications for an app.
|
|
90
|
+
npx zapier-sdk list-authentications gmail
|
|
91
|
+
# Or only list the ones you own.
|
|
92
|
+
npx zapier-sdk list-authentications gmail --owner me
|
|
93
|
+
# Or just grab the first one.
|
|
94
|
+
npx zapier-sdk find-first-authentication gmail --owner me
|
|
95
|
+
|
|
96
|
+
# Make any API request to an app using your authentication (connection).
|
|
97
|
+
npx zapier-sdk fetch "https://gmail.googleapis.com/gmail/v1/users/me/labels" --authentication-id 123
|
|
98
|
+
```
|
|
55
99
|
|
|
56
|
-
|
|
57
|
-
# you don't know the slug or key, just use `list-apps` search to find it.
|
|
58
|
-
zapier-sdk list-apps --search "google sheets"
|
|
100
|
+
The following is optional, but if you want to call our actions from code, it can be helpful to install TypeScript types for those actions. You can also just use `fetch` to make arbitrary API requests. In that case, you don't really need this, and instead you might need types or a good agent that will look up third party API docs.
|
|
59
101
|
|
|
102
|
+
```bash
|
|
103
|
+
# Search for apps that you want to use.
|
|
104
|
+
npx zapier-sdk list-apps --search "slack"
|
|
105
|
+
npx zapier-sdk list-apps --search "google sheets"
|
|
60
106
|
# The output will show you the valid keys next to the app title like this:
|
|
107
|
+
# 1. Slack (SlackCLIAPI, slack)
|
|
61
108
|
# 1. Google Sheets (GoogleSheetsV2CLIAPI, google-sheets, google_sheets)
|
|
62
109
|
|
|
110
|
+
# Generate TypeScript types for actions and fields of any apps you want to use.
|
|
111
|
+
npx zapier-sdk add slack google-sheets
|
|
112
|
+
|
|
63
113
|
# By default, types will be generated inside your `src` or `lib` folder if you
|
|
64
114
|
# have one or otherwise the root folder. Make sure your `tsconfig.json` file
|
|
65
115
|
# includes the generated type files.
|
|
@@ -68,20 +118,20 @@ zapier-sdk list-apps --search "google sheets"
|
|
|
68
118
|
npx zapier-sdk add slack google-sheets --types-output ./types
|
|
69
119
|
```
|
|
70
120
|
|
|
121
|
+
Now let's write some code!
|
|
122
|
+
|
|
71
123
|
```typescript
|
|
72
124
|
import { createZapierSdk } from "@zapier/zapier-sdk";
|
|
73
125
|
|
|
74
126
|
// ######## Initialize Zapier SDK ########
|
|
75
|
-
// Option 1: Running `zapier-sdk login` authenticates through your browser
|
|
76
|
-
//
|
|
127
|
+
// Option 1: Running `zapier-sdk login` authenticates through your browser and
|
|
128
|
+
// stores a token on your local machine. As long as you have the CLI package
|
|
129
|
+
// installed as a development dependency, the SDK will automatically use it.
|
|
77
130
|
const zapier = createZapierSdk();
|
|
78
131
|
|
|
79
|
-
// Option 2:
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
// });
|
|
83
|
-
|
|
84
|
-
// Option 3: Manually provide a client ID and client secret.
|
|
132
|
+
// Option 2: Provide a client ID and client secret.
|
|
133
|
+
// You can get these by running `zapier-sdk create-client-credentials`.
|
|
134
|
+
// This allows you to run the SDK in a server/serverless environment.
|
|
85
135
|
// const zapier = createZapierSdk({
|
|
86
136
|
// credentials: {
|
|
87
137
|
// clientId: "your_client_id_here", // or use ZAPIER_CREDENTIALS_CLIENT_ID env var
|
|
@@ -89,6 +139,12 @@ const zapier = createZapierSdk();
|
|
|
89
139
|
// },
|
|
90
140
|
// });
|
|
91
141
|
|
|
142
|
+
// Option 3: Provide a valid Zapier token.
|
|
143
|
+
// This is for partner OAuth or internal Zapier use.
|
|
144
|
+
// const zapier = createZapierSdk({
|
|
145
|
+
// credentials: "your_zapier_token_here", // or use ZAPIER_CREDENTIALS env var
|
|
146
|
+
// });
|
|
147
|
+
|
|
92
148
|
// ######## Access Apps ########
|
|
93
149
|
// List methods return a promise for a page with {data, nextCursor}.
|
|
94
150
|
const { data: firstPageApps, nextCursor } = await zapier.listApps();
|
|
@@ -165,7 +221,8 @@ console.log({
|
|
|
165
221
|
});
|
|
166
222
|
|
|
167
223
|
// Option 2: Access actions via the `apps` proxy
|
|
168
|
-
// If you've generated TS types for an app using `zapier-sdk add`, you can
|
|
224
|
+
// If you've generated TS types for an app using `zapier-sdk add`, you can
|
|
225
|
+
// access the app's actions like this:
|
|
169
226
|
|
|
170
227
|
// await zapier.apps.slack.read.channles({});
|
|
171
228
|
// await zapier.apps.slack.write.send_message({})
|
|
@@ -218,6 +275,18 @@ console.log({
|
|
|
218
275
|
// For example, either of the following are valid:
|
|
219
276
|
// zapier.apps["google-sheets"]
|
|
220
277
|
// zapier.apps.google_sheets
|
|
278
|
+
|
|
279
|
+
// We have tens of thousands of actions across thousands of apps, but you're
|
|
280
|
+
// still not limited to those. You can make any arbitrary API request to an app
|
|
281
|
+
// using the same authentications with `.fetch`! This returns a response just
|
|
282
|
+
// like a normal `fetch` call in JavaScript.
|
|
283
|
+
const emojiResponse = await zapier.fetch("https://slack.com/api/emoji.list", {
|
|
284
|
+
authenticationId: myAuths[0].id,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const emojiData = await emojiResponse.json();
|
|
288
|
+
|
|
289
|
+
console.log(emojiData.emoji);
|
|
221
290
|
```
|
|
222
291
|
|
|
223
292
|
## Available Functions
|
|
@@ -808,16 +877,16 @@ Make authenticated HTTP requests to any API through Zapier's Relay service. Pass
|
|
|
808
877
|
|
|
809
878
|
**Parameters:**
|
|
810
879
|
|
|
811
|
-
| Name | Type
|
|
812
|
-
| -------------------------- |
|
|
813
|
-
| `url` | `string, custom`
|
|
814
|
-
| `init` | `object`
|
|
815
|
-
| ↳ `method` | `string`
|
|
816
|
-
| ↳ `headers` | `object`
|
|
817
|
-
| ↳ `body` | `string, custom, custom` | ❌ | — | — | Request body — JSON strings are auto-detected and Content-Type is set accordingly |
|
|
818
|
-
| ↳ `authenticationId` | `string, number`
|
|
819
|
-
| ↳ `callbackUrl` | `string`
|
|
820
|
-
| ↳ `authenticationTemplate` | `string`
|
|
880
|
+
| Name | Type | Required | Default | Possible Values | Description |
|
|
881
|
+
| -------------------------- | -------------------------------- | -------- | ------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
882
|
+
| `url` | `string, custom` | ✅ | — | — | The full URL of the API endpoint to call (proxied through Zapier's Relay service) |
|
|
883
|
+
| `init` | `object` | ❌ | — | — | Request options including method, headers, body, and authentication |
|
|
884
|
+
| ↳ `method` | `string` | ❌ | — | `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS` | HTTP method for the request (defaults to GET) |
|
|
885
|
+
| ↳ `headers` | `object` | ❌ | — | — | HTTP headers to include in the request |
|
|
886
|
+
| ↳ `body` | `string, custom, custom, record` | ❌ | — | — | Request body — plain objects and JSON strings are auto-detected and Content-Type is set accordingly |
|
|
887
|
+
| ↳ `authenticationId` | `string, number` | ❌ | — | — | Authentication ID to use for this action |
|
|
888
|
+
| ↳ `callbackUrl` | `string` | ❌ | — | — | URL to send async response to (makes request async) |
|
|
889
|
+
| ↳ `authenticationTemplate` | `string` | ❌ | — | — | Optional JSON string authentication template to bypass Notary lookup |
|
|
821
890
|
|
|
822
891
|
**Returns:** `Promise<Response>`
|
|
823
892
|
|
package/dist/api/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAGjB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAGjB,MAAM,SAAS,CAAC;AAkgBjB,eAAO,MAAM,eAAe,GAAI,SAAS,gBAAgB,KAAG,SAW3D,CAAC"}
|
package/dist/api/client.js
CHANGED
|
@@ -9,6 +9,7 @@ import { createDebugLogger, createDebugFetch } from "./debug";
|
|
|
9
9
|
import { pollUntilComplete } from "./polling";
|
|
10
10
|
import { resolveAuthToken, invalidateCredentialsToken } from "../auth";
|
|
11
11
|
import { getZapierBaseUrl } from "../utils/url-utils";
|
|
12
|
+
import { isPlainObject } from "../utils/type-guard-utils";
|
|
12
13
|
import { ZapierApiError, ZapierAuthenticationError, ZapierValidationError, ZapierNotFoundError, } from "../types/errors";
|
|
13
14
|
// Configuration for paths
|
|
14
15
|
const pathConfig = {
|
|
@@ -27,7 +28,26 @@ class ZapierApiClient {
|
|
|
27
28
|
constructor(options) {
|
|
28
29
|
this.options = options;
|
|
29
30
|
this.fetch = async (path, init) => {
|
|
30
|
-
|
|
31
|
+
if (!path.startsWith("/")) {
|
|
32
|
+
throw new ZapierValidationError(`fetch expects a path starting with '/', got: ${path}`);
|
|
33
|
+
}
|
|
34
|
+
if (init?.body && (isPlainObject(init.body) || Array.isArray(init.body))) {
|
|
35
|
+
init.body = JSON.stringify(init.body);
|
|
36
|
+
}
|
|
37
|
+
const { url, pathConfig } = this.buildUrl(path, init?.searchParams);
|
|
38
|
+
const builtHeaders = await this.buildHeaders(init, pathConfig);
|
|
39
|
+
const inputHeaders = new Headers(init?.headers ?? {});
|
|
40
|
+
const mergedHeaders = new Headers();
|
|
41
|
+
builtHeaders.forEach((value, key) => {
|
|
42
|
+
mergedHeaders.set(key, value);
|
|
43
|
+
});
|
|
44
|
+
inputHeaders.forEach((value, key) => {
|
|
45
|
+
mergedHeaders.set(key, value);
|
|
46
|
+
});
|
|
47
|
+
return await this.options.fetch(url, {
|
|
48
|
+
...init,
|
|
49
|
+
headers: mergedHeaders,
|
|
50
|
+
});
|
|
31
51
|
};
|
|
32
52
|
this.get = async (path, options = {}) => {
|
|
33
53
|
return this.fetchJson("GET", path, undefined, options);
|
|
@@ -43,7 +63,7 @@ class ZapierApiClient {
|
|
|
43
63
|
};
|
|
44
64
|
this.poll = async (path, options = {}) => {
|
|
45
65
|
return pollUntilComplete({
|
|
46
|
-
fetchPoll: () => this.
|
|
66
|
+
fetchPoll: () => this.fetch(path, {
|
|
47
67
|
method: "GET",
|
|
48
68
|
searchParams: options.searchParams,
|
|
49
69
|
authRequired: options.authRequired,
|
|
@@ -320,13 +340,13 @@ class ZapierApiClient {
|
|
|
320
340
|
const wasMissingAuthToken = options.authRequired &&
|
|
321
341
|
(await this.getAuthToken({ requiredScopes: options.requiredScopes })) ==
|
|
322
342
|
null;
|
|
323
|
-
const response = await this.
|
|
343
|
+
const response = await this.fetch(path, {
|
|
324
344
|
...options,
|
|
325
345
|
method,
|
|
326
346
|
body: data != null ? JSON.stringify(data) : undefined,
|
|
327
347
|
headers,
|
|
328
348
|
});
|
|
329
|
-
//
|
|
349
|
+
// fetch already handled all auth and headers
|
|
330
350
|
const result = await this.handleResponse({
|
|
331
351
|
response,
|
|
332
352
|
customErrorHandler: options.customErrorHandler,
|
|
@@ -342,29 +362,6 @@ class ZapierApiClient {
|
|
|
342
362
|
}
|
|
343
363
|
return result;
|
|
344
364
|
}
|
|
345
|
-
// Plain fetch method for API paths (must start with /)
|
|
346
|
-
async plainFetch(path, fetchOptions) {
|
|
347
|
-
if (!path.startsWith("/")) {
|
|
348
|
-
throw new ZapierValidationError(`plainFetch expects a path starting with '/', got: ${path}`);
|
|
349
|
-
}
|
|
350
|
-
if (fetchOptions?.body && typeof fetchOptions.body === "object") {
|
|
351
|
-
fetchOptions.body = JSON.stringify(fetchOptions.body);
|
|
352
|
-
}
|
|
353
|
-
const { url, pathConfig } = this.buildUrl(path, fetchOptions?.searchParams);
|
|
354
|
-
const builtHeaders = await this.buildHeaders(fetchOptions, pathConfig);
|
|
355
|
-
const inputHeaders = new Headers(fetchOptions?.headers ?? {});
|
|
356
|
-
const mergedHeaders = new Headers();
|
|
357
|
-
builtHeaders.forEach((value, key) => {
|
|
358
|
-
mergedHeaders.set(key, value);
|
|
359
|
-
});
|
|
360
|
-
inputHeaders.forEach((value, key) => {
|
|
361
|
-
mergedHeaders.set(key, value);
|
|
362
|
-
});
|
|
363
|
-
return await this.options.fetch(url, {
|
|
364
|
-
...fetchOptions,
|
|
365
|
-
headers: mergedHeaders,
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
365
|
}
|
|
369
366
|
export const createZapierApi = (options) => {
|
|
370
367
|
const { debug = false, fetch: originalFetch = globalThis.fetch } = options;
|
package/dist/api/client.test.js
CHANGED
|
@@ -228,4 +228,82 @@ describe("ApiClient", () => {
|
|
|
228
228
|
expect(mockFetch).toHaveBeenCalledWith("http://localhost:3000/a/b/c/api/v0/sdk/relay/some/path", expect.any(Object));
|
|
229
229
|
});
|
|
230
230
|
});
|
|
231
|
+
describe("fetch body serialization", () => {
|
|
232
|
+
beforeEach(() => {
|
|
233
|
+
mockResolveAuthToken.mockResolvedValue("test-token");
|
|
234
|
+
});
|
|
235
|
+
it("should JSON.stringify plain object bodies", async () => {
|
|
236
|
+
const client = createZapierApi({
|
|
237
|
+
baseUrl: "http://localhost:3000",
|
|
238
|
+
credentials: "test-token",
|
|
239
|
+
debug: false,
|
|
240
|
+
});
|
|
241
|
+
const body = { channel: "C123", text: "hello" };
|
|
242
|
+
await client.fetch("/relay/api.example.com/data", {
|
|
243
|
+
method: "POST",
|
|
244
|
+
body: body,
|
|
245
|
+
});
|
|
246
|
+
const [, options] = mockFetch.mock.calls[0];
|
|
247
|
+
expect(options.body).toBe(JSON.stringify(body));
|
|
248
|
+
});
|
|
249
|
+
it("should JSON.stringify array bodies", async () => {
|
|
250
|
+
const client = createZapierApi({
|
|
251
|
+
baseUrl: "http://localhost:3000",
|
|
252
|
+
credentials: "test-token",
|
|
253
|
+
debug: false,
|
|
254
|
+
});
|
|
255
|
+
const body = [{ id: 1 }, { id: 2 }];
|
|
256
|
+
await client.fetch("/relay/api.example.com/data", {
|
|
257
|
+
method: "POST",
|
|
258
|
+
body: body,
|
|
259
|
+
});
|
|
260
|
+
const [, options] = mockFetch.mock.calls[0];
|
|
261
|
+
expect(options.body).toBe(JSON.stringify(body));
|
|
262
|
+
});
|
|
263
|
+
it("should not stringify FormData bodies", async () => {
|
|
264
|
+
const client = createZapierApi({
|
|
265
|
+
baseUrl: "http://localhost:3000",
|
|
266
|
+
credentials: "test-token",
|
|
267
|
+
debug: false,
|
|
268
|
+
});
|
|
269
|
+
const body = new FormData();
|
|
270
|
+
body.append("file", "contents");
|
|
271
|
+
await client.fetch("/relay/api.example.com/upload", {
|
|
272
|
+
method: "POST",
|
|
273
|
+
body,
|
|
274
|
+
});
|
|
275
|
+
const [, options] = mockFetch.mock.calls[0];
|
|
276
|
+
expect(options.body).toBe(body);
|
|
277
|
+
});
|
|
278
|
+
it("should not stringify Blob bodies", async () => {
|
|
279
|
+
const client = createZapierApi({
|
|
280
|
+
baseUrl: "http://localhost:3000",
|
|
281
|
+
credentials: "test-token",
|
|
282
|
+
debug: false,
|
|
283
|
+
});
|
|
284
|
+
const body = new Blob(["binary data"], {
|
|
285
|
+
type: "application/octet-stream",
|
|
286
|
+
});
|
|
287
|
+
await client.fetch("/relay/api.example.com/upload", {
|
|
288
|
+
method: "POST",
|
|
289
|
+
body,
|
|
290
|
+
});
|
|
291
|
+
const [, options] = mockFetch.mock.calls[0];
|
|
292
|
+
expect(options.body).toBe(body);
|
|
293
|
+
});
|
|
294
|
+
it("should pass string bodies through unchanged", async () => {
|
|
295
|
+
const client = createZapierApi({
|
|
296
|
+
baseUrl: "http://localhost:3000",
|
|
297
|
+
credentials: "test-token",
|
|
298
|
+
debug: false,
|
|
299
|
+
});
|
|
300
|
+
const body = '{"already": "stringified"}';
|
|
301
|
+
await client.fetch("/relay/api.example.com/data", {
|
|
302
|
+
method: "POST",
|
|
303
|
+
body,
|
|
304
|
+
});
|
|
305
|
+
const [, options] = mockFetch.mock.calls[0];
|
|
306
|
+
expect(options.body).toBe(body);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
231
309
|
});
|
package/dist/index.cjs
CHANGED
|
@@ -403,9 +403,10 @@ var FetchInitSchema = zod.z.object({
|
|
|
403
403
|
body: zod.z.union([
|
|
404
404
|
zod.z.string(),
|
|
405
405
|
zod.z.instanceof(FormData),
|
|
406
|
-
zod.z.instanceof(URLSearchParams)
|
|
406
|
+
zod.z.instanceof(URLSearchParams),
|
|
407
|
+
zod.z.record(zod.z.string(), zod.z.unknown())
|
|
407
408
|
]).optional().describe(
|
|
408
|
-
"Request body \u2014 JSON strings are auto-detected and Content-Type is set accordingly"
|
|
409
|
+
"Request body \u2014 plain objects and JSON strings are auto-detected and Content-Type is set accordingly"
|
|
409
410
|
),
|
|
410
411
|
authenticationId: AuthenticationIdPropertySchema.optional(),
|
|
411
412
|
callbackUrl: zod.z.string().optional().describe("URL to send async response to (makes request async)"),
|
|
@@ -430,6 +431,13 @@ function coerceToNumericId(fieldName, value) {
|
|
|
430
431
|
return numericValue;
|
|
431
432
|
}
|
|
432
433
|
|
|
434
|
+
// src/utils/type-guard-utils.ts
|
|
435
|
+
function isPlainObject(value) {
|
|
436
|
+
if (typeof value !== "object" || value === null) return false;
|
|
437
|
+
const proto = Object.getPrototypeOf(value);
|
|
438
|
+
return proto === Object.prototype || proto === null;
|
|
439
|
+
}
|
|
440
|
+
|
|
433
441
|
// src/plugins/fetch/index.ts
|
|
434
442
|
function transformUrlToRelayPath(url) {
|
|
435
443
|
const targetUrl = new URL(url);
|
|
@@ -446,6 +454,27 @@ function normalizeHeaders(optionsHeaders) {
|
|
|
446
454
|
}
|
|
447
455
|
return headers;
|
|
448
456
|
}
|
|
457
|
+
function inferContentType(body) {
|
|
458
|
+
if (body instanceof URLSearchParams) {
|
|
459
|
+
return "application/x-www-form-urlencoded; charset=utf-8";
|
|
460
|
+
}
|
|
461
|
+
if (typeof body === "string") {
|
|
462
|
+
const trimmed = body.trimStart();
|
|
463
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
464
|
+
return void 0;
|
|
465
|
+
}
|
|
466
|
+
try {
|
|
467
|
+
JSON.parse(trimmed);
|
|
468
|
+
return "application/json; charset=utf-8";
|
|
469
|
+
} catch {
|
|
470
|
+
return void 0;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (isPlainObject(body) || Array.isArray(body)) {
|
|
474
|
+
return "application/json; charset=utf-8";
|
|
475
|
+
}
|
|
476
|
+
return void 0;
|
|
477
|
+
}
|
|
449
478
|
var fetchPlugin = ({ context }) => {
|
|
450
479
|
return {
|
|
451
480
|
fetch: async function fetch2(url, init) {
|
|
@@ -468,10 +497,11 @@ var fetchPlugin = ({ context }) => {
|
|
|
468
497
|
(k) => k.toLowerCase() === "content-type"
|
|
469
498
|
);
|
|
470
499
|
if (fetchInit.body && !hasContentType) {
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
500
|
+
const inferred = inferContentType(
|
|
501
|
+
fetchInit.body
|
|
502
|
+
);
|
|
503
|
+
if (inferred) {
|
|
504
|
+
headers["Content-Type"] = inferred;
|
|
475
505
|
}
|
|
476
506
|
}
|
|
477
507
|
if (authenticationId) {
|
|
@@ -4188,7 +4218,31 @@ var ZapierApiClient = class {
|
|
|
4188
4218
|
constructor(options) {
|
|
4189
4219
|
this.options = options;
|
|
4190
4220
|
this.fetch = async (path, init) => {
|
|
4191
|
-
|
|
4221
|
+
if (!path.startsWith("/")) {
|
|
4222
|
+
throw new ZapierValidationError(
|
|
4223
|
+
`fetch expects a path starting with '/', got: ${path}`
|
|
4224
|
+
);
|
|
4225
|
+
}
|
|
4226
|
+
if (init?.body && (isPlainObject(init.body) || Array.isArray(init.body))) {
|
|
4227
|
+
init.body = JSON.stringify(init.body);
|
|
4228
|
+
}
|
|
4229
|
+
const { url, pathConfig: pathConfig2 } = this.buildUrl(path, init?.searchParams);
|
|
4230
|
+
const builtHeaders = await this.buildHeaders(
|
|
4231
|
+
init,
|
|
4232
|
+
pathConfig2
|
|
4233
|
+
);
|
|
4234
|
+
const inputHeaders = new Headers(init?.headers ?? {});
|
|
4235
|
+
const mergedHeaders = new Headers();
|
|
4236
|
+
builtHeaders.forEach((value, key) => {
|
|
4237
|
+
mergedHeaders.set(key, value);
|
|
4238
|
+
});
|
|
4239
|
+
inputHeaders.forEach((value, key) => {
|
|
4240
|
+
mergedHeaders.set(key, value);
|
|
4241
|
+
});
|
|
4242
|
+
return await this.options.fetch(url, {
|
|
4243
|
+
...init,
|
|
4244
|
+
headers: mergedHeaders
|
|
4245
|
+
});
|
|
4192
4246
|
};
|
|
4193
4247
|
this.get = async (path, options = {}) => {
|
|
4194
4248
|
return this.fetchJson("GET", path, void 0, options);
|
|
@@ -4204,7 +4258,7 @@ var ZapierApiClient = class {
|
|
|
4204
4258
|
};
|
|
4205
4259
|
this.poll = async (path, options = {}) => {
|
|
4206
4260
|
return pollUntilComplete({
|
|
4207
|
-
fetchPoll: () => this.
|
|
4261
|
+
fetchPoll: () => this.fetch(path, {
|
|
4208
4262
|
method: "GET",
|
|
4209
4263
|
searchParams: options.searchParams,
|
|
4210
4264
|
authRequired: options.authRequired
|
|
@@ -4440,7 +4494,7 @@ var ZapierApiClient = class {
|
|
|
4440
4494
|
headers["Content-Type"] = "application/json";
|
|
4441
4495
|
}
|
|
4442
4496
|
const wasMissingAuthToken = options.authRequired && await this.getAuthToken({ requiredScopes: options.requiredScopes }) == null;
|
|
4443
|
-
const response = await this.
|
|
4497
|
+
const response = await this.fetch(path, {
|
|
4444
4498
|
...options,
|
|
4445
4499
|
method,
|
|
4446
4500
|
body: data != null ? JSON.stringify(data) : void 0,
|
|
@@ -4462,34 +4516,6 @@ var ZapierApiClient = class {
|
|
|
4462
4516
|
}
|
|
4463
4517
|
return result;
|
|
4464
4518
|
}
|
|
4465
|
-
// Plain fetch method for API paths (must start with /)
|
|
4466
|
-
async plainFetch(path, fetchOptions) {
|
|
4467
|
-
if (!path.startsWith("/")) {
|
|
4468
|
-
throw new ZapierValidationError(
|
|
4469
|
-
`plainFetch expects a path starting with '/', got: ${path}`
|
|
4470
|
-
);
|
|
4471
|
-
}
|
|
4472
|
-
if (fetchOptions?.body && typeof fetchOptions.body === "object") {
|
|
4473
|
-
fetchOptions.body = JSON.stringify(fetchOptions.body);
|
|
4474
|
-
}
|
|
4475
|
-
const { url, pathConfig: pathConfig2 } = this.buildUrl(path, fetchOptions?.searchParams);
|
|
4476
|
-
const builtHeaders = await this.buildHeaders(
|
|
4477
|
-
fetchOptions,
|
|
4478
|
-
pathConfig2
|
|
4479
|
-
);
|
|
4480
|
-
const inputHeaders = new Headers(fetchOptions?.headers ?? {});
|
|
4481
|
-
const mergedHeaders = new Headers();
|
|
4482
|
-
builtHeaders.forEach((value, key) => {
|
|
4483
|
-
mergedHeaders.set(key, value);
|
|
4484
|
-
});
|
|
4485
|
-
inputHeaders.forEach((value, key) => {
|
|
4486
|
-
mergedHeaders.set(key, value);
|
|
4487
|
-
});
|
|
4488
|
-
return await this.options.fetch(url, {
|
|
4489
|
-
...fetchOptions,
|
|
4490
|
-
headers: mergedHeaders
|
|
4491
|
-
});
|
|
4492
|
-
}
|
|
4493
4519
|
};
|
|
4494
4520
|
var createZapierApi = (options) => {
|
|
4495
4521
|
const { debug = false, fetch: originalFetch = globalThis.fetch } = options;
|
|
@@ -5129,7 +5155,7 @@ function getCpuTime() {
|
|
|
5129
5155
|
|
|
5130
5156
|
// package.json
|
|
5131
5157
|
var package_default = {
|
|
5132
|
-
version: "0.25.
|
|
5158
|
+
version: "0.25.2"};
|
|
5133
5159
|
|
|
5134
5160
|
// src/plugins/eventEmission/builders.ts
|
|
5135
5161
|
function createBaseEvent(context = {}) {
|
package/dist/index.mjs
CHANGED
|
@@ -381,9 +381,10 @@ var FetchInitSchema = z.object({
|
|
|
381
381
|
body: z.union([
|
|
382
382
|
z.string(),
|
|
383
383
|
z.instanceof(FormData),
|
|
384
|
-
z.instanceof(URLSearchParams)
|
|
384
|
+
z.instanceof(URLSearchParams),
|
|
385
|
+
z.record(z.string(), z.unknown())
|
|
385
386
|
]).optional().describe(
|
|
386
|
-
"Request body \u2014 JSON strings are auto-detected and Content-Type is set accordingly"
|
|
387
|
+
"Request body \u2014 plain objects and JSON strings are auto-detected and Content-Type is set accordingly"
|
|
387
388
|
),
|
|
388
389
|
authenticationId: AuthenticationIdPropertySchema.optional(),
|
|
389
390
|
callbackUrl: z.string().optional().describe("URL to send async response to (makes request async)"),
|
|
@@ -408,6 +409,13 @@ function coerceToNumericId(fieldName, value) {
|
|
|
408
409
|
return numericValue;
|
|
409
410
|
}
|
|
410
411
|
|
|
412
|
+
// src/utils/type-guard-utils.ts
|
|
413
|
+
function isPlainObject(value) {
|
|
414
|
+
if (typeof value !== "object" || value === null) return false;
|
|
415
|
+
const proto = Object.getPrototypeOf(value);
|
|
416
|
+
return proto === Object.prototype || proto === null;
|
|
417
|
+
}
|
|
418
|
+
|
|
411
419
|
// src/plugins/fetch/index.ts
|
|
412
420
|
function transformUrlToRelayPath(url) {
|
|
413
421
|
const targetUrl = new URL(url);
|
|
@@ -424,6 +432,27 @@ function normalizeHeaders(optionsHeaders) {
|
|
|
424
432
|
}
|
|
425
433
|
return headers;
|
|
426
434
|
}
|
|
435
|
+
function inferContentType(body) {
|
|
436
|
+
if (body instanceof URLSearchParams) {
|
|
437
|
+
return "application/x-www-form-urlencoded; charset=utf-8";
|
|
438
|
+
}
|
|
439
|
+
if (typeof body === "string") {
|
|
440
|
+
const trimmed = body.trimStart();
|
|
441
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
442
|
+
return void 0;
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
JSON.parse(trimmed);
|
|
446
|
+
return "application/json; charset=utf-8";
|
|
447
|
+
} catch {
|
|
448
|
+
return void 0;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (isPlainObject(body) || Array.isArray(body)) {
|
|
452
|
+
return "application/json; charset=utf-8";
|
|
453
|
+
}
|
|
454
|
+
return void 0;
|
|
455
|
+
}
|
|
427
456
|
var fetchPlugin = ({ context }) => {
|
|
428
457
|
return {
|
|
429
458
|
fetch: async function fetch2(url, init) {
|
|
@@ -446,10 +475,11 @@ var fetchPlugin = ({ context }) => {
|
|
|
446
475
|
(k) => k.toLowerCase() === "content-type"
|
|
447
476
|
);
|
|
448
477
|
if (fetchInit.body && !hasContentType) {
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
478
|
+
const inferred = inferContentType(
|
|
479
|
+
fetchInit.body
|
|
480
|
+
);
|
|
481
|
+
if (inferred) {
|
|
482
|
+
headers["Content-Type"] = inferred;
|
|
453
483
|
}
|
|
454
484
|
}
|
|
455
485
|
if (authenticationId) {
|
|
@@ -4166,7 +4196,31 @@ var ZapierApiClient = class {
|
|
|
4166
4196
|
constructor(options) {
|
|
4167
4197
|
this.options = options;
|
|
4168
4198
|
this.fetch = async (path, init) => {
|
|
4169
|
-
|
|
4199
|
+
if (!path.startsWith("/")) {
|
|
4200
|
+
throw new ZapierValidationError(
|
|
4201
|
+
`fetch expects a path starting with '/', got: ${path}`
|
|
4202
|
+
);
|
|
4203
|
+
}
|
|
4204
|
+
if (init?.body && (isPlainObject(init.body) || Array.isArray(init.body))) {
|
|
4205
|
+
init.body = JSON.stringify(init.body);
|
|
4206
|
+
}
|
|
4207
|
+
const { url, pathConfig: pathConfig2 } = this.buildUrl(path, init?.searchParams);
|
|
4208
|
+
const builtHeaders = await this.buildHeaders(
|
|
4209
|
+
init,
|
|
4210
|
+
pathConfig2
|
|
4211
|
+
);
|
|
4212
|
+
const inputHeaders = new Headers(init?.headers ?? {});
|
|
4213
|
+
const mergedHeaders = new Headers();
|
|
4214
|
+
builtHeaders.forEach((value, key) => {
|
|
4215
|
+
mergedHeaders.set(key, value);
|
|
4216
|
+
});
|
|
4217
|
+
inputHeaders.forEach((value, key) => {
|
|
4218
|
+
mergedHeaders.set(key, value);
|
|
4219
|
+
});
|
|
4220
|
+
return await this.options.fetch(url, {
|
|
4221
|
+
...init,
|
|
4222
|
+
headers: mergedHeaders
|
|
4223
|
+
});
|
|
4170
4224
|
};
|
|
4171
4225
|
this.get = async (path, options = {}) => {
|
|
4172
4226
|
return this.fetchJson("GET", path, void 0, options);
|
|
@@ -4182,7 +4236,7 @@ var ZapierApiClient = class {
|
|
|
4182
4236
|
};
|
|
4183
4237
|
this.poll = async (path, options = {}) => {
|
|
4184
4238
|
return pollUntilComplete({
|
|
4185
|
-
fetchPoll: () => this.
|
|
4239
|
+
fetchPoll: () => this.fetch(path, {
|
|
4186
4240
|
method: "GET",
|
|
4187
4241
|
searchParams: options.searchParams,
|
|
4188
4242
|
authRequired: options.authRequired
|
|
@@ -4418,7 +4472,7 @@ var ZapierApiClient = class {
|
|
|
4418
4472
|
headers["Content-Type"] = "application/json";
|
|
4419
4473
|
}
|
|
4420
4474
|
const wasMissingAuthToken = options.authRequired && await this.getAuthToken({ requiredScopes: options.requiredScopes }) == null;
|
|
4421
|
-
const response = await this.
|
|
4475
|
+
const response = await this.fetch(path, {
|
|
4422
4476
|
...options,
|
|
4423
4477
|
method,
|
|
4424
4478
|
body: data != null ? JSON.stringify(data) : void 0,
|
|
@@ -4440,34 +4494,6 @@ var ZapierApiClient = class {
|
|
|
4440
4494
|
}
|
|
4441
4495
|
return result;
|
|
4442
4496
|
}
|
|
4443
|
-
// Plain fetch method for API paths (must start with /)
|
|
4444
|
-
async plainFetch(path, fetchOptions) {
|
|
4445
|
-
if (!path.startsWith("/")) {
|
|
4446
|
-
throw new ZapierValidationError(
|
|
4447
|
-
`plainFetch expects a path starting with '/', got: ${path}`
|
|
4448
|
-
);
|
|
4449
|
-
}
|
|
4450
|
-
if (fetchOptions?.body && typeof fetchOptions.body === "object") {
|
|
4451
|
-
fetchOptions.body = JSON.stringify(fetchOptions.body);
|
|
4452
|
-
}
|
|
4453
|
-
const { url, pathConfig: pathConfig2 } = this.buildUrl(path, fetchOptions?.searchParams);
|
|
4454
|
-
const builtHeaders = await this.buildHeaders(
|
|
4455
|
-
fetchOptions,
|
|
4456
|
-
pathConfig2
|
|
4457
|
-
);
|
|
4458
|
-
const inputHeaders = new Headers(fetchOptions?.headers ?? {});
|
|
4459
|
-
const mergedHeaders = new Headers();
|
|
4460
|
-
builtHeaders.forEach((value, key) => {
|
|
4461
|
-
mergedHeaders.set(key, value);
|
|
4462
|
-
});
|
|
4463
|
-
inputHeaders.forEach((value, key) => {
|
|
4464
|
-
mergedHeaders.set(key, value);
|
|
4465
|
-
});
|
|
4466
|
-
return await this.options.fetch(url, {
|
|
4467
|
-
...fetchOptions,
|
|
4468
|
-
headers: mergedHeaders
|
|
4469
|
-
});
|
|
4470
|
-
}
|
|
4471
4497
|
};
|
|
4472
4498
|
var createZapierApi = (options) => {
|
|
4473
4499
|
const { debug = false, fetch: originalFetch = globalThis.fetch } = options;
|
|
@@ -5107,7 +5133,7 @@ function getCpuTime() {
|
|
|
5107
5133
|
|
|
5108
5134
|
// package.json
|
|
5109
5135
|
var package_default = {
|
|
5110
|
-
version: "0.25.
|
|
5136
|
+
version: "0.25.2"};
|
|
5111
5137
|
|
|
5112
5138
|
// src/plugins/eventEmission/builders.ts
|
|
5113
5139
|
function createBaseEvent(context = {}) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AA6D7D,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,CACL,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,IAAI,CAAC,EAAE,WAAW,GAAG;QACnB,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QACnC,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,sBAAsB,CAAC,EAAE,MAAM,CAAC;QAChC,UAAU,CAAC,EAAE;YAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC;KACrC,KACE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvB,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,KAAK,EAAE;gBACL,WAAW,EAAE,MAAM,CAAC;gBACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;gBACnB,UAAU,EAAE,MAAM,EAAE,CAAC;gBACrB,UAAU,EAAE,MAAM,CAAC;gBACnB,eAAe,EAAE,KAAK,CAAC;oBAAE,IAAI,EAAE,MAAM,CAAC;oBAAC,MAAM,EAAE,CAAC,CAAC,SAAS,CAAA;iBAAE,CAAC,CAAC;aAC/D,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAED;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAC9B,EAAE,EAAE,sBAAsB;AAC1B,AADI,sBAAsB;AAC1B;IAAE,GAAG,EAAE,SAAS,CAAA;CAAE,GAAG,oBAAoB,EAAE,0CAA0C;AACrF,mBAAmB,CAmHpB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,WAAW,GAAG;IACjD,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FetchUrlSchema, FetchInitSchema } from "./schemas";
|
|
2
2
|
import { coerceToNumericId } from "../../utils/id-utils";
|
|
3
|
+
import { isPlainObject } from "../../utils/type-guard-utils";
|
|
3
4
|
/**
|
|
4
5
|
* Transforms full URLs into Relay format: /relay/{domain}/{path}
|
|
5
6
|
*/
|
|
@@ -25,6 +26,28 @@ function normalizeHeaders(optionsHeaders) {
|
|
|
25
26
|
}
|
|
26
27
|
return headers;
|
|
27
28
|
}
|
|
29
|
+
function inferContentType(body) {
|
|
30
|
+
if (body instanceof URLSearchParams) {
|
|
31
|
+
return "application/x-www-form-urlencoded; charset=utf-8";
|
|
32
|
+
}
|
|
33
|
+
if (typeof body === "string") {
|
|
34
|
+
const trimmed = body.trimStart();
|
|
35
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
JSON.parse(trimmed);
|
|
40
|
+
return "application/json; charset=utf-8";
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (isPlainObject(body) || Array.isArray(body)) {
|
|
47
|
+
return "application/json; charset=utf-8";
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
28
51
|
/**
|
|
29
52
|
* Fetch plugin — the primary way to make authenticated HTTP requests through Zapier's Relay service.
|
|
30
53
|
* Mirrors the native fetch(url, init?) signature with additional Zapier-specific options.
|
|
@@ -39,15 +62,11 @@ export const fetchPlugin = ({ context }) => {
|
|
|
39
62
|
const { authenticationId, callbackUrl, authenticationTemplate, _telemetry, ...fetchInit } = init || {};
|
|
40
63
|
const relayPath = transformUrlToRelayPath(url);
|
|
41
64
|
const headers = normalizeHeaders(fetchInit.headers);
|
|
42
|
-
// Auto-set Content-Type for JSON bodies if not already specified
|
|
43
65
|
const hasContentType = Object.keys(headers).some((k) => k.toLowerCase() === "content-type");
|
|
44
66
|
if (fetchInit.body && !hasContentType) {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const trimmed = bodyStr.trimStart();
|
|
49
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
50
|
-
headers["Content-Type"] = "application/json; charset=utf-8";
|
|
67
|
+
const inferred = inferContentType(fetchInit.body);
|
|
68
|
+
if (inferred) {
|
|
69
|
+
headers["Content-Type"] = inferred;
|
|
51
70
|
}
|
|
52
71
|
}
|
|
53
72
|
if (authenticationId) {
|
|
@@ -242,6 +242,99 @@ describe("fetch plugin", () => {
|
|
|
242
242
|
authRequired: true,
|
|
243
243
|
});
|
|
244
244
|
});
|
|
245
|
+
it("should not set Content-Type for FormData bodies", async () => {
|
|
246
|
+
const sdk = createTestSdk();
|
|
247
|
+
const body = new FormData();
|
|
248
|
+
body.append("file", "contents");
|
|
249
|
+
await sdk.fetch("https://api.example.com/upload", {
|
|
250
|
+
method: "POST",
|
|
251
|
+
body,
|
|
252
|
+
});
|
|
253
|
+
expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/upload", {
|
|
254
|
+
method: "POST",
|
|
255
|
+
body,
|
|
256
|
+
headers: {},
|
|
257
|
+
authRequired: true,
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
it("should set Content-Type for URLSearchParams bodies", async () => {
|
|
261
|
+
const sdk = createTestSdk();
|
|
262
|
+
const body = new URLSearchParams({ key: "value", foo: "bar" });
|
|
263
|
+
await sdk.fetch("https://api.example.com/form", {
|
|
264
|
+
method: "POST",
|
|
265
|
+
body,
|
|
266
|
+
});
|
|
267
|
+
expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/form", {
|
|
268
|
+
method: "POST",
|
|
269
|
+
body,
|
|
270
|
+
headers: {
|
|
271
|
+
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
|
272
|
+
},
|
|
273
|
+
authRequired: true,
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
it("should not set Content-Type for invalid JSON-like strings", async () => {
|
|
277
|
+
const sdk = createTestSdk();
|
|
278
|
+
const body = "{ this is not json";
|
|
279
|
+
await sdk.fetch("https://api.example.com/data", {
|
|
280
|
+
method: "POST",
|
|
281
|
+
body,
|
|
282
|
+
});
|
|
283
|
+
expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
|
|
284
|
+
method: "POST",
|
|
285
|
+
body,
|
|
286
|
+
headers: {},
|
|
287
|
+
authRequired: true,
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
it("should auto-set Content-Type for plain object bodies", async () => {
|
|
291
|
+
const sdk = createTestSdk();
|
|
292
|
+
const body = { channel: "C0123456789", text: "Hello, world!" };
|
|
293
|
+
await sdk.fetch("https://api.example.com/data", {
|
|
294
|
+
method: "POST",
|
|
295
|
+
body: body,
|
|
296
|
+
});
|
|
297
|
+
expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
|
|
298
|
+
method: "POST",
|
|
299
|
+
body,
|
|
300
|
+
headers: {
|
|
301
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
302
|
+
},
|
|
303
|
+
authRequired: true,
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
it("should auto-set Content-Type for plain array bodies", async () => {
|
|
307
|
+
const sdk = createTestSdk();
|
|
308
|
+
const body = [{ id: 1 }, { id: 2 }];
|
|
309
|
+
await sdk.fetch("https://api.example.com/data", {
|
|
310
|
+
method: "POST",
|
|
311
|
+
body: body,
|
|
312
|
+
});
|
|
313
|
+
expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/data", {
|
|
314
|
+
method: "POST",
|
|
315
|
+
body,
|
|
316
|
+
headers: {
|
|
317
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
318
|
+
},
|
|
319
|
+
authRequired: true,
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
it("should not set Content-Type for Blob bodies", async () => {
|
|
323
|
+
const sdk = createTestSdk();
|
|
324
|
+
const body = new Blob(["binary data"], {
|
|
325
|
+
type: "application/octet-stream",
|
|
326
|
+
});
|
|
327
|
+
await sdk.fetch("https://api.example.com/upload", {
|
|
328
|
+
method: "POST",
|
|
329
|
+
body,
|
|
330
|
+
});
|
|
331
|
+
expect(mockFetch).toHaveBeenCalledWith("/relay/api.example.com/upload", {
|
|
332
|
+
method: "POST",
|
|
333
|
+
body,
|
|
334
|
+
headers: {},
|
|
335
|
+
authRequired: true,
|
|
336
|
+
});
|
|
337
|
+
});
|
|
245
338
|
});
|
|
246
339
|
describe("authentication", () => {
|
|
247
340
|
it("should set authRequired to true in API call options", async () => {
|
|
@@ -11,7 +11,7 @@ export declare const FetchInitSchema: z.ZodOptional<z.ZodObject<{
|
|
|
11
11
|
OPTIONS: "OPTIONS";
|
|
12
12
|
}>>;
|
|
13
13
|
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
14
|
-
body: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodCustom<FormData, FormData>, z.ZodCustom<URLSearchParams, URLSearchParams>]>>;
|
|
14
|
+
body: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodCustom<FormData, FormData>, z.ZodCustom<URLSearchParams, URLSearchParams>, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>;
|
|
15
15
|
authenticationId: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
16
16
|
callbackUrl: z.ZodOptional<z.ZodString>;
|
|
17
17
|
authenticationTemplate: z.ZodOptional<z.ZodString>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,cAAc,2DAIxB,CAAC;AAEJ,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,cAAc,2DAIxB,CAAC;AAEJ,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;kBAoCzB,CAAC"}
|
|
@@ -19,9 +19,10 @@ export const FetchInitSchema = z
|
|
|
19
19
|
z.string(),
|
|
20
20
|
z.instanceof(FormData),
|
|
21
21
|
z.instanceof(URLSearchParams),
|
|
22
|
+
z.record(z.string(), z.unknown()),
|
|
22
23
|
])
|
|
23
24
|
.optional()
|
|
24
|
-
.describe("Request body — JSON strings are auto-detected and Content-Type is set accordingly"),
|
|
25
|
+
.describe("Request body — plain objects and JSON strings are auto-detected and Content-Type is set accordingly"),
|
|
25
26
|
authenticationId: AuthenticationIdPropertySchema.optional(),
|
|
26
27
|
callbackUrl: z
|
|
27
28
|
.string()
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true only for plain objects created via `{}`, `new Object()`, or
|
|
3
|
+
* `Object.create(null)`. Rejects all class instances (FormData, Blob, etc.)
|
|
4
|
+
* without needing to enumerate them.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
7
|
+
//# sourceMappingURL=type-guard-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-guard-utils.d.ts","sourceRoot":"","sources":["../../src/utils/type-guard-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIlC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true only for plain objects created via `{}`, `new Object()`, or
|
|
3
|
+
* `Object.create(null)`. Rejects all class instances (FormData, Blob, etc.)
|
|
4
|
+
* without needing to enumerate them.
|
|
5
|
+
*/
|
|
6
|
+
export function isPlainObject(value) {
|
|
7
|
+
if (typeof value !== "object" || value === null)
|
|
8
|
+
return false;
|
|
9
|
+
const proto = Object.getPrototypeOf(value);
|
|
10
|
+
return proto === Object.prototype || proto === null;
|
|
11
|
+
}
|