@zapier/zapier-sdk 0.15.4 → 0.15.9
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 +30 -0
- package/dist/api/auth.d.ts.map +1 -1
- package/dist/api/auth.js +14 -4
- package/dist/api/auth.test.d.ts +2 -0
- package/dist/api/auth.test.d.ts.map +1 -0
- package/dist/api/auth.test.js +220 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +18 -32
- package/dist/api/client.methods.test.d.ts +2 -0
- package/dist/api/client.methods.test.d.ts.map +1 -0
- package/dist/api/client.methods.test.js +158 -0
- package/dist/api/client.test.js +27 -11
- package/dist/api/router.d.ts +16 -0
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/router.js +37 -0
- package/dist/api/router.test.d.ts +2 -0
- package/dist/api/router.test.d.ts.map +1 -0
- package/dist/api/router.test.js +109 -0
- package/dist/auth.d.ts +15 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +25 -0
- package/dist/index.cjs +247 -74
- package/dist/index.d.mts +402 -241
- package/dist/index.mjs +247 -75
- package/dist/plugins/eventEmission/index.d.ts.map +1 -1
- package/dist/plugins/eventEmission/index.js +9 -5
- package/dist/plugins/eventEmission/index.test.js +161 -0
- package/dist/plugins/getAction/index.d.ts.map +1 -1
- package/dist/plugins/getAction/index.js +2 -4
- package/dist/plugins/getAction/index.test.js +26 -3
- package/dist/plugins/getAuthentication/index.d.ts +2 -5
- package/dist/plugins/getAuthentication/index.d.ts.map +1 -1
- package/dist/plugins/getAuthentication/index.js +3 -24
- package/dist/plugins/getAuthentication/index.test.js +32 -144
- package/dist/plugins/getAuthentication/schemas.d.ts +4 -13
- package/dist/plugins/getAuthentication/schemas.d.ts.map +1 -1
- package/dist/plugins/getAuthentication/schemas.js +1 -11
- package/dist/sdk.d.ts +1 -1
- package/dist/temporary-internal-core/handlers/getAuthentication.d.ts +94 -0
- package/dist/temporary-internal-core/handlers/getAuthentication.d.ts.map +1 -0
- package/dist/temporary-internal-core/handlers/getAuthentication.js +68 -0
- package/dist/temporary-internal-core/handlers/getAuthentication.test.d.ts +2 -0
- package/dist/temporary-internal-core/handlers/getAuthentication.test.d.ts.map +1 -0
- package/dist/temporary-internal-core/handlers/getAuthentication.test.js +248 -0
- package/dist/temporary-internal-core/handlers/listApps.js +1 -1
- package/dist/temporary-internal-core/index.d.ts +2 -0
- package/dist/temporary-internal-core/index.d.ts.map +1 -1
- package/dist/temporary-internal-core/index.js +2 -0
- package/dist/temporary-internal-core/schemas/authentications/index.d.ts +454 -0
- package/dist/temporary-internal-core/schemas/authentications/index.d.ts.map +1 -0
- package/dist/temporary-internal-core/schemas/authentications/index.js +96 -0
- package/dist/temporary-internal-core/schemas/errors/index.d.ts +139 -0
- package/dist/temporary-internal-core/schemas/errors/index.d.ts.map +1 -0
- package/dist/temporary-internal-core/schemas/errors/index.js +129 -0
- package/dist/temporary-internal-core/utils/app-locators.d.ts +0 -20
- package/dist/temporary-internal-core/utils/app-locators.d.ts.map +1 -1
- package/dist/temporary-internal-core/utils/app-locators.js +1 -45
- package/dist/temporary-internal-core/utils/string-utils.d.ts +28 -0
- package/dist/temporary-internal-core/utils/string-utils.d.ts.map +1 -0
- package/dist/temporary-internal-core/utils/string-utils.js +52 -0
- package/dist/temporary-internal-core/utils/transformations.d.ts +14 -0
- package/dist/temporary-internal-core/utils/transformations.d.ts.map +1 -1
- package/dist/temporary-internal-core/utils/transformations.js +37 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @zapier/zapier-sdk
|
|
2
2
|
|
|
3
|
+
## 0.15.9
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 450b009: Fix getAction to search across all paginated results
|
|
8
|
+
|
|
9
|
+
## 0.15.8
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 371315e: Extract main logic/transformations of getAuthentication into temporary-internal-core to prepare for full migration
|
|
14
|
+
|
|
15
|
+
## 0.15.7
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 9f3695b: Make sure path of base URL is used when overriding base URL.
|
|
20
|
+
|
|
21
|
+
## 0.15.6
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- 6a0e0db: Fix telemetry user context to use token from SDK options and Deduplicate JWT validation logic
|
|
26
|
+
|
|
27
|
+
## 0.15.5
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- 712c02b: Refactored temporary route override pattern
|
|
32
|
+
|
|
3
33
|
## 0.15.4
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
package/dist/api/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAW5C;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAW5C;AAaD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO5D;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG;IACpD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAwCA"}
|
package/dist/api/auth.js
CHANGED
|
@@ -15,6 +15,16 @@ export function isJwt(token) {
|
|
|
15
15
|
const base64UrlPattern = /^[A-Za-z0-9_-]+$/;
|
|
16
16
|
return parts.every((part) => part.length > 0 && base64UrlPattern.test(part));
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Parse a JWT token into its three parts
|
|
20
|
+
* Returns the parts array if valid, null otherwise
|
|
21
|
+
*/
|
|
22
|
+
function parseJwt(token) {
|
|
23
|
+
if (!isJwt(token)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return token.split(".");
|
|
27
|
+
}
|
|
18
28
|
export function getAuthorizationHeader(token) {
|
|
19
29
|
// Check if token is a JWT (has 3 parts separated by dots)
|
|
20
30
|
if (isJwt(token)) {
|
|
@@ -30,11 +40,11 @@ export function getAuthorizationHeader(token) {
|
|
|
30
40
|
* Returns null values on any failure (silent-by-design)
|
|
31
41
|
*/
|
|
32
42
|
export function extractUserIdsFromJwt(token) {
|
|
43
|
+
const parts = parseJwt(token);
|
|
44
|
+
if (!parts) {
|
|
45
|
+
return { customuser_id: null, account_id: null };
|
|
46
|
+
}
|
|
33
47
|
try {
|
|
34
|
-
const parts = token.split(".");
|
|
35
|
-
if (parts.length !== 3) {
|
|
36
|
-
return { customuser_id: null, account_id: null };
|
|
37
|
-
}
|
|
38
48
|
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
|
|
39
49
|
let actualPayload = payload;
|
|
40
50
|
if (payload.sub_type === "service" && payload.njwt) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.test.d.ts","sourceRoot":"","sources":["../../src/api/auth.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { isJwt, getAuthorizationHeader, extractUserIdsFromJwt } from "./auth";
|
|
3
|
+
describe("api/auth", () => {
|
|
4
|
+
describe("isJwt", () => {
|
|
5
|
+
it("should return true for valid JWT tokens", () => {
|
|
6
|
+
const validJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
|
|
7
|
+
expect(isJwt(validJwt)).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
it("should return true for JWT with minimal valid parts", () => {
|
|
10
|
+
const minimalJwt = "a.b.c";
|
|
11
|
+
expect(isJwt(minimalJwt)).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
it("should return false for tokens with fewer than 3 parts", () => {
|
|
14
|
+
expect(isJwt("header.payload")).toBe(false);
|
|
15
|
+
expect(isJwt("onlyonepart")).toBe(false);
|
|
16
|
+
expect(isJwt("")).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
it("should return false for tokens with more than 3 parts", () => {
|
|
19
|
+
expect(isJwt("part1.part2.part3.part4")).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
it("should return false for tokens with empty parts", () => {
|
|
22
|
+
expect(isJwt("..")).toBe(false);
|
|
23
|
+
expect(isJwt(".payload.signature")).toBe(false);
|
|
24
|
+
expect(isJwt("header..signature")).toBe(false);
|
|
25
|
+
expect(isJwt("header.payload.")).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
it("should return false for tokens with invalid base64url characters", () => {
|
|
28
|
+
expect(isJwt("header+plus.payload.signature")).toBe(false);
|
|
29
|
+
expect(isJwt("header.payload/slash.signature")).toBe(false);
|
|
30
|
+
expect(isJwt("header.payload=padding.signature")).toBe(false);
|
|
31
|
+
expect(isJwt("header.payload with space.signature")).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
it("should accept valid base64url characters including dashes and underscores", () => {
|
|
34
|
+
const validBase64Url = "abc-123_XYZ.def-456_UVW.ghi-789_RST";
|
|
35
|
+
expect(isJwt(validBase64Url)).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe("getAuthorizationHeader", () => {
|
|
39
|
+
it("should return JWT prefix for valid JWT tokens", () => {
|
|
40
|
+
const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U";
|
|
41
|
+
expect(getAuthorizationHeader(jwt)).toBe(`JWT ${jwt}`);
|
|
42
|
+
});
|
|
43
|
+
it("should return Bearer prefix for non-JWT tokens", () => {
|
|
44
|
+
const apiKey = "sk_test_1234567890abcdef";
|
|
45
|
+
expect(getAuthorizationHeader(apiKey)).toBe(`Bearer ${apiKey}`);
|
|
46
|
+
});
|
|
47
|
+
it("should return Bearer prefix for tokens with wrong number of parts", () => {
|
|
48
|
+
expect(getAuthorizationHeader("header.payload")).toBe("Bearer header.payload");
|
|
49
|
+
});
|
|
50
|
+
it("should return Bearer prefix for empty string", () => {
|
|
51
|
+
expect(getAuthorizationHeader("")).toBe("Bearer ");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe("extractUserIdsFromJwt", () => {
|
|
55
|
+
function createJwt(payload) {
|
|
56
|
+
const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url");
|
|
57
|
+
const payloadEncoded = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
58
|
+
const signature = "fake_signature_for_testing";
|
|
59
|
+
return `${header}.${payloadEncoded}.${signature}`;
|
|
60
|
+
}
|
|
61
|
+
it("should return null values for invalid JWT tokens", () => {
|
|
62
|
+
expect(extractUserIdsFromJwt("not-a-jwt")).toEqual({
|
|
63
|
+
customuser_id: null,
|
|
64
|
+
account_id: null,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
it("should return null values for JWT with invalid payload", () => {
|
|
68
|
+
const invalidJwt = "header.not-valid-base64!.signature";
|
|
69
|
+
expect(extractUserIdsFromJwt(invalidJwt)).toEqual({
|
|
70
|
+
customuser_id: null,
|
|
71
|
+
account_id: null,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
it("should extract customuser_id and account_id from regular JWT", () => {
|
|
75
|
+
const payload = {
|
|
76
|
+
sub: "12345",
|
|
77
|
+
sub_type: "customuser",
|
|
78
|
+
"zap:acc": "67890",
|
|
79
|
+
};
|
|
80
|
+
const jwt = createJwt(payload);
|
|
81
|
+
expect(extractUserIdsFromJwt(jwt)).toEqual({
|
|
82
|
+
customuser_id: 12345,
|
|
83
|
+
account_id: 67890,
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
it("should handle numeric sub and zap:acc values", () => {
|
|
87
|
+
const payload = {
|
|
88
|
+
sub: 99999,
|
|
89
|
+
sub_type: "customuser",
|
|
90
|
+
"zap:acc": 11111,
|
|
91
|
+
};
|
|
92
|
+
const jwt = createJwt(payload);
|
|
93
|
+
expect(extractUserIdsFromJwt(jwt)).toEqual({
|
|
94
|
+
customuser_id: 99999,
|
|
95
|
+
account_id: 11111,
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
it("should return null customuser_id when sub_type is not customuser", () => {
|
|
99
|
+
const payload = {
|
|
100
|
+
sub: "12345",
|
|
101
|
+
sub_type: "other",
|
|
102
|
+
"zap:acc": "67890",
|
|
103
|
+
};
|
|
104
|
+
const jwt = createJwt(payload);
|
|
105
|
+
expect(extractUserIdsFromJwt(jwt)).toEqual({
|
|
106
|
+
customuser_id: null,
|
|
107
|
+
account_id: 67890,
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
it("should return null customuser_id when sub is missing", () => {
|
|
111
|
+
const payload = {
|
|
112
|
+
sub_type: "customuser",
|
|
113
|
+
"zap:acc": "67890",
|
|
114
|
+
};
|
|
115
|
+
const jwt = createJwt(payload);
|
|
116
|
+
expect(extractUserIdsFromJwt(jwt)).toEqual({
|
|
117
|
+
customuser_id: null,
|
|
118
|
+
account_id: 67890,
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
it("should return null account_id when zap:acc is missing", () => {
|
|
122
|
+
const payload = {
|
|
123
|
+
sub: "12345",
|
|
124
|
+
sub_type: "customuser",
|
|
125
|
+
};
|
|
126
|
+
const jwt = createJwt(payload);
|
|
127
|
+
expect(extractUserIdsFromJwt(jwt)).toEqual({
|
|
128
|
+
customuser_id: 12345,
|
|
129
|
+
account_id: null,
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
it("should return null values when both fields are missing", () => {
|
|
133
|
+
const payload = {
|
|
134
|
+
other_field: "value",
|
|
135
|
+
};
|
|
136
|
+
const jwt = createJwt(payload);
|
|
137
|
+
expect(extractUserIdsFromJwt(jwt)).toEqual({
|
|
138
|
+
customuser_id: null,
|
|
139
|
+
account_id: null,
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
it("should handle nested JWT for service tokens", () => {
|
|
143
|
+
const nestedPayload = {
|
|
144
|
+
sub: "12345",
|
|
145
|
+
sub_type: "customuser",
|
|
146
|
+
"zap:acc": "67890",
|
|
147
|
+
};
|
|
148
|
+
const nestedJwt = createJwt(nestedPayload);
|
|
149
|
+
const servicePayload = {
|
|
150
|
+
sub_type: "service",
|
|
151
|
+
njwt: nestedJwt,
|
|
152
|
+
};
|
|
153
|
+
const serviceJwt = createJwt(servicePayload);
|
|
154
|
+
expect(extractUserIdsFromJwt(serviceJwt)).toEqual({
|
|
155
|
+
customuser_id: 12345,
|
|
156
|
+
account_id: 67890,
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
it("should fall back to outer payload when nested JWT is invalid", () => {
|
|
160
|
+
const servicePayload = {
|
|
161
|
+
sub_type: "service",
|
|
162
|
+
njwt: "invalid.jwt",
|
|
163
|
+
"zap:acc": "99999",
|
|
164
|
+
};
|
|
165
|
+
const serviceJwt = createJwt(servicePayload);
|
|
166
|
+
expect(extractUserIdsFromJwt(serviceJwt)).toEqual({
|
|
167
|
+
customuser_id: null,
|
|
168
|
+
account_id: 99999,
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
it("should use outer payload when sub_type is service but njwt is missing", () => {
|
|
172
|
+
const servicePayload = {
|
|
173
|
+
sub: "12345",
|
|
174
|
+
sub_type: "service",
|
|
175
|
+
"zap:acc": "67890",
|
|
176
|
+
};
|
|
177
|
+
const serviceJwt = createJwt(servicePayload);
|
|
178
|
+
expect(extractUserIdsFromJwt(serviceJwt)).toEqual({
|
|
179
|
+
customuser_id: null,
|
|
180
|
+
account_id: 67890,
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
it("should return null for non-numeric sub values", () => {
|
|
184
|
+
const payload = {
|
|
185
|
+
sub: "not-a-number",
|
|
186
|
+
sub_type: "customuser",
|
|
187
|
+
"zap:acc": "67890",
|
|
188
|
+
};
|
|
189
|
+
const jwt = createJwt(payload);
|
|
190
|
+
expect(extractUserIdsFromJwt(jwt)).toEqual({
|
|
191
|
+
customuser_id: null,
|
|
192
|
+
account_id: 67890,
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
it("should return null for non-numeric zap:acc values", () => {
|
|
196
|
+
const payload = {
|
|
197
|
+
sub: "12345",
|
|
198
|
+
sub_type: "customuser",
|
|
199
|
+
"zap:acc": "not-a-number",
|
|
200
|
+
};
|
|
201
|
+
const jwt = createJwt(payload);
|
|
202
|
+
expect(extractUserIdsFromJwt(jwt)).toEqual({
|
|
203
|
+
customuser_id: 12345,
|
|
204
|
+
account_id: null,
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
it("should return null for values that parse to NaN", () => {
|
|
208
|
+
const payload = {
|
|
209
|
+
sub: "abc",
|
|
210
|
+
sub_type: "customuser",
|
|
211
|
+
"zap:acc": "xyz",
|
|
212
|
+
};
|
|
213
|
+
const jwt = createJwt(payload);
|
|
214
|
+
expect(extractUserIdsFromJwt(jwt)).toEqual({
|
|
215
|
+
customuser_id: null,
|
|
216
|
+
account_id: null,
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|
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;AAggBjB,eAAO,MAAM,eAAe,GAAI,SAAS,gBAAgB,KAAG,SAW3D,CAAC"}
|
package/dist/api/client.js
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
import { getAuthorizationHeader } from "./auth";
|
|
8
8
|
import { createDebugLogger, createDebugFetch } from "./debug";
|
|
9
9
|
import { pollUntilComplete } from "./polling";
|
|
10
|
-
import {
|
|
10
|
+
import { resolveAuthToken } from "../auth";
|
|
11
11
|
import { getZapierBaseUrl } from "../utils/url-utils";
|
|
12
12
|
import { ZapierApiError, ZapierAuthenticationError, ZapierValidationError, ZapierNotFoundError, } from "../types/errors";
|
|
13
|
-
import {
|
|
13
|
+
import { matchRoute } from "./router";
|
|
14
14
|
// Configuration for paths
|
|
15
15
|
const pathConfig = {
|
|
16
16
|
// e.g. /relay -> https://sdkapi.zapier.com/api/v0/sdk/relay/...
|
|
@@ -23,9 +23,6 @@ const pathConfig = {
|
|
|
23
23
|
authHeader: "Authorization",
|
|
24
24
|
pathPrefix: "/api/v0/sdk/zapier",
|
|
25
25
|
},
|
|
26
|
-
"/api/v0/apps": {
|
|
27
|
-
handlerOverride: handleListApps,
|
|
28
|
-
},
|
|
29
26
|
};
|
|
30
27
|
class ZapierApiClient {
|
|
31
28
|
constructor(options) {
|
|
@@ -71,16 +68,9 @@ class ZapierApiClient {
|
|
|
71
68
|
}
|
|
72
69
|
// Helper to get a token from the different places it could be gotten
|
|
73
70
|
async getAuthToken() {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (this.options.getToken) {
|
|
78
|
-
const token = await this.options.getToken();
|
|
79
|
-
if (token) {
|
|
80
|
-
return token;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return getTokenFromEnvOrConfig({
|
|
71
|
+
return resolveAuthToken({
|
|
72
|
+
token: this.options.token,
|
|
73
|
+
getToken: this.options.getToken,
|
|
84
74
|
onEvent: this.options.onEvent,
|
|
85
75
|
fetch: this.options.fetch,
|
|
86
76
|
baseUrl: this.options.baseUrl,
|
|
@@ -200,16 +190,6 @@ class ZapierApiClient {
|
|
|
200
190
|
}
|
|
201
191
|
return undefined;
|
|
202
192
|
}
|
|
203
|
-
// Helper to check if a path config has a handler override
|
|
204
|
-
hasHandlerOverride(pathConfig) {
|
|
205
|
-
return (pathConfig !== undefined &&
|
|
206
|
-
"handlerOverride" in pathConfig &&
|
|
207
|
-
typeof pathConfig.handlerOverride === "function");
|
|
208
|
-
}
|
|
209
|
-
// Helper to check if a path config is a standard path config
|
|
210
|
-
isStandardPathConfig(pathConfig) {
|
|
211
|
-
return pathConfig !== undefined && !this.hasHandlerOverride(pathConfig);
|
|
212
|
-
}
|
|
213
193
|
// Helper to parse API error response
|
|
214
194
|
parseErrorResponse(errorInfo) {
|
|
215
195
|
// If we can't parse data, use status text
|
|
@@ -275,8 +255,10 @@ class ZapierApiClient {
|
|
|
275
255
|
};
|
|
276
256
|
}
|
|
277
257
|
// For a base URL that isn't a Zapier-inferred domain, use the whole base URL.
|
|
258
|
+
const baseUrl = new URL(this.options.baseUrl);
|
|
259
|
+
const fullPath = baseUrl.pathname.replace(/\/$/, "") + path;
|
|
278
260
|
return {
|
|
279
|
-
url: new URL(
|
|
261
|
+
url: new URL(fullPath, baseUrl.origin),
|
|
280
262
|
pathConfig: config,
|
|
281
263
|
};
|
|
282
264
|
}
|
|
@@ -299,7 +281,7 @@ class ZapierApiClient {
|
|
|
299
281
|
// session!
|
|
300
282
|
const authToken = await this.getAuthToken();
|
|
301
283
|
if (authToken) {
|
|
302
|
-
const authHeaderName =
|
|
284
|
+
const authHeaderName = pathConfig && pathConfig.authHeader
|
|
303
285
|
? pathConfig.authHeader
|
|
304
286
|
: "Authorization";
|
|
305
287
|
headers.set(authHeaderName, getAuthorizationHeader(authToken));
|
|
@@ -316,12 +298,16 @@ class ZapierApiClient {
|
|
|
316
298
|
// Helper to perform HTTP requests with JSON handling
|
|
317
299
|
async fetchJson(method, path, data, options = {}) {
|
|
318
300
|
// Check if this path has a handler override
|
|
319
|
-
const
|
|
320
|
-
if (
|
|
301
|
+
const routeMatch = matchRoute(method, path);
|
|
302
|
+
if (routeMatch) {
|
|
321
303
|
// Invoke the handler instead of making an HTTP request
|
|
322
|
-
//
|
|
323
|
-
const handlerRequest =
|
|
324
|
-
|
|
304
|
+
// Merge params from all sources: body, searchParams, and route params
|
|
305
|
+
const handlerRequest = {
|
|
306
|
+
...(typeof data === "object" ? data : {}),
|
|
307
|
+
...options.searchParams,
|
|
308
|
+
...routeMatch.params,
|
|
309
|
+
};
|
|
310
|
+
return routeMatch.handler({
|
|
325
311
|
request: handlerRequest,
|
|
326
312
|
deps: {
|
|
327
313
|
httpClient: this,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.methods.test.d.ts","sourceRoot":"","sources":["../../src/api/client.methods.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, } from "vitest";
|
|
2
|
+
import { createZapierApi } from "./client";
|
|
3
|
+
import * as router from "./router";
|
|
4
|
+
vi.mock("./router");
|
|
5
|
+
describe("ZapierApiClient Methods & Routing", () => {
|
|
6
|
+
let mockFetch;
|
|
7
|
+
let mockMatchRoute;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
vi.clearAllMocks();
|
|
10
|
+
mockFetch = vi.fn();
|
|
11
|
+
mockMatchRoute = vi.mocked(router.matchRoute);
|
|
12
|
+
// Default fetch implementation
|
|
13
|
+
mockFetch.mockResolvedValue({
|
|
14
|
+
ok: true,
|
|
15
|
+
status: 200,
|
|
16
|
+
json: async () => ({ data: "default-response" }),
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
const createClient = () => createZapierApi({
|
|
20
|
+
baseUrl: "https://api.zapier.com",
|
|
21
|
+
token: "test-token",
|
|
22
|
+
fetch: mockFetch,
|
|
23
|
+
});
|
|
24
|
+
describe("GET requests", () => {
|
|
25
|
+
it("should route GET requests to handler when matched", async () => {
|
|
26
|
+
const client = createClient();
|
|
27
|
+
const mockHandler = vi.fn().mockResolvedValue({ id: 1, name: "Test" });
|
|
28
|
+
mockMatchRoute.mockReturnValue({
|
|
29
|
+
handler: mockHandler,
|
|
30
|
+
params: { id: "123" },
|
|
31
|
+
});
|
|
32
|
+
const result = await client.get("/api/resource/123", {
|
|
33
|
+
searchParams: { include: "details" },
|
|
34
|
+
});
|
|
35
|
+
expect(mockMatchRoute).toHaveBeenCalledWith("GET", "/api/resource/123");
|
|
36
|
+
expect(mockHandler).toHaveBeenCalledWith({
|
|
37
|
+
request: {
|
|
38
|
+
id: "123",
|
|
39
|
+
include: "details",
|
|
40
|
+
},
|
|
41
|
+
deps: expect.objectContaining({
|
|
42
|
+
httpClient: expect.any(Object),
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
expect(result).toEqual({ id: 1, name: "Test" });
|
|
46
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
47
|
+
});
|
|
48
|
+
it("should fallback to plain fetch when no route matches", async () => {
|
|
49
|
+
const client = createClient();
|
|
50
|
+
mockMatchRoute.mockReturnValue(null);
|
|
51
|
+
await client.get("/api/unknown");
|
|
52
|
+
expect(mockMatchRoute).toHaveBeenCalledWith("GET", "/api/unknown");
|
|
53
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("/api/unknown"), expect.objectContaining({
|
|
54
|
+
method: "GET",
|
|
55
|
+
}));
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe("POST requests", () => {
|
|
59
|
+
it("should route POST requests to handler with body", async () => {
|
|
60
|
+
const client = createClient();
|
|
61
|
+
const mockHandler = vi.fn().mockResolvedValue({ success: true });
|
|
62
|
+
const body = { name: "New Item" };
|
|
63
|
+
mockMatchRoute.mockReturnValue({
|
|
64
|
+
handler: mockHandler,
|
|
65
|
+
params: {},
|
|
66
|
+
});
|
|
67
|
+
await client.post("/api/resource", body);
|
|
68
|
+
expect(mockMatchRoute).toHaveBeenCalledWith("POST", "/api/resource");
|
|
69
|
+
expect(mockHandler).toHaveBeenCalledWith({
|
|
70
|
+
request: {
|
|
71
|
+
name: "New Item",
|
|
72
|
+
},
|
|
73
|
+
deps: expect.objectContaining({
|
|
74
|
+
httpClient: expect.any(Object),
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
78
|
+
});
|
|
79
|
+
it("should merge body, params, and query params for handler", async () => {
|
|
80
|
+
const client = createClient();
|
|
81
|
+
const mockHandler = vi.fn().mockResolvedValue({ success: true });
|
|
82
|
+
const body = { name: "Updated Item" };
|
|
83
|
+
mockMatchRoute.mockReturnValue({
|
|
84
|
+
handler: mockHandler,
|
|
85
|
+
params: { id: "456" },
|
|
86
|
+
});
|
|
87
|
+
await client.post("/api/resource/456", body, {
|
|
88
|
+
searchParams: { version: "2" },
|
|
89
|
+
});
|
|
90
|
+
expect(mockHandler).toHaveBeenCalledWith({
|
|
91
|
+
request: {
|
|
92
|
+
name: "Updated Item", // from body
|
|
93
|
+
id: "456", // from route params
|
|
94
|
+
version: "2", // from query params
|
|
95
|
+
},
|
|
96
|
+
deps: expect.any(Object),
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe("PUT requests", () => {
|
|
101
|
+
it("should route PUT requests to handler", async () => {
|
|
102
|
+
const client = createClient();
|
|
103
|
+
const mockHandler = vi.fn().mockResolvedValue({ updated: true });
|
|
104
|
+
const body = { status: "active" };
|
|
105
|
+
mockMatchRoute.mockReturnValue({
|
|
106
|
+
handler: mockHandler,
|
|
107
|
+
params: { id: "789" },
|
|
108
|
+
});
|
|
109
|
+
await client.put("/api/resource/789", body);
|
|
110
|
+
expect(mockMatchRoute).toHaveBeenCalledWith("PUT", "/api/resource/789");
|
|
111
|
+
expect(mockHandler).toHaveBeenCalledWith({
|
|
112
|
+
request: {
|
|
113
|
+
status: "active",
|
|
114
|
+
id: "789",
|
|
115
|
+
},
|
|
116
|
+
deps: expect.any(Object),
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe("DELETE requests", () => {
|
|
121
|
+
it("should route DELETE requests to handler", async () => {
|
|
122
|
+
const client = createClient();
|
|
123
|
+
const mockHandler = vi.fn().mockResolvedValue({ deleted: true });
|
|
124
|
+
mockMatchRoute.mockReturnValue({
|
|
125
|
+
handler: mockHandler,
|
|
126
|
+
params: { id: "999" },
|
|
127
|
+
});
|
|
128
|
+
await client.delete("/api/resource/999");
|
|
129
|
+
expect(mockMatchRoute).toHaveBeenCalledWith("DELETE", "/api/resource/999");
|
|
130
|
+
expect(mockHandler).toHaveBeenCalledWith({
|
|
131
|
+
request: {
|
|
132
|
+
id: "999",
|
|
133
|
+
},
|
|
134
|
+
deps: expect.any(Object),
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
describe("Header handling", () => {
|
|
139
|
+
it("should pass headers to plain fetch but NOT to handler request object (by default)", async () => {
|
|
140
|
+
const client = createClient();
|
|
141
|
+
const mockHandler = vi.fn().mockResolvedValue({});
|
|
142
|
+
mockMatchRoute.mockReturnValue({
|
|
143
|
+
handler: mockHandler,
|
|
144
|
+
params: {},
|
|
145
|
+
});
|
|
146
|
+
await client.get("/api/test", {
|
|
147
|
+
headers: { "X-Custom": "Value" },
|
|
148
|
+
});
|
|
149
|
+
// The handler request object only includes params/body, not headers
|
|
150
|
+
// based on client.ts implementation:
|
|
151
|
+
// const handlerRequest = { ...data, ...options.searchParams, ...routeMatch.params };
|
|
152
|
+
expect(mockHandler).toHaveBeenCalledWith({
|
|
153
|
+
request: {},
|
|
154
|
+
deps: expect.any(Object),
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
package/dist/api/client.test.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createZapierApi } from "./client";
|
|
|
3
3
|
import * as auth from "../auth";
|
|
4
4
|
vi.mock("../auth");
|
|
5
5
|
describe("ApiClient", () => {
|
|
6
|
-
const
|
|
6
|
+
const mockResolveAuthToken = vi.mocked(auth.resolveAuthToken);
|
|
7
7
|
beforeEach(() => {
|
|
8
8
|
vi.clearAllMocks();
|
|
9
9
|
// Prevent any actual HTTP calls
|
|
@@ -14,8 +14,8 @@ describe("ApiClient", () => {
|
|
|
14
14
|
});
|
|
15
15
|
});
|
|
16
16
|
describe("authentication token resolution", () => {
|
|
17
|
-
it("should pass authBaseUrl to
|
|
18
|
-
|
|
17
|
+
it("should pass authBaseUrl to resolveAuthToken when provided", async () => {
|
|
18
|
+
mockResolveAuthToken.mockResolvedValue("test-token");
|
|
19
19
|
const client = createZapierApi({
|
|
20
20
|
baseUrl: "https://api.custom.zapier.dev",
|
|
21
21
|
authBaseUrl: "https://auth.custom.zapier.dev",
|
|
@@ -23,7 +23,9 @@ describe("ApiClient", () => {
|
|
|
23
23
|
});
|
|
24
24
|
// Make a request that would trigger token resolution
|
|
25
25
|
await client.get("/test", { authRequired: true });
|
|
26
|
-
expect(
|
|
26
|
+
expect(mockResolveAuthToken).toHaveBeenCalledWith({
|
|
27
|
+
token: undefined,
|
|
28
|
+
getToken: undefined,
|
|
27
29
|
onEvent: undefined,
|
|
28
30
|
fetch: expect.any(Function),
|
|
29
31
|
baseUrl: "https://api.custom.zapier.dev",
|
|
@@ -32,7 +34,7 @@ describe("ApiClient", () => {
|
|
|
32
34
|
});
|
|
33
35
|
});
|
|
34
36
|
it("should pass undefined authBaseUrl when not provided", async () => {
|
|
35
|
-
|
|
37
|
+
mockResolveAuthToken.mockResolvedValue("test-token");
|
|
36
38
|
const client = createZapierApi({
|
|
37
39
|
baseUrl: "https://api.custom.zapier.dev",
|
|
38
40
|
// authBaseUrl intentionally omitted
|
|
@@ -40,7 +42,9 @@ describe("ApiClient", () => {
|
|
|
40
42
|
});
|
|
41
43
|
// Make a request that would trigger token resolution
|
|
42
44
|
await client.get("/test", { authRequired: true });
|
|
43
|
-
expect(
|
|
45
|
+
expect(mockResolveAuthToken).toHaveBeenCalledWith({
|
|
46
|
+
token: undefined,
|
|
47
|
+
getToken: undefined,
|
|
44
48
|
onEvent: undefined,
|
|
45
49
|
fetch: expect.any(Function),
|
|
46
50
|
baseUrl: "https://api.custom.zapier.dev",
|
|
@@ -48,8 +52,8 @@ describe("ApiClient", () => {
|
|
|
48
52
|
authClientId: undefined,
|
|
49
53
|
});
|
|
50
54
|
});
|
|
51
|
-
it("should pass authClientId to
|
|
52
|
-
|
|
55
|
+
it("should pass authClientId to resolveAuthToken when provided", async () => {
|
|
56
|
+
mockResolveAuthToken.mockResolvedValue("test-token");
|
|
53
57
|
const client = createZapierApi({
|
|
54
58
|
baseUrl: "https://api.custom.zapier.dev",
|
|
55
59
|
authClientId: "custom-client-id",
|
|
@@ -57,7 +61,9 @@ describe("ApiClient", () => {
|
|
|
57
61
|
});
|
|
58
62
|
// Make a request that would trigger token resolution
|
|
59
63
|
await client.get("/test", { authRequired: true });
|
|
60
|
-
expect(
|
|
64
|
+
expect(mockResolveAuthToken).toHaveBeenCalledWith({
|
|
65
|
+
token: undefined,
|
|
66
|
+
getToken: undefined,
|
|
61
67
|
onEvent: undefined,
|
|
62
68
|
fetch: expect.any(Function),
|
|
63
69
|
baseUrl: "https://api.custom.zapier.dev",
|
|
@@ -65,7 +71,8 @@ describe("ApiClient", () => {
|
|
|
65
71
|
authClientId: "custom-client-id",
|
|
66
72
|
});
|
|
67
73
|
});
|
|
68
|
-
it("should not call
|
|
74
|
+
it("should not call resolveAuthToken when token is provided directly", async () => {
|
|
75
|
+
mockResolveAuthToken.mockResolvedValue("direct-token");
|
|
69
76
|
const client = createZapierApi({
|
|
70
77
|
token: "direct-token",
|
|
71
78
|
baseUrl: "https://api.custom.zapier.dev",
|
|
@@ -74,7 +81,16 @@ describe("ApiClient", () => {
|
|
|
74
81
|
});
|
|
75
82
|
// Make a request that would use the direct token
|
|
76
83
|
await client.get("/test", { authRequired: true });
|
|
77
|
-
|
|
84
|
+
// resolveAuthToken is still called, but it returns the direct token immediately
|
|
85
|
+
expect(mockResolveAuthToken).toHaveBeenCalledWith({
|
|
86
|
+
token: "direct-token",
|
|
87
|
+
getToken: undefined,
|
|
88
|
+
onEvent: undefined,
|
|
89
|
+
fetch: expect.any(Function),
|
|
90
|
+
baseUrl: "https://api.custom.zapier.dev",
|
|
91
|
+
authBaseUrl: "https://auth.custom.zapier.dev",
|
|
92
|
+
authClientId: undefined,
|
|
93
|
+
});
|
|
78
94
|
});
|
|
79
95
|
});
|
|
80
96
|
});
|