@zapier/zapier-sdk 0.13.6 → 0.13.7
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 +7 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +7 -2
- package/src/api/auth.ts +0 -28
- package/src/api/client.ts +0 -491
- package/src/api/debug.test.ts +0 -76
- package/src/api/debug.ts +0 -154
- package/src/api/index.ts +0 -90
- package/src/api/polling.test.ts +0 -405
- package/src/api/polling.ts +0 -253
- package/src/api/schemas.ts +0 -465
- package/src/api/types.ts +0 -152
- package/src/auth.ts +0 -72
- package/src/constants.ts +0 -16
- package/src/index.ts +0 -111
- package/src/plugins/api/index.ts +0 -43
- package/src/plugins/apps/index.ts +0 -203
- package/src/plugins/apps/schemas.ts +0 -64
- package/src/plugins/eventEmission/builders.ts +0 -115
- package/src/plugins/eventEmission/index.test.ts +0 -169
- package/src/plugins/eventEmission/index.ts +0 -294
- package/src/plugins/eventEmission/transport.test.ts +0 -214
- package/src/plugins/eventEmission/transport.ts +0 -135
- package/src/plugins/eventEmission/types.ts +0 -58
- package/src/plugins/eventEmission/utils.ts +0 -121
- package/src/plugins/fetch/index.ts +0 -83
- package/src/plugins/fetch/schemas.ts +0 -37
- package/src/plugins/findFirstAuthentication/index.test.ts +0 -209
- package/src/plugins/findFirstAuthentication/index.ts +0 -68
- package/src/plugins/findFirstAuthentication/schemas.ts +0 -47
- package/src/plugins/findUniqueAuthentication/index.test.ts +0 -197
- package/src/plugins/findUniqueAuthentication/index.ts +0 -77
- package/src/plugins/findUniqueAuthentication/schemas.ts +0 -49
- package/src/plugins/getAction/index.test.ts +0 -239
- package/src/plugins/getAction/index.ts +0 -75
- package/src/plugins/getAction/schemas.ts +0 -41
- package/src/plugins/getApp/index.test.ts +0 -181
- package/src/plugins/getApp/index.ts +0 -60
- package/src/plugins/getApp/schemas.ts +0 -33
- package/src/plugins/getAuthentication/index.test.ts +0 -294
- package/src/plugins/getAuthentication/index.ts +0 -95
- package/src/plugins/getAuthentication/schemas.ts +0 -38
- package/src/plugins/getProfile/index.ts +0 -60
- package/src/plugins/getProfile/schemas.ts +0 -24
- package/src/plugins/listActions/index.test.ts +0 -526
- package/src/plugins/listActions/index.ts +0 -132
- package/src/plugins/listActions/schemas.ts +0 -55
- package/src/plugins/listApps/index.test.ts +0 -378
- package/src/plugins/listApps/index.ts +0 -159
- package/src/plugins/listApps/schemas.ts +0 -41
- package/src/plugins/listAuthentications/index.test.ts +0 -739
- package/src/plugins/listAuthentications/index.ts +0 -152
- package/src/plugins/listAuthentications/schemas.ts +0 -77
- package/src/plugins/listInputFieldChoices/index.test.ts +0 -653
- package/src/plugins/listInputFieldChoices/index.ts +0 -173
- package/src/plugins/listInputFieldChoices/schemas.ts +0 -125
- package/src/plugins/listInputFields/index.test.ts +0 -439
- package/src/plugins/listInputFields/index.ts +0 -294
- package/src/plugins/listInputFields/schemas.ts +0 -68
- package/src/plugins/manifest/index.test.ts +0 -776
- package/src/plugins/manifest/index.ts +0 -461
- package/src/plugins/manifest/schemas.ts +0 -60
- package/src/plugins/registry/index.ts +0 -160
- package/src/plugins/request/index.test.ts +0 -333
- package/src/plugins/request/index.ts +0 -105
- package/src/plugins/request/schemas.ts +0 -69
- package/src/plugins/runAction/index.test.ts +0 -388
- package/src/plugins/runAction/index.ts +0 -215
- package/src/plugins/runAction/schemas.ts +0 -60
- package/src/resolvers/actionKey.ts +0 -37
- package/src/resolvers/actionType.ts +0 -34
- package/src/resolvers/appKey.ts +0 -7
- package/src/resolvers/authenticationId.ts +0 -54
- package/src/resolvers/index.ts +0 -11
- package/src/resolvers/inputFieldKey.ts +0 -70
- package/src/resolvers/inputs.ts +0 -69
- package/src/schemas/Action.ts +0 -52
- package/src/schemas/App.ts +0 -45
- package/src/schemas/Auth.ts +0 -59
- package/src/schemas/Field.ts +0 -169
- package/src/schemas/Run.ts +0 -40
- package/src/schemas/UserProfile.ts +0 -60
- package/src/sdk.test.ts +0 -212
- package/src/sdk.ts +0 -178
- package/src/types/domain.test.ts +0 -50
- package/src/types/domain.ts +0 -66
- package/src/types/errors.ts +0 -278
- package/src/types/events.ts +0 -43
- package/src/types/functions.ts +0 -28
- package/src/types/optional-zapier-sdk-cli-login.d.ts +0 -37
- package/src/types/plugin.ts +0 -125
- package/src/types/properties.ts +0 -80
- package/src/types/sdk.ts +0 -111
- package/src/types/telemetry-events.ts +0 -85
- package/src/utils/array-utils.test.ts +0 -131
- package/src/utils/array-utils.ts +0 -41
- package/src/utils/domain-utils.test.ts +0 -433
- package/src/utils/domain-utils.ts +0 -267
- package/src/utils/file-utils.test.ts +0 -73
- package/src/utils/file-utils.ts +0 -94
- package/src/utils/function-utils.test.ts +0 -141
- package/src/utils/function-utils.ts +0 -245
- package/src/utils/pagination-utils.test.ts +0 -620
- package/src/utils/pagination-utils.ts +0 -242
- package/src/utils/schema-utils.ts +0 -207
- package/src/utils/string-utils.test.ts +0 -45
- package/src/utils/string-utils.ts +0 -54
- package/src/utils/validation.test.ts +0 -51
- package/src/utils/validation.ts +0 -44
- package/tsconfig.build.json +0 -18
- package/tsconfig.json +0 -20
- package/tsconfig.tsbuildinfo +0 -1
- package/tsup.config.ts +0 -23
package/CHANGELOG.md
CHANGED
package/dist/index.cjs
CHANGED
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zapier/zapier-sdk",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.7",
|
|
4
4
|
"description": "Complete Zapier SDK - combines all Zapier SDK packages",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md",
|
|
23
|
+
"CHANGELOG.md"
|
|
24
|
+
],
|
|
20
25
|
"keywords": [
|
|
21
26
|
"zapier",
|
|
22
27
|
"sdk",
|
|
@@ -28,7 +33,7 @@
|
|
|
28
33
|
"author": "",
|
|
29
34
|
"license": "ISC",
|
|
30
35
|
"publishConfig": {
|
|
31
|
-
"access": "
|
|
36
|
+
"access": "public"
|
|
32
37
|
},
|
|
33
38
|
"dependencies": {
|
|
34
39
|
"zod": "^3.25.67"
|
package/src/api/auth.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Authentication Utilities
|
|
3
|
-
*
|
|
4
|
-
* This module provides utilities for handling authentication tokens,
|
|
5
|
-
* including JWT detection and authorization header generation.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export function isJwt(token: string): boolean {
|
|
9
|
-
// JWT tokens have exactly 3 parts separated by dots
|
|
10
|
-
const parts = token.split(".");
|
|
11
|
-
if (parts.length !== 3) {
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Each part should be base64url encoded (no padding)
|
|
16
|
-
// Basic validation - each part should be non-empty and contain valid base64url characters
|
|
17
|
-
const base64UrlPattern = /^[A-Za-z0-9_-]+$/;
|
|
18
|
-
return parts.every((part) => part.length > 0 && base64UrlPattern.test(part));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function getAuthorizationHeader(token: string): string {
|
|
22
|
-
// Check if token is a JWT (has 3 parts separated by dots)
|
|
23
|
-
if (isJwt(token)) {
|
|
24
|
-
return `JWT ${token}`;
|
|
25
|
-
}
|
|
26
|
-
// Default to Bearer for other token types
|
|
27
|
-
return `Bearer ${token}`;
|
|
28
|
-
}
|
package/src/api/client.ts
DELETED
|
@@ -1,491 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API Client Implementation
|
|
3
|
-
*
|
|
4
|
-
* This module contains the core API client implementation, including
|
|
5
|
-
* HTTP method handlers, response processing, and client factory functions.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
ApiClient,
|
|
10
|
-
ApiClientOptions,
|
|
11
|
-
RequestOptions,
|
|
12
|
-
PollOptions,
|
|
13
|
-
} from "./types";
|
|
14
|
-
import { getAuthorizationHeader } from "./auth";
|
|
15
|
-
import { createDebugLogger, createDebugFetch } from "./debug";
|
|
16
|
-
import { pollUntilComplete } from "./polling";
|
|
17
|
-
import { getTokenFromEnvOrConfig } from "../auth";
|
|
18
|
-
import {
|
|
19
|
-
ZapierApiError,
|
|
20
|
-
ZapierAuthenticationError,
|
|
21
|
-
ZapierValidationError,
|
|
22
|
-
ZapierNotFoundError,
|
|
23
|
-
type ApiError,
|
|
24
|
-
} from "../types/errors";
|
|
25
|
-
|
|
26
|
-
// Configuration for path prefixes that should be treated as subdomains
|
|
27
|
-
type SubdomainConfigMap = typeof SubdomainConfigMap;
|
|
28
|
-
type SubdomainConfig = SubdomainConfigMap[keyof SubdomainConfigMap];
|
|
29
|
-
const SubdomainConfigMap = {
|
|
30
|
-
// e.g. https://relay.zapier.com
|
|
31
|
-
relay: {
|
|
32
|
-
authHeader: "X-Relay-Authorization",
|
|
33
|
-
},
|
|
34
|
-
} as const;
|
|
35
|
-
|
|
36
|
-
class ZapierApiClient implements ApiClient {
|
|
37
|
-
constructor(
|
|
38
|
-
private readonly options: ApiClientOptions &
|
|
39
|
-
Required<Pick<ApiClientOptions, "fetch">>,
|
|
40
|
-
) {}
|
|
41
|
-
|
|
42
|
-
// Helper to parse response data
|
|
43
|
-
private async parseResult<TOutput = unknown>(
|
|
44
|
-
response: Response,
|
|
45
|
-
): Promise<{ type: "json"; data: TOutput } | { type: "text"; data: string }> {
|
|
46
|
-
try {
|
|
47
|
-
return { type: "json", data: await response.json() };
|
|
48
|
-
} catch {
|
|
49
|
-
return { type: "text", data: await response.text() };
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Helper to get a token from the different places it could be gotten
|
|
54
|
-
private async getAuthToken(): Promise<string | undefined> {
|
|
55
|
-
if (this.options.token) {
|
|
56
|
-
return this.options.token;
|
|
57
|
-
}
|
|
58
|
-
if (this.options.getToken) {
|
|
59
|
-
const token = await this.options.getToken();
|
|
60
|
-
if (token) {
|
|
61
|
-
return token;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return getTokenFromEnvOrConfig({
|
|
65
|
-
onEvent: this.options.onEvent,
|
|
66
|
-
fetch: this.options.fetch,
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Helper to handle responses
|
|
71
|
-
private async handleResponse<TOutput = unknown>(params: {
|
|
72
|
-
response: Response;
|
|
73
|
-
customErrorHandler?: RequestOptions["customErrorHandler"];
|
|
74
|
-
wasMissingAuthToken?: boolean;
|
|
75
|
-
}): Promise<string | TOutput> {
|
|
76
|
-
const { response, customErrorHandler, wasMissingAuthToken } = params;
|
|
77
|
-
const { data: responseData } = await this.parseResult<TOutput>(response);
|
|
78
|
-
if (response.ok) {
|
|
79
|
-
return responseData;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const errorInfo = {
|
|
83
|
-
status: response.status,
|
|
84
|
-
statusText: response.statusText,
|
|
85
|
-
data: responseData,
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// Check for custom error handling first
|
|
89
|
-
if (customErrorHandler) {
|
|
90
|
-
const customError = customErrorHandler(errorInfo);
|
|
91
|
-
if (customError) {
|
|
92
|
-
// javascript may not respect customErrorHandler return type, so we need to throw a proper error if it's not an Error
|
|
93
|
-
if (customError instanceof Error) {
|
|
94
|
-
throw customError;
|
|
95
|
-
} else {
|
|
96
|
-
throw new Error(
|
|
97
|
-
`customErrorHandler returned a non-Error: ${JSON.stringify(customError)}`,
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Parse the error response
|
|
104
|
-
const { message, errors } = this.parseErrorResponse(errorInfo);
|
|
105
|
-
|
|
106
|
-
const errorOptions = {
|
|
107
|
-
statusCode: response.status,
|
|
108
|
-
errors,
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
// Use appropriate error type based on status code
|
|
112
|
-
if (response.status === 404) {
|
|
113
|
-
throw new ZapierNotFoundError(message, errorOptions);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (response.status === 401 || response.status === 403) {
|
|
117
|
-
// If we get a 401/403 error and no auth token was provided, give helpful message
|
|
118
|
-
if (wasMissingAuthToken) {
|
|
119
|
-
throw new ZapierAuthenticationError(
|
|
120
|
-
`Authentication required (HTTP ${response.status}). Please provide a token in options or set ZAPIER_TOKEN environment variable.`,
|
|
121
|
-
errorOptions,
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
throw new ZapierAuthenticationError(message, errorOptions);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (response.status === 400) {
|
|
128
|
-
throw new ZapierValidationError(message, errorOptions);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Generic API error for other status codes
|
|
132
|
-
throw new ZapierApiError(message, errorOptions);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
private hasErrorArray<TOutput = unknown>(
|
|
136
|
-
data: TOutput | string,
|
|
137
|
-
): data is { errors: unknown[] } & Exclude<TOutput, string> {
|
|
138
|
-
return (
|
|
139
|
-
typeof data === "object" &&
|
|
140
|
-
data !== null &&
|
|
141
|
-
"errors" in data &&
|
|
142
|
-
Array.isArray(data.errors)
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Helper to check if data has API errors
|
|
147
|
-
private isApiErrorArray(dataArray: unknown[]): dataArray is ApiError[] {
|
|
148
|
-
const data = dataArray[0];
|
|
149
|
-
return (
|
|
150
|
-
typeof data === "object" &&
|
|
151
|
-
data !== null &&
|
|
152
|
-
"message" in data &&
|
|
153
|
-
"code" in data &&
|
|
154
|
-
"title" in data &&
|
|
155
|
-
"detail" in data
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Do our best to extract an error message from the response data
|
|
160
|
-
private extractErrorMessage<TOutput = unknown>(
|
|
161
|
-
data: TOutput | string,
|
|
162
|
-
): string | undefined {
|
|
163
|
-
if (typeof data === "string") {
|
|
164
|
-
return data;
|
|
165
|
-
}
|
|
166
|
-
if (typeof data === "object" && data !== null) {
|
|
167
|
-
if ("message" in data && typeof data.message === "string") {
|
|
168
|
-
return data.message;
|
|
169
|
-
}
|
|
170
|
-
if ("error" in data) {
|
|
171
|
-
if (typeof data.error === "string") {
|
|
172
|
-
return data.error;
|
|
173
|
-
}
|
|
174
|
-
if (typeof data.error === "object" && data.error !== null) {
|
|
175
|
-
if (
|
|
176
|
-
"message" in data.error &&
|
|
177
|
-
typeof data.error.message === "string"
|
|
178
|
-
) {
|
|
179
|
-
return data.error.message;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
try {
|
|
183
|
-
return JSON.stringify(data.error);
|
|
184
|
-
} catch {
|
|
185
|
-
/* defend against circular objects, even though they shouldn't be coming back from the wire */
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if ("errors" in data && Array.isArray(data.errors)) {
|
|
189
|
-
if (this.isApiErrorArray(data.errors)) {
|
|
190
|
-
return data.errors[0].detail || data.errors[0].title;
|
|
191
|
-
} else if (data.errors.length > 0) {
|
|
192
|
-
// Handle simple string errors array
|
|
193
|
-
const firstError = data.errors[0];
|
|
194
|
-
if (typeof firstError === "string") {
|
|
195
|
-
return firstError;
|
|
196
|
-
}
|
|
197
|
-
// For non-string errors, stringify them
|
|
198
|
-
try {
|
|
199
|
-
return JSON.stringify(firstError);
|
|
200
|
-
} catch {
|
|
201
|
-
return String(firstError);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return undefined;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Helper to parse API error response
|
|
210
|
-
private parseErrorResponse<TOutput = unknown>(errorInfo: {
|
|
211
|
-
status: number;
|
|
212
|
-
statusText: string;
|
|
213
|
-
data: TOutput | string;
|
|
214
|
-
}): { message: string; errors?: ApiError[] } {
|
|
215
|
-
// If we can't parse data, use status text
|
|
216
|
-
const fallbackMessage = `HTTP ${errorInfo.status}: ${errorInfo.statusText}`;
|
|
217
|
-
try {
|
|
218
|
-
// Check if the response has the standard error format
|
|
219
|
-
if (typeof errorInfo.data === "string") {
|
|
220
|
-
return { message: `${fallbackMessage}: ${errorInfo.data}` };
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const errorMessage =
|
|
224
|
-
this.extractErrorMessage(errorInfo.data) || fallbackMessage;
|
|
225
|
-
|
|
226
|
-
if (this.hasErrorArray(errorInfo.data)) {
|
|
227
|
-
if (this.isApiErrorArray(errorInfo.data.errors)) {
|
|
228
|
-
return {
|
|
229
|
-
message: errorMessage,
|
|
230
|
-
errors: errorInfo.data.errors,
|
|
231
|
-
};
|
|
232
|
-
} else {
|
|
233
|
-
return {
|
|
234
|
-
message: errorMessage,
|
|
235
|
-
errors: errorInfo.data.errors.map((e) => ({
|
|
236
|
-
status: errorInfo.status,
|
|
237
|
-
code: String(errorInfo.status),
|
|
238
|
-
title: errorInfo.statusText,
|
|
239
|
-
detail: JSON.stringify(e),
|
|
240
|
-
})),
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return { message: errorMessage };
|
|
246
|
-
} catch {
|
|
247
|
-
return { message: fallbackMessage };
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Check if this is a path that needs subdomain routing
|
|
252
|
-
// e.g. /relay/workflows -> relay.zapier.com/workflows
|
|
253
|
-
private applySubdomainBehavior(path: string): {
|
|
254
|
-
url: URL;
|
|
255
|
-
subdomainConfig?: SubdomainConfig;
|
|
256
|
-
} {
|
|
257
|
-
const pathSegments = path.split("/").filter(Boolean);
|
|
258
|
-
|
|
259
|
-
if (pathSegments.length > 0 && pathSegments[0] in SubdomainConfigMap) {
|
|
260
|
-
// Transform paths to use subdomain routing
|
|
261
|
-
// /prefix/domain/path -> prefix.zapier.com/domain/path
|
|
262
|
-
const domainPrefix = pathSegments[0] as keyof typeof SubdomainConfigMap;
|
|
263
|
-
const subdomainConfig = SubdomainConfigMap[domainPrefix];
|
|
264
|
-
const originalBaseUrl = new URL(this.options.baseUrl);
|
|
265
|
-
const finalBaseUrl = `https://${domainPrefix}.${originalBaseUrl.hostname}`;
|
|
266
|
-
const pathWithoutPrefix = "/" + pathSegments.slice(1).join("/");
|
|
267
|
-
return { url: new URL(pathWithoutPrefix, finalBaseUrl), subdomainConfig };
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return {
|
|
271
|
-
url: new URL(path, this.options.baseUrl),
|
|
272
|
-
subdomainConfig: undefined,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Helper to build full URLs and return routing info
|
|
277
|
-
private buildUrl(
|
|
278
|
-
path: string,
|
|
279
|
-
searchParams?: Record<string, string>,
|
|
280
|
-
): {
|
|
281
|
-
url: string;
|
|
282
|
-
subdomainConfig?: SubdomainConfig;
|
|
283
|
-
} {
|
|
284
|
-
const { url, subdomainConfig } = this.applySubdomainBehavior(path);
|
|
285
|
-
if (searchParams) {
|
|
286
|
-
Object.entries(searchParams).forEach(([key, value]) => {
|
|
287
|
-
url.searchParams.set(key, value);
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
return { url: url.toString(), subdomainConfig };
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Helper to build headers
|
|
294
|
-
private async buildHeaders(
|
|
295
|
-
options: RequestOptions = {},
|
|
296
|
-
subdomainConfig?: (typeof SubdomainConfigMap)[keyof typeof SubdomainConfigMap],
|
|
297
|
-
): Promise<Headers> {
|
|
298
|
-
const headers = new Headers(options.headers ?? {});
|
|
299
|
-
|
|
300
|
-
// Even if auth is not required, we still want to add it in case it adds
|
|
301
|
-
// useful context to the API. The session is a good example of this. Auth
|
|
302
|
-
// is not required, but if we don't add auth, then we won't get the user's
|
|
303
|
-
// session!
|
|
304
|
-
const authToken = await this.getAuthToken();
|
|
305
|
-
|
|
306
|
-
if (authToken) {
|
|
307
|
-
const authHeaderName = subdomainConfig?.authHeader || "Authorization";
|
|
308
|
-
headers.set(authHeaderName, getAuthorizationHeader(authToken));
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// If we know auth is required, and we don't have a token, throw an error
|
|
312
|
-
// before we even make a request.
|
|
313
|
-
if (options.authRequired) {
|
|
314
|
-
if (headers.get("Authorization") == null && authToken == null) {
|
|
315
|
-
throw new ZapierAuthenticationError(
|
|
316
|
-
`Authentication required but no token available. Please set ZAPIER_TOKEN, or run the 'login' command with the CLI.`,
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
return headers;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Helper to perform HTTP requests with JSON handling
|
|
325
|
-
private async fetchJson<TOutput = unknown>(
|
|
326
|
-
method: string,
|
|
327
|
-
path: string,
|
|
328
|
-
data?: unknown,
|
|
329
|
-
options: RequestOptions = {},
|
|
330
|
-
): Promise<TOutput> {
|
|
331
|
-
const headers = { ...options.headers };
|
|
332
|
-
|
|
333
|
-
// Add Content-Type for JSON requests with body data
|
|
334
|
-
if (data && typeof data === "object") {
|
|
335
|
-
headers["Content-Type"] = "application/json";
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Check if we have an auth token available
|
|
339
|
-
const wasMissingAuthToken =
|
|
340
|
-
options.authRequired && (await this.getAuthToken()) == null;
|
|
341
|
-
|
|
342
|
-
const response = await this.plainFetch(path, {
|
|
343
|
-
...options,
|
|
344
|
-
method,
|
|
345
|
-
body: data != null ? JSON.stringify(data) : undefined,
|
|
346
|
-
headers,
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
// plainFetch already handled all auth and headers
|
|
350
|
-
const result = await this.handleResponse<TOutput>({
|
|
351
|
-
response,
|
|
352
|
-
customErrorHandler: options.customErrorHandler,
|
|
353
|
-
wasMissingAuthToken,
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
if (typeof result === "string") {
|
|
357
|
-
throw new ZapierValidationError(
|
|
358
|
-
`Response could not be parsed as JSON: ${result}`,
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return result;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Plain fetch method for API paths (must start with /)
|
|
366
|
-
private async plainFetch(
|
|
367
|
-
path: string,
|
|
368
|
-
fetchOptions?: RequestInit & {
|
|
369
|
-
searchParams?: Record<string, string>;
|
|
370
|
-
authRequired?: boolean;
|
|
371
|
-
},
|
|
372
|
-
): Promise<Response> {
|
|
373
|
-
if (!path.startsWith("/")) {
|
|
374
|
-
throw new ZapierValidationError(
|
|
375
|
-
`plainFetch expects a path starting with '/', got: ${path}`,
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
if (fetchOptions?.body && typeof fetchOptions.body === "object") {
|
|
380
|
-
fetchOptions.body = JSON.stringify(fetchOptions.body);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
const { url, subdomainConfig } = this.buildUrl(
|
|
384
|
-
path,
|
|
385
|
-
fetchOptions?.searchParams,
|
|
386
|
-
);
|
|
387
|
-
|
|
388
|
-
const builtHeaders = await this.buildHeaders(
|
|
389
|
-
fetchOptions as RequestOptions,
|
|
390
|
-
subdomainConfig,
|
|
391
|
-
);
|
|
392
|
-
const inputHeaders = new Headers(fetchOptions?.headers ?? {});
|
|
393
|
-
|
|
394
|
-
const mergedHeaders = new Headers();
|
|
395
|
-
builtHeaders.forEach((value, key) => {
|
|
396
|
-
mergedHeaders.set(key, value);
|
|
397
|
-
});
|
|
398
|
-
inputHeaders.forEach((value, key) => {
|
|
399
|
-
mergedHeaders.set(key, value);
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
return await this.options.fetch(url, {
|
|
403
|
-
...fetchOptions,
|
|
404
|
-
headers: mergedHeaders,
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
public fetch = async (
|
|
409
|
-
path: string,
|
|
410
|
-
init?: RequestInit & {
|
|
411
|
-
searchParams?: Record<string, string>;
|
|
412
|
-
authRequired?: boolean;
|
|
413
|
-
},
|
|
414
|
-
): Promise<Response> => {
|
|
415
|
-
return this.plainFetch(path, init);
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
public get = async <TOutput = unknown>(
|
|
419
|
-
path: string,
|
|
420
|
-
options: RequestOptions = {},
|
|
421
|
-
): Promise<TOutput> => {
|
|
422
|
-
return this.fetchJson<TOutput>("GET", path, undefined, options);
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
public post = async <TOutput = unknown>(
|
|
426
|
-
path: string,
|
|
427
|
-
data?: unknown,
|
|
428
|
-
options: RequestOptions = {},
|
|
429
|
-
): Promise<TOutput> => {
|
|
430
|
-
return this.fetchJson<TOutput>("POST", path, data, options);
|
|
431
|
-
};
|
|
432
|
-
|
|
433
|
-
public put = async <TOutput = unknown>(
|
|
434
|
-
path: string,
|
|
435
|
-
data?: unknown,
|
|
436
|
-
options: RequestOptions = {},
|
|
437
|
-
): Promise<TOutput> => {
|
|
438
|
-
return this.fetchJson<TOutput>("PUT", path, data, options);
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
public delete = async <TOutput = unknown>(
|
|
442
|
-
path: string,
|
|
443
|
-
options: RequestOptions = {},
|
|
444
|
-
): Promise<TOutput> => {
|
|
445
|
-
return this.fetchJson<TOutput>("DELETE", path, undefined, options);
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
public poll = async <TOutput = unknown>(
|
|
449
|
-
path: string,
|
|
450
|
-
options: PollOptions = {},
|
|
451
|
-
): Promise<TOutput> => {
|
|
452
|
-
return pollUntilComplete<TOutput>({
|
|
453
|
-
fetchPoll: () =>
|
|
454
|
-
this.plainFetch(path, {
|
|
455
|
-
method: "GET",
|
|
456
|
-
searchParams: options.searchParams,
|
|
457
|
-
authRequired: options.authRequired,
|
|
458
|
-
}),
|
|
459
|
-
initialDelay: options.initialDelay,
|
|
460
|
-
timeoutMs: options.timeoutMs,
|
|
461
|
-
successStatus: options.successStatus,
|
|
462
|
-
pendingStatus: options.pendingStatus,
|
|
463
|
-
resultExtractor: options.resultExtractor as
|
|
464
|
-
| ((response: unknown) => TOutput)
|
|
465
|
-
| undefined,
|
|
466
|
-
});
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
export const createZapierApi = (options: ApiClientOptions): ApiClient => {
|
|
471
|
-
const {
|
|
472
|
-
baseUrl,
|
|
473
|
-
token,
|
|
474
|
-
getToken,
|
|
475
|
-
debug = false,
|
|
476
|
-
fetch: originalFetch = globalThis.fetch,
|
|
477
|
-
onEvent,
|
|
478
|
-
} = options;
|
|
479
|
-
|
|
480
|
-
const debugLog = createDebugLogger(debug);
|
|
481
|
-
const debugFetch = createDebugFetch({ originalFetch, debugLog });
|
|
482
|
-
|
|
483
|
-
return new ZapierApiClient({
|
|
484
|
-
baseUrl,
|
|
485
|
-
token,
|
|
486
|
-
getToken,
|
|
487
|
-
debug,
|
|
488
|
-
fetch: debugFetch,
|
|
489
|
-
onEvent,
|
|
490
|
-
});
|
|
491
|
-
};
|
package/src/api/debug.test.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { censorHeaders } from "./debug";
|
|
3
|
-
|
|
4
|
-
describe("censorHeaders", () => {
|
|
5
|
-
it("should return undefined for undefined headers", () => {
|
|
6
|
-
expect(censorHeaders(undefined)).toBeUndefined();
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
it("should return headers unchanged for non-auth headers", () => {
|
|
10
|
-
const headers = {
|
|
11
|
-
"Content-Type": "application/json",
|
|
12
|
-
"User-Agent": "test",
|
|
13
|
-
};
|
|
14
|
-
const result = censorHeaders(headers);
|
|
15
|
-
// Headers API normalizes header names to lowercase
|
|
16
|
-
expect(result!["content-type"]).toBe("application/json");
|
|
17
|
-
expect(result!["user-agent"]).toBe("test");
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("should censor authorization header with Bearer prefix - long token", () => {
|
|
21
|
-
const headers = { authorization: "Bearer abcdef1234567890xyz" };
|
|
22
|
-
const result = censorHeaders(headers);
|
|
23
|
-
|
|
24
|
-
expect(result!.authorization).toBe("Bearer abcd...0xyz");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("should censor authorization header with Bearer prefix - short token", () => {
|
|
28
|
-
const headers = { authorization: "Bearer short123" };
|
|
29
|
-
const result = censorHeaders(headers);
|
|
30
|
-
|
|
31
|
-
expect(result!.authorization).toBe("Bearer s...");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("should censor x-api-key header - long token", () => {
|
|
35
|
-
const headers = { "x-api-key": "sk-1234567890abcdefghij" };
|
|
36
|
-
const result = censorHeaders(headers);
|
|
37
|
-
|
|
38
|
-
expect(result!["x-api-key"]).toBe("sk-1...ghij");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("should censor x-api-key header - short token", () => {
|
|
42
|
-
const headers = { "x-api-key": "short" };
|
|
43
|
-
const result = censorHeaders(headers);
|
|
44
|
-
|
|
45
|
-
expect(result!["x-api-key"]).toBe("s...");
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("should handle Headers object input", () => {
|
|
49
|
-
const headers = new Headers();
|
|
50
|
-
headers.set("authorization", "Bearer verylongtoken123456789");
|
|
51
|
-
|
|
52
|
-
const result = censorHeaders(headers);
|
|
53
|
-
expect(result!.authorization).toBe("Bearer very...6789");
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("should handle mixed case authorization headers", () => {
|
|
57
|
-
const headers = { Authorization: "Bearer mixedcasetoken123" };
|
|
58
|
-
const result = censorHeaders(headers);
|
|
59
|
-
|
|
60
|
-
expect(result!.authorization).toBe("Bearer mixe...n123");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("should preserve non-auth headers while censoring auth headers", () => {
|
|
64
|
-
const headers = {
|
|
65
|
-
"Content-Type": "application/json",
|
|
66
|
-
authorization: "Bearer shouldbecensored123456789",
|
|
67
|
-
"User-Agent": "test-agent",
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const result = censorHeaders(headers);
|
|
71
|
-
|
|
72
|
-
expect(result!["content-type"]).toBe("application/json");
|
|
73
|
-
expect(result!["user-agent"]).toBe("test-agent");
|
|
74
|
-
expect(result!.authorization).toBe("Bearer shou...6789");
|
|
75
|
-
});
|
|
76
|
-
});
|