@zapier/zapier-sdk 0.42.1 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +75 -73
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +309 -35
- package/dist/api/types.d.ts +29 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/constants.d.ts +25 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +32 -0
- package/dist/index.cjs +441 -44
- package/dist/index.d.mts +82 -4
- package/dist/index.mjs +437 -45
- package/dist/plugins/api/index.d.ts.map +1 -1
- package/dist/plugins/api/index.js +5 -1
- package/dist/plugins/createClientCredentials/index.d.ts.map +1 -1
- package/dist/plugins/createClientCredentials/index.js +1 -0
- package/dist/plugins/createClientCredentials/schemas.d.ts +1 -0
- package/dist/plugins/createClientCredentials/schemas.d.ts.map +1 -1
- package/dist/plugins/createClientCredentials/schemas.js +6 -0
- package/dist/plugins/fetch/index.d.ts.map +1 -1
- package/dist/plugins/fetch/index.js +15 -1
- package/dist/plugins/getInputFieldsSchema/schemas.js +1 -1
- package/dist/plugins/listInputFieldChoices/schemas.js +1 -1
- package/dist/plugins/listInputFields/schemas.js +1 -1
- package/dist/plugins/manifest/schemas.d.ts +2 -2
- package/dist/plugins/runAction/index.d.ts.map +1 -1
- package/dist/plugins/runAction/index.js +12 -1
- package/dist/plugins/runAction/schemas.js +1 -1
- package/dist/types/connections.d.ts +2 -2
- package/dist/types/connections.d.ts.map +1 -1
- package/dist/types/connections.js +5 -3
- package/dist/types/errors.d.ts +37 -0
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/errors.js +18 -0
- package/dist/types/properties.js +1 -1
- package/dist/types/sdk.d.ts +7 -0
- package/dist/types/sdk.d.ts.map +1 -1
- package/dist/types/sdk.js +20 -0
- package/dist/utils/open-approval.d.ts +2 -0
- package/dist/utils/open-approval.d.ts.map +1 -0
- package/dist/utils/open-approval.js +13 -0
- package/dist/utils/open-url.d.ts +3 -0
- package/dist/utils/open-url.d.ts.map +1 -0
- package/dist/utils/open-url.js +72 -0
- package/package.json +3 -1
package/dist/api/client.js
CHANGED
|
@@ -11,8 +11,21 @@ import { resolveAuthToken, invalidateCredentialsToken, isCliLoginAvailable, } fr
|
|
|
11
11
|
import { getZapierBaseUrl } from "../utils/url-utils";
|
|
12
12
|
import { sleep, calculateExponentialBackoffMs } from "../utils/retry-utils";
|
|
13
13
|
import { isPlainObject } from "../utils/type-guard-utils";
|
|
14
|
-
import { ZapierApiError, ZapierAuthenticationError, ZapierValidationError, ZapierNotFoundError, ZapierRateLimitError, } from "../types/errors";
|
|
15
|
-
import { ZAPIER_MAX_NETWORK_RETRIES, ZAPIER_MAX_NETWORK_RETRY_DELAY_MS, } from "../constants";
|
|
14
|
+
import { ZapierApiError, ZapierApprovalError, ZapierAuthenticationError, ZapierTimeoutError, ZapierValidationError, ZapierNotFoundError, ZapierRateLimitError, } from "../types/errors";
|
|
15
|
+
import { ZAPIER_MAX_NETWORK_RETRIES, ZAPIER_MAX_NETWORK_RETRY_DELAY_MS, getZapierIsInteractive, getZapierApprovalMode, DEFAULT_APPROVAL_TIMEOUT_MS, DEFAULT_MAX_APPROVAL_RETRIES, } from "../constants";
|
|
16
|
+
import { openApproval } from "../utils/open-approval";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
const ApprovalStatusSchema = z.enum(["pending_approval", "approved", "denied"]);
|
|
19
|
+
const CreateApprovalResponseSchema = z.object({
|
|
20
|
+
status: ApprovalStatusSchema,
|
|
21
|
+
approval_id: z.string(),
|
|
22
|
+
approval_url: z.string().url(),
|
|
23
|
+
poll_url: z.string().url(),
|
|
24
|
+
});
|
|
25
|
+
const PollApprovalResponseSchema = z.object({
|
|
26
|
+
status: ApprovalStatusSchema,
|
|
27
|
+
approval_id: z.string(),
|
|
28
|
+
});
|
|
16
29
|
/**
|
|
17
30
|
* Parse rate limit information from response headers
|
|
18
31
|
*/
|
|
@@ -85,14 +98,23 @@ const pathConfig = {
|
|
|
85
98
|
class ZapierApiClient {
|
|
86
99
|
constructor(options) {
|
|
87
100
|
this.options = options;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Perform a request against an already-resolved URL.
|
|
103
|
+
*
|
|
104
|
+
* Does auth, header merging, and 429 retry — all the cross-cutting
|
|
105
|
+
* concerns that every Zapier-bound HTTP call needs. Callers that have a
|
|
106
|
+
* path (e.g. `/relay/...`) should use `rawFetch` instead, which does
|
|
107
|
+
* path → URL resolution and delegates here.
|
|
108
|
+
*
|
|
109
|
+
* Exposed as a separate helper so call sites with a server-supplied
|
|
110
|
+
* absolute URL (e.g. an approval poll URL) can still share the same
|
|
111
|
+
* auth/retry pipeline instead of reaching for `this.options.fetch`
|
|
112
|
+
* directly and drifting.
|
|
113
|
+
*/
|
|
114
|
+
this.rawFetchUrl = async (url, init, pathConfig) => {
|
|
92
115
|
if (init?.body && (isPlainObject(init.body) || Array.isArray(init.body))) {
|
|
93
116
|
init.body = JSON.stringify(init.body);
|
|
94
117
|
}
|
|
95
|
-
const { url, pathConfig } = this.buildUrl(path, init?.searchParams);
|
|
96
118
|
const builtHeaders = await this.buildHeaders(init, pathConfig);
|
|
97
119
|
const inputHeaders = new Headers(init?.headers ?? {});
|
|
98
120
|
const mergedHeaders = new Headers();
|
|
@@ -102,44 +124,147 @@ class ZapierApiClient {
|
|
|
102
124
|
inputHeaders.forEach((value, key) => {
|
|
103
125
|
mergedHeaders.set(key, value);
|
|
104
126
|
});
|
|
105
|
-
// Retry loop for rate limiting (429)
|
|
106
127
|
let retries = 0;
|
|
128
|
+
// Retry loop for rate limiting (429)
|
|
107
129
|
while (true) {
|
|
108
130
|
const response = await this.options.fetch(url, {
|
|
109
131
|
...init,
|
|
110
132
|
headers: mergedHeaders,
|
|
111
133
|
});
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
retries,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
retries++;
|
|
128
|
-
// Emit retry event
|
|
129
|
-
this.emitEvent("api:rate_limit_retry", {
|
|
130
|
-
retry: retries,
|
|
131
|
-
maxNetworkRetries: this.maxNetworkRetries,
|
|
132
|
-
delayMs,
|
|
133
|
-
path,
|
|
134
|
-
method: init?.method ?? "GET",
|
|
134
|
+
if (response.status !== 429) {
|
|
135
|
+
return response;
|
|
136
|
+
}
|
|
137
|
+
// Calculate delay: prefer Retry-After/X-RateLimit-Reset, fall back to exponential backoff
|
|
138
|
+
const rateLimitInfo = parseRateLimitHeaders(response);
|
|
139
|
+
const delayMs = rateLimitInfo.retryAfterMs ??
|
|
140
|
+
calculateExponentialBackoffMs(retries + 1);
|
|
141
|
+
// If delay exceeds maxNetworkRetryDelayMs or retries exhausted, throw immediately
|
|
142
|
+
if (delayMs > this.maxNetworkRetryDelayMs ||
|
|
143
|
+
retries >= this.maxNetworkRetries) {
|
|
144
|
+
throw new ZapierRateLimitError("Rate limited", {
|
|
145
|
+
statusCode: 429,
|
|
135
146
|
rateLimit: rateLimitInfo,
|
|
147
|
+
retries,
|
|
136
148
|
});
|
|
137
|
-
// Wait before retrying
|
|
138
|
-
await sleep(delayMs);
|
|
139
|
-
continue;
|
|
140
149
|
}
|
|
141
|
-
|
|
150
|
+
retries++;
|
|
151
|
+
// Emit retry event
|
|
152
|
+
this.emitEvent("api:rate_limit_retry", {
|
|
153
|
+
retry: retries,
|
|
154
|
+
maxNetworkRetries: this.maxNetworkRetries,
|
|
155
|
+
delayMs,
|
|
156
|
+
path: url,
|
|
157
|
+
method: init?.method ?? "GET",
|
|
158
|
+
rateLimit: rateLimitInfo,
|
|
159
|
+
});
|
|
160
|
+
await sleep(delayMs);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Perform a request with auth, header merging, and rate-limit (429) retries.
|
|
165
|
+
* Does NOT handle 403 approval_required — that's routed by `fetch`.
|
|
166
|
+
*/
|
|
167
|
+
this.rawFetch = async (path, init) => {
|
|
168
|
+
if (!path.startsWith("/")) {
|
|
169
|
+
throw new ZapierValidationError(`fetch expects a path starting with '/', got: ${path}`);
|
|
142
170
|
}
|
|
171
|
+
const { url, pathConfig } = this.buildUrl(path, init?.searchParams);
|
|
172
|
+
return this.rawFetchUrl(url, init, pathConfig);
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Approval-aware HTTP fetch.
|
|
176
|
+
*
|
|
177
|
+
* Wraps `rawFetch` with the backend's just-in-time approval protocol. The
|
|
178
|
+
* backend signals approval state via a 403 response with an
|
|
179
|
+
* `x-zapier-error-type` header:
|
|
180
|
+
*
|
|
181
|
+
* - `request_denied_by_policy` → a policy rule permanently blocks the
|
|
182
|
+
* request; no human can approve it. Throw.
|
|
183
|
+
* - `approval_required` → the request needs human approval; the
|
|
184
|
+
* SDK creates an approval, opens the URL
|
|
185
|
+
* (poll mode), waits for resolution, and
|
|
186
|
+
* retries the original request.
|
|
187
|
+
* - anything else → not our concern, pass through.
|
|
188
|
+
*
|
|
189
|
+
* The retry loop exists because a single user action can legitimately
|
|
190
|
+
* require multiple sequential approvals (e.g. policies that approve one
|
|
191
|
+
* step at a time). Each iteration is either a first attempt or a post-
|
|
192
|
+
* approval retry; `maxApprovalRetries` bounds the loop as a runaway-loop
|
|
193
|
+
* safeguard.
|
|
194
|
+
*
|
|
195
|
+
* Loop accounting: `attempt` counts rawFetch calls, not approval rounds.
|
|
196
|
+
* With `maxRetries = N` we perform up to `N + 1` rawFetch calls and at
|
|
197
|
+
* most `N` approval rounds. The `attempt === maxRetries` guard ensures we
|
|
198
|
+
* don't run an (N+1)th approval round we'll never consume — if the final
|
|
199
|
+
* retry still returns `approval_required`, we break out and throw
|
|
200
|
+
* `max_retries_exceeded` instead.
|
|
201
|
+
*/
|
|
202
|
+
this.fetch = async (path, init) => {
|
|
203
|
+
const maxRetries = this.options.maxApprovalRetries ?? DEFAULT_MAX_APPROVAL_RETRIES;
|
|
204
|
+
// `<= maxRetries` gives us one initial attempt plus `maxRetries` post-
|
|
205
|
+
// approval retries. See "Loop accounting" in the jsdoc above.
|
|
206
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
207
|
+
const response = await this.rawFetch(path, init);
|
|
208
|
+
// Happy path and non-403 errors: hand the response back untouched. Any
|
|
209
|
+
// other error handling (e.g. 4xx envelope parsing) lives downstream.
|
|
210
|
+
if (response.status !== 403)
|
|
211
|
+
return response;
|
|
212
|
+
// All approval-related responses are 403 + a typed header. Classify the
|
|
213
|
+
// kind of 403 we got exactly once, here — no other call site should be
|
|
214
|
+
// reading `x-zapier-error-type`.
|
|
215
|
+
const errorType = response.headers.get("x-zapier-error-type");
|
|
216
|
+
// Terminal policy denial: a rule blocked the request and no approval
|
|
217
|
+
// can unblock it. Bubble the server-provided detail message so callers
|
|
218
|
+
// surface the actual reason ("Spend over $X requires finance approval",
|
|
219
|
+
// etc.) rather than a generic one.
|
|
220
|
+
if (errorType === "request_denied_by_policy") {
|
|
221
|
+
const { data } = await this.parseResult(response);
|
|
222
|
+
const { message, errors } = this.parseErrorResponse({
|
|
223
|
+
status: response.status,
|
|
224
|
+
statusText: response.statusText,
|
|
225
|
+
data,
|
|
226
|
+
});
|
|
227
|
+
throw new ZapierApprovalError(message || "Request explicitly denied by policy", {
|
|
228
|
+
status: "policy_denied",
|
|
229
|
+
statusCode: response.status,
|
|
230
|
+
errors,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
// 403 with no `approval_required` header isn't part of the approval
|
|
234
|
+
// protocol — could be auth scope, per-endpoint perms, etc. Pass it
|
|
235
|
+
// through so the caller's normal error handling sees it.
|
|
236
|
+
if (errorType !== "approval_required")
|
|
237
|
+
return response;
|
|
238
|
+
// We got `approval_required`. If this was our last scheduled attempt,
|
|
239
|
+
// don't start an approval round we can't retry — fall out of the loop
|
|
240
|
+
// and throw `max_retries_exceeded` below.
|
|
241
|
+
if (attempt === maxRetries)
|
|
242
|
+
break;
|
|
243
|
+
// Approval requires opening a URL in a browser. If the caller's session
|
|
244
|
+
// can't do that (CI, server, headless container), surface a distinct
|
|
245
|
+
// status so callers can branch on "retry interactively or use
|
|
246
|
+
// approvalMode: 'fail'" vs. a real policy denial.
|
|
247
|
+
const isInteractive = this.options.isInteractive ?? getZapierIsInteractive();
|
|
248
|
+
if (!isInteractive) {
|
|
249
|
+
throw new ZapierApprovalError("Approval required but session is not interactive", { status: "approval_required" });
|
|
250
|
+
}
|
|
251
|
+
// `approvalContext` is a builder, not a value, because the SDK may
|
|
252
|
+
// retry the same request across multiple approval rounds and each POST
|
|
253
|
+
// to /approvals needs a freshly-built context (timestamps, etc.).
|
|
254
|
+
// Missing context is a programmer error — this code path is only
|
|
255
|
+
// reachable for callers that opted into approval-capable requests.
|
|
256
|
+
if (!init?.approvalContext) {
|
|
257
|
+
throw new ZapierApiError(`Received 403 approval_required for ${path}, but the caller did not provide an approvalContext builder. Every approval-capable request must pass approvalContext so the SDK can create the approval with the correct policy context.`, { statusCode: 403 });
|
|
258
|
+
}
|
|
259
|
+
// Create the approval, open the URL (poll mode) or throw with the URL
|
|
260
|
+
// attached (fail mode), and wait for terminal resolution. Returns only
|
|
261
|
+
// on "approved" — every other outcome throws from inside the helper.
|
|
262
|
+
// On return, loop back to retry the original request.
|
|
263
|
+
await this.runOneApprovalRound(init.approvalContext);
|
|
264
|
+
}
|
|
265
|
+
// Only reachable when the final attempt returned `approval_required`
|
|
266
|
+
// (see the `break` above). Every other terminal outcome throws inline.
|
|
267
|
+
throw new ZapierApprovalError(`Exceeded maximum approval retries (${maxRetries}) for ${path}`, { status: "max_retries_exceeded" });
|
|
143
268
|
};
|
|
144
269
|
this.get = async (path, options = {}) => {
|
|
145
270
|
return this.fetchJson("GET", path, undefined, options);
|
|
@@ -480,6 +605,155 @@ class ZapierApiClient {
|
|
|
480
605
|
}
|
|
481
606
|
return result;
|
|
482
607
|
}
|
|
608
|
+
/**
|
|
609
|
+
* Run a single approval round: create the approval, open the URL (poll mode)
|
|
610
|
+
* or throw (fail mode), poll until resolved, and emit events. Throws on
|
|
611
|
+
* denied/timeout/unexpected status. Returns on approved.
|
|
612
|
+
*/
|
|
613
|
+
async runOneApprovalRound(buildContext) {
|
|
614
|
+
const context = buildContext();
|
|
615
|
+
// Route through rawFetch so the approval POST shares auth + 429 retry.
|
|
616
|
+
let approvalResponse;
|
|
617
|
+
try {
|
|
618
|
+
approvalResponse = await this.rawFetch("/api/v0/approvals", {
|
|
619
|
+
method: "POST",
|
|
620
|
+
headers: {
|
|
621
|
+
"Content-Type": "application/json",
|
|
622
|
+
Accept: "application/json",
|
|
623
|
+
},
|
|
624
|
+
body: JSON.stringify({ context }),
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
catch (err) {
|
|
628
|
+
throw new ZapierApiError("Failed to create approval request", {
|
|
629
|
+
statusCode: 0,
|
|
630
|
+
cause: err,
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
if (!approvalResponse.ok) {
|
|
634
|
+
const body = await approvalResponse.text().catch(() => undefined);
|
|
635
|
+
throw new ZapierApiError(`Failed to create approval request: ${approvalResponse.status}`, { statusCode: approvalResponse.status, response: body });
|
|
636
|
+
}
|
|
637
|
+
let body;
|
|
638
|
+
try {
|
|
639
|
+
body = await approvalResponse.text();
|
|
640
|
+
}
|
|
641
|
+
catch (err) {
|
|
642
|
+
throw new ZapierApiError("Failed to read approval response body", {
|
|
643
|
+
statusCode: approvalResponse.status,
|
|
644
|
+
cause: err,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
let approval;
|
|
648
|
+
try {
|
|
649
|
+
approval = CreateApprovalResponseSchema.parse(JSON.parse(body));
|
|
650
|
+
}
|
|
651
|
+
catch (err) {
|
|
652
|
+
throw new ZapierApiError(`Failed to parse approval response: ${body}`, {
|
|
653
|
+
statusCode: approvalResponse.status,
|
|
654
|
+
cause: err,
|
|
655
|
+
response: body,
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
// Origin-pin server-supplied URLs before we attach the bearer token
|
|
659
|
+
// (poll_url) or open them in the user's browser (approval_url). Without
|
|
660
|
+
// this, a compromised or buggy sdkapi response could redirect the SDK to
|
|
661
|
+
// an attacker-controlled host and leak the auth token.
|
|
662
|
+
const sdkapiOrigin = new URL(this.buildUrl("/api/v0/approvals").url).origin;
|
|
663
|
+
const browserOrigin = getZapierBaseUrl(this.options.baseUrl) ?? sdkapiOrigin;
|
|
664
|
+
const assertApprovalOrigin = (url, expectedOrigin, label) => {
|
|
665
|
+
let parsed;
|
|
666
|
+
try {
|
|
667
|
+
parsed = new URL(url);
|
|
668
|
+
}
|
|
669
|
+
catch {
|
|
670
|
+
throw new ZapierApiError(`Invalid approval ${label}: ${url}`, {
|
|
671
|
+
statusCode: approvalResponse.status,
|
|
672
|
+
response: body,
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
if (parsed.origin !== expectedOrigin) {
|
|
676
|
+
throw new ZapierApiError(`Approval ${label} origin ${parsed.origin} does not match expected ${expectedOrigin}`, { statusCode: approvalResponse.status, response: body });
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
assertApprovalOrigin(approval.poll_url, sdkapiOrigin, "poll_url");
|
|
680
|
+
assertApprovalOrigin(approval.approval_url, browserOrigin, "approval_url");
|
|
681
|
+
this.emitEvent("approval:required", {
|
|
682
|
+
approvalId: approval.approval_id,
|
|
683
|
+
approvalUrl: approval.approval_url,
|
|
684
|
+
});
|
|
685
|
+
const approvalMode = this.options.approvalMode ?? getZapierApprovalMode();
|
|
686
|
+
if (approvalMode === "fail") {
|
|
687
|
+
throw new ZapierApprovalError("This request requires approval.", {
|
|
688
|
+
approvalId: approval.approval_id,
|
|
689
|
+
approvalUrl: approval.approval_url,
|
|
690
|
+
pollUrl: approval.poll_url,
|
|
691
|
+
status: "pending",
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
// approvalMode: "poll" (default) — open browser and poll
|
|
695
|
+
await openApproval(approval.approval_url);
|
|
696
|
+
const timeoutMs = this.options.approvalTimeoutMs ?? DEFAULT_APPROVAL_TIMEOUT_MS;
|
|
697
|
+
let rawPollResult;
|
|
698
|
+
try {
|
|
699
|
+
rawPollResult = await pollUntilComplete({
|
|
700
|
+
// poll_url is an absolute URL supplied by the server, so we use
|
|
701
|
+
// rawFetchUrl directly (skipping path resolution) but still share
|
|
702
|
+
// auth + interactive-header + 429-retry with the rest of the SDK.
|
|
703
|
+
fetchPoll: () => this.rawFetchUrl(approval.poll_url, {
|
|
704
|
+
method: "GET",
|
|
705
|
+
headers: { Accept: "application/json" },
|
|
706
|
+
}),
|
|
707
|
+
timeoutMs,
|
|
708
|
+
isPending: (body) => {
|
|
709
|
+
const parsed = PollApprovalResponseSchema.safeParse(body);
|
|
710
|
+
return parsed.success && parsed.data.status === "pending_approval";
|
|
711
|
+
},
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
catch (err) {
|
|
715
|
+
if (!(err instanceof ZapierTimeoutError)) {
|
|
716
|
+
throw err;
|
|
717
|
+
}
|
|
718
|
+
this.emitEvent("approval:timeout", {
|
|
719
|
+
approvalId: approval.approval_id,
|
|
720
|
+
});
|
|
721
|
+
throw new ZapierApprovalError(`Approval timed out after ${timeoutMs / 1000} seconds`, {
|
|
722
|
+
approvalId: approval.approval_id,
|
|
723
|
+
approvalUrl: approval.approval_url,
|
|
724
|
+
pollUrl: approval.poll_url,
|
|
725
|
+
status: "timeout",
|
|
726
|
+
cause: err,
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
const pollParse = PollApprovalResponseSchema.safeParse(rawPollResult);
|
|
730
|
+
if (!pollParse.success) {
|
|
731
|
+
const bodyPreview = typeof rawPollResult === "string"
|
|
732
|
+
? rawPollResult
|
|
733
|
+
: JSON.stringify(rawPollResult);
|
|
734
|
+
throw new ZapierApiError(`Failed to parse approval poll response: ${bodyPreview}`, {
|
|
735
|
+
statusCode: 0,
|
|
736
|
+
cause: pollParse.error,
|
|
737
|
+
response: rawPollResult,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
const pollResult = pollParse.data;
|
|
741
|
+
if (pollResult.status === "denied") {
|
|
742
|
+
this.emitEvent("approval:denied", {
|
|
743
|
+
approvalId: approval.approval_id,
|
|
744
|
+
});
|
|
745
|
+
throw new ZapierApprovalError("Request denied by user", {
|
|
746
|
+
approvalId: approval.approval_id,
|
|
747
|
+
status: "denied",
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
if (pollResult.status !== "approved") {
|
|
751
|
+
throw new ZapierApiError(`Unexpected approval status received: ${pollResult.status}`);
|
|
752
|
+
}
|
|
753
|
+
this.emitEvent("approval:approved", {
|
|
754
|
+
approvalId: approval.approval_id,
|
|
755
|
+
});
|
|
756
|
+
}
|
|
483
757
|
}
|
|
484
758
|
export const createZapierApi = (options) => {
|
|
485
759
|
const { debug = false, fetch: originalFetch = globalThis.fetch } = options;
|
package/dist/api/types.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { ConnectionSchema, ConnectionsResponseSchema } from "@zapier/zapier
|
|
|
12
12
|
import type { ImplementationMetaSchema, ImplementationsMetaResponseSchema } from "@zapier/zapier-sdk-core/v0/schemas/implementations";
|
|
13
13
|
import type { SdkEvent } from "../types/events";
|
|
14
14
|
import type { Credentials } from "../types/credentials";
|
|
15
|
+
import type { RequestContext } from "@zapier/policy-context";
|
|
15
16
|
import type { z } from "zod";
|
|
16
17
|
import type { NeedChoicesSchema, NeedSchema, ActionLinksSchema, ActionPermissionsSchema, ActionSchema, ChoiceSchema, FieldSchema, ActionExecutionResultSchema, ActionFieldChoiceSchema, ActionFieldSchema, UserProfileSchema, AppSchema, NeedsRequestSchema, NeedsResponseSchema, ImplementationSchema, ImplementationsResponseSchema, ServiceSchema, ServicesResponseSchema, NeedChoicesRequestSchema, NeedChoicesResponseSchema, NeedChoicesResponseMetaSchema, NeedChoicesResponseLinksSchema } from "./schemas";
|
|
17
18
|
export interface ApiClientOptions {
|
|
@@ -38,6 +39,27 @@ export interface ApiClientOptions {
|
|
|
38
39
|
* Default is 60000 (60 seconds).
|
|
39
40
|
*/
|
|
40
41
|
maxNetworkRetryDelayMs?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Whether this session is interactive (user can visit approval URLs).
|
|
44
|
+
*/
|
|
45
|
+
isInteractive?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Controls how the approval flow is handled.
|
|
48
|
+
* - "poll": (default) Opens the approval URL in a browser and polls until resolved.
|
|
49
|
+
* - "fail": Creates the approval and throws a ZapierApprovalError immediately with the approval URL.
|
|
50
|
+
*/
|
|
51
|
+
approvalMode?: "poll" | "fail";
|
|
52
|
+
/**
|
|
53
|
+
* Timeout in ms for approval polling. Default: 600000 (10 minutes).
|
|
54
|
+
*/
|
|
55
|
+
approvalTimeoutMs?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Maximum number of sequential approval rounds for a single request before
|
|
58
|
+
* giving up. A single request can legitimately require multiple approvals
|
|
59
|
+
* (one per gating policy); this cap prevents a runaway loop if the server
|
|
60
|
+
* keeps returning approval_required. Default: 2.
|
|
61
|
+
*/
|
|
62
|
+
maxApprovalRetries?: number;
|
|
41
63
|
}
|
|
42
64
|
export interface ApiClient {
|
|
43
65
|
get: <T = unknown>(path: string, options?: RequestOptions) => Promise<T>;
|
|
@@ -49,6 +71,7 @@ export interface ApiClient {
|
|
|
49
71
|
fetch: (path: string, init?: RequestInit & {
|
|
50
72
|
searchParams?: Record<string, string>;
|
|
51
73
|
authRequired?: boolean;
|
|
74
|
+
approvalContext?: () => RequestContext;
|
|
52
75
|
}) => Promise<Response>;
|
|
53
76
|
}
|
|
54
77
|
export interface RequestOptions {
|
|
@@ -66,6 +89,12 @@ export interface RequestOptions {
|
|
|
66
89
|
statusText: string;
|
|
67
90
|
data: unknown;
|
|
68
91
|
}) => Error | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* Builds the policy context for this request if it triggers the approval
|
|
94
|
+
* flow. Callers that may receive a 403 `approval_required` response MUST
|
|
95
|
+
* provide this so the SDK can create an approval with the correct context.
|
|
96
|
+
*/
|
|
97
|
+
approvalContext?: () => RequestContext;
|
|
69
98
|
}
|
|
70
99
|
export interface PollOptions extends RequestOptions {
|
|
71
100
|
initialDelay?: number;
|
package/dist/api/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EACV,gBAAgB,EAChB,yBAAyB,EAC1B,MAAM,gDAAgD,CAAC;AACxD,OAAO,KAAK,EACV,wBAAwB,EACxB,iCAAiC,EAClC,MAAM,oDAAoD,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EACV,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EACjB,uBAAuB,EACvB,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,2BAA2B,EAC3B,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,EACjB,SAAS,EACT,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,6BAA6B,EAC7B,aAAa,EACb,sBAAsB,EACtB,wBAAwB,EACxB,yBAAyB,EACzB,6BAA6B,EAC7B,8BAA8B,EAC/B,MAAM,WAAW,CAAC;AAMnB,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IACpC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"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"}
|
package/dist/constants.d.ts
CHANGED
|
@@ -24,4 +24,29 @@ export declare const DEFAULT_ACTION_TIMEOUT_MS = 180000;
|
|
|
24
24
|
*/
|
|
25
25
|
export declare const ZAPIER_MAX_NETWORK_RETRIES: number;
|
|
26
26
|
export declare const ZAPIER_MAX_NETWORK_RETRY_DELAY_MS: number;
|
|
27
|
+
/**
|
|
28
|
+
* Whether this session is interactive (user can visit approval URLs).
|
|
29
|
+
*
|
|
30
|
+
* Read lazily so tests can set the env var after module import.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getZapierIsInteractive(): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Approval flow behavior from environment variable.
|
|
35
|
+
* - "poll": (default) Opens the approval URL in a browser and polls until resolved.
|
|
36
|
+
* - "fail": Creates the approval and throws immediately with the approval URL.
|
|
37
|
+
*
|
|
38
|
+
* Read lazily so tests can set the env var after module import.
|
|
39
|
+
*/
|
|
40
|
+
export declare function getZapierApprovalMode(): "poll" | "fail" | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Default timeout for approval polling (10 minutes)
|
|
43
|
+
*/
|
|
44
|
+
export declare const DEFAULT_APPROVAL_TIMEOUT_MS: number;
|
|
45
|
+
/**
|
|
46
|
+
* Default cap on chained approval retries for a single request. Multiple
|
|
47
|
+
* sequential approvals can occur when more than one policy gates the same
|
|
48
|
+
* action; this cap prevents a runaway loop if the server keeps returning
|
|
49
|
+
* approval_required after a successful approval.
|
|
50
|
+
*/
|
|
51
|
+
export declare const DEFAULT_MAX_APPROVAL_RETRIES = 2;
|
|
27
52
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,eAAO,MAAM,eAAe,QACsC,CAAC;AAEnE;;GAEG;AACH,eAAO,MAAM,cAAc,QAAQ,CAAC;AAEpC;;GAEG;AACH,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC;;GAEG;AACH,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAejD;;GAEG;AACH,eAAO,MAAM,0BAA0B,QACY,CAAC;AACpD,eAAO,MAAM,iCAAiC,QACiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,eAAO,MAAM,eAAe,QACsC,CAAC;AAEnE;;GAEG;AACH,eAAO,MAAM,cAAc,QAAQ,CAAC;AAEpC;;GAEG;AACH,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC;;GAEG;AACH,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAejD;;GAEG;AACH,eAAO,MAAM,0BAA0B,QACY,CAAC;AACpD,eAAO,MAAM,iCAAiC,QACiB,CAAC;AAEhE;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,MAAM,GAAG,SAAS,CAInE;AAED;;GAEG;AACH,eAAO,MAAM,2BAA2B,QAAiB,CAAC;AAE1D;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,IAAI,CAAC"}
|
package/dist/constants.js
CHANGED
|
@@ -35,3 +35,35 @@ function parseIntEnvVar(name) {
|
|
|
35
35
|
*/
|
|
36
36
|
export const ZAPIER_MAX_NETWORK_RETRIES = parseIntEnvVar("ZAPIER_MAX_NETWORK_RETRIES") ?? 3;
|
|
37
37
|
export const ZAPIER_MAX_NETWORK_RETRY_DELAY_MS = parseIntEnvVar("ZAPIER_MAX_NETWORK_RETRY_DELAY_MS") ?? 60000;
|
|
38
|
+
/**
|
|
39
|
+
* Whether this session is interactive (user can visit approval URLs).
|
|
40
|
+
*
|
|
41
|
+
* Read lazily so tests can set the env var after module import.
|
|
42
|
+
*/
|
|
43
|
+
export function getZapierIsInteractive() {
|
|
44
|
+
return globalThis.process?.env?.ZAPIER_IS_INTERACTIVE === "true";
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Approval flow behavior from environment variable.
|
|
48
|
+
* - "poll": (default) Opens the approval URL in a browser and polls until resolved.
|
|
49
|
+
* - "fail": Creates the approval and throws immediately with the approval URL.
|
|
50
|
+
*
|
|
51
|
+
* Read lazily so tests can set the env var after module import.
|
|
52
|
+
*/
|
|
53
|
+
export function getZapierApprovalMode() {
|
|
54
|
+
const value = globalThis.process?.env?.ZAPIER_APPROVAL_MODE;
|
|
55
|
+
if (value === "poll" || value === "fail")
|
|
56
|
+
return value;
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Default timeout for approval polling (10 minutes)
|
|
61
|
+
*/
|
|
62
|
+
export const DEFAULT_APPROVAL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
63
|
+
/**
|
|
64
|
+
* Default cap on chained approval retries for a single request. Multiple
|
|
65
|
+
* sequential approvals can occur when more than one policy gates the same
|
|
66
|
+
* action; this cap prevents a runaway loop if the server keeps returning
|
|
67
|
+
* approval_required after a successful approval.
|
|
68
|
+
*/
|
|
69
|
+
export const DEFAULT_MAX_APPROVAL_RETRIES = 2;
|