@executor-js/plugin-openapi 1.4.33 → 1.5.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/dist/AddOpenApiSource-7M52SRUX.js +893 -0
- package/dist/AddOpenApiSource-7M52SRUX.js.map +1 -0
- package/dist/EditOpenApiSource-WTAMRJUK.js +68 -0
- package/dist/EditOpenApiSource-WTAMRJUK.js.map +1 -0
- package/dist/OpenApiAccountsPanel-3VJJXNQF.js +112 -0
- package/dist/OpenApiAccountsPanel-3VJJXNQF.js.map +1 -0
- package/dist/api/group.d.ts +104 -225
- package/dist/api/index.d.ts +127 -271
- package/dist/{chunk-BB5IAKRG.js → chunk-AQ7JDDRM.js} +12 -2
- package/dist/chunk-AQ7JDDRM.js.map +1 -0
- package/dist/chunk-BSLE6HCE.js +181 -0
- package/dist/chunk-BSLE6HCE.js.map +1 -0
- package/dist/chunk-MZWZQ24W.js +226 -0
- package/dist/chunk-MZWZQ24W.js.map +1 -0
- package/dist/chunk-OSIFYIQP.js +623 -0
- package/dist/chunk-OSIFYIQP.js.map +1 -0
- package/dist/chunk-QSSRVK6M.js +139 -0
- package/dist/chunk-QSSRVK6M.js.map +1 -0
- package/dist/chunk-V7VCHYOY.js +1544 -0
- package/dist/chunk-V7VCHYOY.js.map +1 -0
- package/dist/{chunk-AN4HJFNP.js → chunk-YVRI7KRC.js} +162 -186
- package/dist/chunk-YVRI7KRC.js.map +1 -0
- package/dist/client.js +9 -8
- package/dist/client.js.map +1 -1
- package/dist/core.js +28 -11
- package/dist/index.js +11 -4
- package/dist/react/AddOpenApiSource.d.ts +2 -13
- package/dist/react/GoogleProductPicker.d.ts +9 -0
- package/dist/react/OpenApiAccountsPanel.d.ts +6 -0
- package/dist/react/OpenApiSourceDetailsFields.d.ts +3 -2
- package/dist/react/atoms.d.ts +177 -192
- package/dist/react/auth-method-config.d.ts +15 -0
- package/dist/react/client.d.ts +103 -224
- package/dist/react/index.d.ts +2 -2
- package/dist/react/source-plugin.d.ts +2 -2
- package/dist/sdk/config.d.ts +75 -0
- package/dist/sdk/configure.test.d.ts +1 -0
- package/dist/sdk/describe-auth-methods.test.d.ts +1 -0
- package/dist/sdk/errors.d.ts +4 -6
- package/dist/sdk/extract.d.ts +7 -5
- package/dist/sdk/google-bundle.test.d.ts +1 -0
- package/dist/sdk/google-discovery.d.ts +43 -0
- package/dist/sdk/google-discovery.test.d.ts +1 -0
- package/dist/sdk/google-oauth-batches.d.ts +13 -0
- package/dist/sdk/google-oauth-batches.test.d.ts +1 -0
- package/dist/sdk/google-oauth-scopes.d.ts +3 -0
- package/dist/sdk/google-oauth-scopes.test.d.ts +1 -0
- package/dist/sdk/google-presets.d.ts +16 -0
- package/dist/sdk/google-presets.test.d.ts +1 -0
- package/dist/sdk/google-product-picker-scopes.test.d.ts +1 -0
- package/dist/sdk/index.d.ts +7 -5
- package/dist/sdk/invoke.d.ts +6 -9
- package/dist/sdk/openapi-utils.d.ts +1 -0
- package/dist/sdk/plugin.d.ts +74 -231
- package/dist/sdk/presets.d.ts +2 -1
- package/dist/sdk/preview.d.ts +20 -15
- package/dist/sdk/query-serialization.test.d.ts +1 -0
- package/dist/sdk/store.d.ts +14 -41
- package/dist/sdk/types.d.ts +23 -51
- package/dist/testing/index.d.ts +49 -38
- package/dist/testing.js +46 -18
- package/dist/testing.js.map +1 -1
- package/package.json +6 -4
- package/dist/AddOpenApiSource-NSCULGTM.js +0 -19
- package/dist/AddOpenApiSource-NSCULGTM.js.map +0 -1
- package/dist/EditOpenApiSource-MV7NYTRP.js +0 -774
- package/dist/EditOpenApiSource-MV7NYTRP.js.map +0 -1
- package/dist/OpenApiSourceSummary-7JBS7PUV.js +0 -122
- package/dist/OpenApiSourceSummary-7JBS7PUV.js.map +0 -1
- package/dist/chunk-2ZKKZYZH.js +0 -1181
- package/dist/chunk-2ZKKZYZH.js.map +0 -1
- package/dist/chunk-AN4HJFNP.js.map +0 -1
- package/dist/chunk-BB5IAKRG.js.map +0 -1
- package/dist/chunk-PRVJDE43.js +0 -2101
- package/dist/chunk-PRVJDE43.js.map +0 -1
- package/dist/chunk-X5JX3KTA.js +0 -201
- package/dist/chunk-X5JX3KTA.js.map +0 -1
- package/dist/react/OpenApiSourceSummary.d.ts +0 -5
- package/dist/sdk/credential-status.d.ts +0 -23
- package/dist/sdk/source-contracts.d.ts +0 -55
- /package/dist/{sdk/credential-status.test.d.ts → react/auth-method-config.test.d.ts} +0 -0
|
@@ -0,0 +1,893 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compactGoogleOAuthScopes,
|
|
3
|
+
isGoogleDiscoveryUrl
|
|
4
|
+
} from "./chunk-OSIFYIQP.js";
|
|
5
|
+
import {
|
|
6
|
+
openApiPresets
|
|
7
|
+
} from "./chunk-AQ7JDDRM.js";
|
|
8
|
+
import {
|
|
9
|
+
authenticationFromEditorValue,
|
|
10
|
+
editorValueFromAuthentication
|
|
11
|
+
} from "./chunk-QSSRVK6M.js";
|
|
12
|
+
import {
|
|
13
|
+
GOOGLE_BUNDLE_PRESET_ID,
|
|
14
|
+
googleOAuthConsentScopesForPreset,
|
|
15
|
+
googleOpenApiPresets
|
|
16
|
+
} from "./chunk-MZWZQ24W.js";
|
|
17
|
+
import {
|
|
18
|
+
addOpenApiSpec,
|
|
19
|
+
previewOpenApiSpec
|
|
20
|
+
} from "./chunk-BSLE6HCE.js";
|
|
21
|
+
import {
|
|
22
|
+
TOKEN_VARIABLE,
|
|
23
|
+
expandServerUrlOptions,
|
|
24
|
+
variable
|
|
25
|
+
} from "./chunk-YVRI7KRC.js";
|
|
26
|
+
|
|
27
|
+
// src/react/AddOpenApiSource.tsx
|
|
28
|
+
import { useCallback, useEffect, useMemo as useMemo2, useRef, useState as useState2 } from "react";
|
|
29
|
+
import { useAtomSet, useAtomValue } from "@effect/atom-react";
|
|
30
|
+
import { Link } from "@tanstack/react-router";
|
|
31
|
+
import * as Effect from "effect/Effect";
|
|
32
|
+
import * as Exit from "effect/Exit";
|
|
33
|
+
import * as Option from "effect/Option";
|
|
34
|
+
import * as Predicate from "effect/Predicate";
|
|
35
|
+
import * as Schema from "effect/Schema";
|
|
36
|
+
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
|
|
37
|
+
import {
|
|
38
|
+
AuthTemplateSlug
|
|
39
|
+
} from "@executor-js/sdk/shared";
|
|
40
|
+
import { integrationsOptimisticAtom } from "@executor-js/react/api/atoms";
|
|
41
|
+
import { integrationWriteKeys } from "@executor-js/react/api/reactivity-keys";
|
|
42
|
+
import {
|
|
43
|
+
slugifyNamespace,
|
|
44
|
+
useIntegrationIdentity
|
|
45
|
+
} from "@executor-js/react/plugins/integration-identity";
|
|
46
|
+
import { Button as Button2 } from "@executor-js/react/components/button";
|
|
47
|
+
import {
|
|
48
|
+
AuthTemplateEditor
|
|
49
|
+
} from "@executor-js/react/components/auth-template-editor";
|
|
50
|
+
import { CardStack as CardStack2, CardStackContent as CardStackContent2 } from "@executor-js/react/components/card-stack";
|
|
51
|
+
import { FieldLabel as FieldLabel2 } from "@executor-js/react/components/field";
|
|
52
|
+
import { FloatActions } from "@executor-js/react/components/float-actions";
|
|
53
|
+
import { Textarea } from "@executor-js/react/components/textarea";
|
|
54
|
+
import { IOSSpinner, Spinner } from "@executor-js/react/components/spinner";
|
|
55
|
+
import { PlusIcon as PlusIcon2, XIcon as XIcon2 } from "lucide-react";
|
|
56
|
+
|
|
57
|
+
// src/react/OpenApiSourceDetailsFields.tsx
|
|
58
|
+
import {
|
|
59
|
+
CardStack,
|
|
60
|
+
CardStackContent,
|
|
61
|
+
CardStackEntry,
|
|
62
|
+
CardStackEntryContent,
|
|
63
|
+
CardStackEntryDescription,
|
|
64
|
+
CardStackEntryField,
|
|
65
|
+
CardStackEntryTitle
|
|
66
|
+
} from "@executor-js/react/components/card-stack";
|
|
67
|
+
import {
|
|
68
|
+
FreeformCombobox
|
|
69
|
+
} from "@executor-js/react/components/combobox";
|
|
70
|
+
import { Input } from "@executor-js/react/components/input";
|
|
71
|
+
import { IntegrationFavicon } from "@executor-js/react/components/integration-favicon";
|
|
72
|
+
import {
|
|
73
|
+
IntegrationIdentityFieldRows
|
|
74
|
+
} from "@executor-js/react/plugins/integration-identity";
|
|
75
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
76
|
+
function OpenApiSourceDetailsFields(props) {
|
|
77
|
+
const baseUrlOptions = props.baseUrlOptions ?? [];
|
|
78
|
+
return /* @__PURE__ */ jsx(CardStack, { children: /* @__PURE__ */ jsxs(CardStackContent, { className: "border-t-0", children: [
|
|
79
|
+
/* @__PURE__ */ jsxs(CardStackEntry, { children: [
|
|
80
|
+
(props.faviconIcon || props.faviconUrl) && /* @__PURE__ */ jsx(IntegrationFavicon, { icon: props.faviconIcon, url: props.faviconUrl, size: 16 }),
|
|
81
|
+
/* @__PURE__ */ jsxs(CardStackEntryContent, { children: [
|
|
82
|
+
/* @__PURE__ */ jsx(CardStackEntryTitle, { children: props.title }),
|
|
83
|
+
props.description && /* @__PURE__ */ jsx(CardStackEntryDescription, { children: props.description })
|
|
84
|
+
] }),
|
|
85
|
+
props.saveState && props.saveState !== "idle" && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: props.saveState === "saving" ? "Saving\u2026" : "Saved" })
|
|
86
|
+
] }),
|
|
87
|
+
/* @__PURE__ */ jsx(
|
|
88
|
+
IntegrationIdentityFieldRows,
|
|
89
|
+
{
|
|
90
|
+
identity: props.identity,
|
|
91
|
+
namespaceReadOnly: props.namespaceReadOnly
|
|
92
|
+
}
|
|
93
|
+
),
|
|
94
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2", children: [
|
|
95
|
+
/* @__PURE__ */ jsxs(CardStackEntryField, { label: "Base URL", children: [
|
|
96
|
+
baseUrlOptions.length > 0 ? /* @__PURE__ */ jsx(
|
|
97
|
+
FreeformCombobox,
|
|
98
|
+
{
|
|
99
|
+
value: props.baseUrl,
|
|
100
|
+
onValueChange: props.onBaseUrlChange,
|
|
101
|
+
options: baseUrlOptions,
|
|
102
|
+
placeholder: "https://api.example.com",
|
|
103
|
+
className: "w-full",
|
|
104
|
+
inputClassName: "font-mono text-sm"
|
|
105
|
+
}
|
|
106
|
+
) : /* @__PURE__ */ jsx(
|
|
107
|
+
Input,
|
|
108
|
+
{
|
|
109
|
+
value: props.baseUrl,
|
|
110
|
+
onChange: (e) => props.onBaseUrlChange(e.target.value),
|
|
111
|
+
placeholder: "https://api.example.com",
|
|
112
|
+
className: "font-mono text-sm"
|
|
113
|
+
}
|
|
114
|
+
),
|
|
115
|
+
props.baseUrlMissingMessage && !props.baseUrl && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-amber-600 dark:text-amber-400", children: props.baseUrlMissingMessage })
|
|
116
|
+
] }),
|
|
117
|
+
props.specUrl !== void 0 && props.onSpecUrlChange && /* @__PURE__ */ jsx(CardStackEntryField, { label: "Spec URL", children: /* @__PURE__ */ jsx(
|
|
118
|
+
Input,
|
|
119
|
+
{
|
|
120
|
+
value: props.specUrl,
|
|
121
|
+
onChange: (e) => props.onSpecUrlChange?.(e.target.value),
|
|
122
|
+
placeholder: "https://api.example.com/openapi.json",
|
|
123
|
+
className: "font-mono text-sm",
|
|
124
|
+
disabled: props.specUrlDisabled
|
|
125
|
+
}
|
|
126
|
+
) })
|
|
127
|
+
] }),
|
|
128
|
+
props.footer && /* @__PURE__ */ jsx(CardStackEntry, { children: /* @__PURE__ */ jsx(CardStackEntryContent, { children: /* @__PURE__ */ jsx(CardStackEntryTitle, { children: props.footer }) }) })
|
|
129
|
+
] }) });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/react/GoogleProductPicker.tsx
|
|
133
|
+
import { useMemo, useState } from "react";
|
|
134
|
+
import { ChevronDownIcon, PlusIcon, TriangleAlert, XIcon } from "lucide-react";
|
|
135
|
+
import { cn } from "@executor-js/react/lib/utils";
|
|
136
|
+
import { Badge } from "@executor-js/react/components/badge";
|
|
137
|
+
import { Button } from "@executor-js/react/components/button";
|
|
138
|
+
import { Checkbox } from "@executor-js/react/components/checkbox";
|
|
139
|
+
import {
|
|
140
|
+
Collapsible,
|
|
141
|
+
CollapsibleContent,
|
|
142
|
+
CollapsibleTrigger
|
|
143
|
+
} from "@executor-js/react/components/collapsible";
|
|
144
|
+
import { FieldLabel } from "@executor-js/react/components/field";
|
|
145
|
+
import { Input as Input2 } from "@executor-js/react/components/input";
|
|
146
|
+
import { IntegrationFavicon as IntegrationFavicon2 } from "@executor-js/react/components/integration-favicon";
|
|
147
|
+
|
|
148
|
+
// src/sdk/google-oauth-batches.ts
|
|
149
|
+
var GOOGLE_CLOUD_BATCH_IDS = /* @__PURE__ */ new Set(["google-bigquery", "google-cloud-resource-manager"]);
|
|
150
|
+
var googleOAuthConsentBatches = (items) => {
|
|
151
|
+
const standardScopes = [];
|
|
152
|
+
const cloudScopes = [];
|
|
153
|
+
const batches = [];
|
|
154
|
+
for (const item of items) {
|
|
155
|
+
if (item.scopes.length === 0) continue;
|
|
156
|
+
if (item.oauthAudience === "standard-user") {
|
|
157
|
+
standardScopes.push(...item.scopes);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (GOOGLE_CLOUD_BATCH_IDS.has(item.id)) {
|
|
161
|
+
cloudScopes.push(...item.scopes);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
batches.push({
|
|
165
|
+
id: item.id,
|
|
166
|
+
label: item.name,
|
|
167
|
+
apiScopes: item.scopes
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const compactedStandardScopes = compactGoogleOAuthScopes(standardScopes);
|
|
171
|
+
const compactedCloudScopes = compactGoogleOAuthScopes(cloudScopes);
|
|
172
|
+
return [
|
|
173
|
+
...compactedStandardScopes.length > 0 ? [
|
|
174
|
+
{
|
|
175
|
+
id: "google-core",
|
|
176
|
+
label: "Core Google services",
|
|
177
|
+
apiScopes: compactedStandardScopes
|
|
178
|
+
}
|
|
179
|
+
] : [],
|
|
180
|
+
...batches.map((batch) => ({
|
|
181
|
+
...batch,
|
|
182
|
+
apiScopes: compactGoogleOAuthScopes(batch.apiScopes)
|
|
183
|
+
})),
|
|
184
|
+
...compactedCloudScopes.length > 0 ? [
|
|
185
|
+
{
|
|
186
|
+
id: "google-cloud",
|
|
187
|
+
label: "Google Cloud services",
|
|
188
|
+
apiScopes: compactedCloudScopes
|
|
189
|
+
}
|
|
190
|
+
] : []
|
|
191
|
+
].filter((batch) => batch.apiScopes.length > 0);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// src/react/GoogleProductPicker.tsx
|
|
195
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
196
|
+
var AUDIENCE_ORDER = [
|
|
197
|
+
"standard-user",
|
|
198
|
+
"advanced-user",
|
|
199
|
+
"workspace-admin",
|
|
200
|
+
"unsupported-user"
|
|
201
|
+
];
|
|
202
|
+
var AUDIENCE_LABEL = {
|
|
203
|
+
"standard-user": "Core Google services",
|
|
204
|
+
"advanced-user": "Advanced services",
|
|
205
|
+
"workspace-admin": "Workspace admin",
|
|
206
|
+
"unsupported-user": "Limited user consent"
|
|
207
|
+
};
|
|
208
|
+
var AUDIENCE_DESCRIPTION = {
|
|
209
|
+
"standard-user": "Connect with a normal Google account \u2014 one consent screen.",
|
|
210
|
+
"advanced-user": "Broader scopes that may need an unverified-app warning to be accepted.",
|
|
211
|
+
"workspace-admin": "Requires a Google Workspace admin account; not available on personal Gmail.",
|
|
212
|
+
"unsupported-user": "Google does not grant these scopes through standard user OAuth consent."
|
|
213
|
+
};
|
|
214
|
+
var audienceNeedsWarning = (audience) => audience === "workspace-admin" || audience === "unsupported-user";
|
|
215
|
+
var AudienceWarningChip = ({ audience }) => audience === "workspace-admin" ? /* @__PURE__ */ jsxs2(
|
|
216
|
+
Badge,
|
|
217
|
+
{
|
|
218
|
+
variant: "outline",
|
|
219
|
+
className: "shrink-0 border-amber-500/40 text-amber-700 dark:text-amber-400",
|
|
220
|
+
children: [
|
|
221
|
+
/* @__PURE__ */ jsx2(TriangleAlert, { className: "size-3" }),
|
|
222
|
+
"Admin only"
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
) : audience === "unsupported-user" ? /* @__PURE__ */ jsxs2(Badge, { variant: "outline", className: "shrink-0 border-destructive/40 text-destructive", children: [
|
|
226
|
+
/* @__PURE__ */ jsx2(TriangleAlert, { className: "size-3" }),
|
|
227
|
+
"Limited consent"
|
|
228
|
+
] }) : null;
|
|
229
|
+
var ProductRow = ({
|
|
230
|
+
preset,
|
|
231
|
+
checked,
|
|
232
|
+
onToggle
|
|
233
|
+
}) => /* @__PURE__ */ jsxs2(
|
|
234
|
+
FieldLabel,
|
|
235
|
+
{
|
|
236
|
+
className: cn(
|
|
237
|
+
// `w-full` overrides FieldLabel's base `w-fit` (which would size the row to
|
|
238
|
+
// its content and overflow the column); `min-w-0` then lets the cell shrink
|
|
239
|
+
// to its track so the name/summary truncates instead of spilling over.
|
|
240
|
+
"flex w-full min-w-0 cursor-pointer items-center gap-2.5 rounded-md px-2 py-1.5 transition-colors",
|
|
241
|
+
checked ? "bg-primary/5" : "hover:bg-muted/40"
|
|
242
|
+
),
|
|
243
|
+
children: [
|
|
244
|
+
/* @__PURE__ */ jsx2(Checkbox, { checked, onCheckedChange: (next) => onToggle(next === true) }),
|
|
245
|
+
/* @__PURE__ */ jsx2("div", { className: "shrink-0", children: /* @__PURE__ */ jsx2(IntegrationFavicon2, { icon: preset.icon, url: preset.url, size: 16 }) }),
|
|
246
|
+
/* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1 truncate text-sm", children: [
|
|
247
|
+
/* @__PURE__ */ jsx2("span", { className: "font-medium text-foreground", children: preset.name }),
|
|
248
|
+
" ",
|
|
249
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[11px] text-muted-foreground", children: preset.summary })
|
|
250
|
+
] }),
|
|
251
|
+
/* @__PURE__ */ jsx2(AudienceWarningChip, { audience: preset.oauthAudience })
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
var CustomUrlEscapeHatch = ({
|
|
256
|
+
customUrls,
|
|
257
|
+
onAddCustomUrl,
|
|
258
|
+
onRemoveCustomUrl
|
|
259
|
+
}) => {
|
|
260
|
+
const [draft, setDraft] = useState("");
|
|
261
|
+
const trimmed = draft.trim();
|
|
262
|
+
const isValid = isGoogleDiscoveryUrl(trimmed);
|
|
263
|
+
const isDuplicate = customUrls.includes(trimmed);
|
|
264
|
+
const commit = () => {
|
|
265
|
+
if (!isValid || isDuplicate) return;
|
|
266
|
+
onAddCustomUrl(trimmed);
|
|
267
|
+
setDraft("");
|
|
268
|
+
};
|
|
269
|
+
return /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
270
|
+
/* @__PURE__ */ jsx2(FieldLabel, { className: "text-[11px] font-medium text-muted-foreground", children: "Add a custom Google Discovery URL" }),
|
|
271
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
|
|
272
|
+
/* @__PURE__ */ jsx2(
|
|
273
|
+
Input2,
|
|
274
|
+
{
|
|
275
|
+
value: draft,
|
|
276
|
+
onChange: (event) => setDraft(event.target.value),
|
|
277
|
+
onKeyDown: (event) => {
|
|
278
|
+
if (event.key === "Enter") {
|
|
279
|
+
event.preventDefault();
|
|
280
|
+
commit();
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
placeholder: "https://www.googleapis.com/discovery/v1/apis/<service>/<version>/rest",
|
|
284
|
+
className: "font-mono text-[11px]"
|
|
285
|
+
}
|
|
286
|
+
),
|
|
287
|
+
/* @__PURE__ */ jsxs2(
|
|
288
|
+
Button,
|
|
289
|
+
{
|
|
290
|
+
type: "button",
|
|
291
|
+
variant: "outline",
|
|
292
|
+
size: "sm",
|
|
293
|
+
disabled: !isValid || isDuplicate,
|
|
294
|
+
onClick: commit,
|
|
295
|
+
children: [
|
|
296
|
+
/* @__PURE__ */ jsx2(PlusIcon, { className: "size-3.5" }),
|
|
297
|
+
"Add"
|
|
298
|
+
]
|
|
299
|
+
}
|
|
300
|
+
)
|
|
301
|
+
] }),
|
|
302
|
+
trimmed.length > 0 && !isValid ? /* @__PURE__ */ jsx2("p", { className: "text-[11px] text-destructive", children: "Enter a Google Discovery document URL (a *.googleapis.com discovery/$discovery endpoint)." }) : null,
|
|
303
|
+
customUrls.length > 0 ? /* @__PURE__ */ jsx2("ul", { className: "space-y-1", children: customUrls.map((url) => /* @__PURE__ */ jsxs2(
|
|
304
|
+
"li",
|
|
305
|
+
{
|
|
306
|
+
className: "flex items-center justify-between gap-2 rounded-md border border-border bg-muted/20 px-2.5 py-1.5",
|
|
307
|
+
children: [
|
|
308
|
+
/* @__PURE__ */ jsx2("span", { className: "truncate font-mono text-[11px] text-foreground", children: url }),
|
|
309
|
+
/* @__PURE__ */ jsx2(
|
|
310
|
+
Button,
|
|
311
|
+
{
|
|
312
|
+
type: "button",
|
|
313
|
+
variant: "ghost",
|
|
314
|
+
size: "icon",
|
|
315
|
+
className: "size-6 shrink-0",
|
|
316
|
+
onClick: () => onRemoveCustomUrl(url),
|
|
317
|
+
"aria-label": `Remove ${url}`,
|
|
318
|
+
children: /* @__PURE__ */ jsx2(XIcon, { className: "size-3.5" })
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
]
|
|
322
|
+
},
|
|
323
|
+
url
|
|
324
|
+
)) }) : null
|
|
325
|
+
] });
|
|
326
|
+
};
|
|
327
|
+
function GoogleProductPicker({
|
|
328
|
+
selectedPresetIds,
|
|
329
|
+
onToggle,
|
|
330
|
+
customUrls,
|
|
331
|
+
onAddCustomUrl,
|
|
332
|
+
onRemoveCustomUrl
|
|
333
|
+
}) {
|
|
334
|
+
const [scopesOpen, setScopesOpen] = useState(false);
|
|
335
|
+
const groups = useMemo(
|
|
336
|
+
() => AUDIENCE_ORDER.flatMap((audience) => {
|
|
337
|
+
const presets = googleOpenApiPresets.filter(
|
|
338
|
+
(preset) => preset.oauthAudience === audience
|
|
339
|
+
);
|
|
340
|
+
return presets.length > 0 ? [{ audience, presets }] : [];
|
|
341
|
+
}),
|
|
342
|
+
[]
|
|
343
|
+
);
|
|
344
|
+
const consentBatches = useMemo(
|
|
345
|
+
() => googleOAuthConsentBatches(
|
|
346
|
+
googleOpenApiPresets.filter((preset) => selectedPresetIds.has(preset.id)).map((preset) => ({
|
|
347
|
+
id: preset.id,
|
|
348
|
+
name: preset.name,
|
|
349
|
+
oauthAudience: preset.oauthAudience,
|
|
350
|
+
scopes: googleOAuthConsentScopesForPreset(preset.id)
|
|
351
|
+
}))
|
|
352
|
+
),
|
|
353
|
+
[selectedPresetIds]
|
|
354
|
+
);
|
|
355
|
+
const selectedCount = selectedPresetIds.size + customUrls.length;
|
|
356
|
+
return /* @__PURE__ */ jsxs2("section", { className: "space-y-4", children: [
|
|
357
|
+
/* @__PURE__ */ jsxs2("div", { className: "space-y-1", children: [
|
|
358
|
+
/* @__PURE__ */ jsx2(FieldLabel, { children: "Customize your Google connection" }),
|
|
359
|
+
/* @__PURE__ */ jsx2("p", { className: "text-[11px] text-muted-foreground", children: "Pick the Google APIs to bundle into one connection. They share a single OAuth consent and appear as merged tools under one Google integration." })
|
|
360
|
+
] }),
|
|
361
|
+
groups.map(
|
|
362
|
+
({
|
|
363
|
+
audience,
|
|
364
|
+
presets
|
|
365
|
+
}) => /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
366
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
|
|
367
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[11px] font-semibold tracking-wide text-foreground uppercase", children: AUDIENCE_LABEL[audience] }),
|
|
368
|
+
audienceNeedsWarning(audience) ? /* @__PURE__ */ jsx2(AudienceWarningChip, { audience }) : null,
|
|
369
|
+
/* @__PURE__ */ jsx2(
|
|
370
|
+
Button,
|
|
371
|
+
{
|
|
372
|
+
type: "button",
|
|
373
|
+
variant: "ghost",
|
|
374
|
+
size: "sm",
|
|
375
|
+
className: "ml-auto h-auto px-1.5 py-0.5 text-[11px] font-normal text-muted-foreground hover:bg-transparent hover:text-foreground",
|
|
376
|
+
onClick: () => {
|
|
377
|
+
const allSelected = presets.every(
|
|
378
|
+
(preset) => selectedPresetIds.has(preset.id)
|
|
379
|
+
);
|
|
380
|
+
presets.forEach(
|
|
381
|
+
(preset) => onToggle(preset.id, !allSelected)
|
|
382
|
+
);
|
|
383
|
+
},
|
|
384
|
+
children: presets.every((preset) => selectedPresetIds.has(preset.id)) ? "Clear" : "Select all"
|
|
385
|
+
}
|
|
386
|
+
)
|
|
387
|
+
] }),
|
|
388
|
+
/* @__PURE__ */ jsx2("p", { className: "text-[11px] text-muted-foreground", children: AUDIENCE_DESCRIPTION[audience] }),
|
|
389
|
+
/* @__PURE__ */ jsx2("div", { className: "grid grid-cols-1 gap-x-4 gap-y-0.5 sm:grid-cols-2", children: presets.map((preset) => /* @__PURE__ */ jsx2(
|
|
390
|
+
ProductRow,
|
|
391
|
+
{
|
|
392
|
+
preset,
|
|
393
|
+
checked: selectedPresetIds.has(preset.id),
|
|
394
|
+
onToggle: (checked) => onToggle(preset.id, checked)
|
|
395
|
+
},
|
|
396
|
+
preset.id
|
|
397
|
+
)) })
|
|
398
|
+
] }, audience)
|
|
399
|
+
),
|
|
400
|
+
/* @__PURE__ */ jsx2(
|
|
401
|
+
CustomUrlEscapeHatch,
|
|
402
|
+
{
|
|
403
|
+
customUrls,
|
|
404
|
+
onAddCustomUrl,
|
|
405
|
+
onRemoveCustomUrl
|
|
406
|
+
}
|
|
407
|
+
),
|
|
408
|
+
/* @__PURE__ */ jsxs2("div", { className: "space-y-1.5 rounded-lg border border-border bg-muted/10 px-3 py-2.5", children: [
|
|
409
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
|
|
410
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[11px] font-semibold tracking-wide text-foreground uppercase", children: "Authentication" }),
|
|
411
|
+
/* @__PURE__ */ jsx2(Badge, { variant: "secondary", children: "OAuth" })
|
|
412
|
+
] }),
|
|
413
|
+
/* @__PURE__ */ jsx2("p", { className: "text-[11px] text-muted-foreground", children: "The selected Google APIs share one OAuth consent. Review the scopes below, then connect a Google account from the integration page after adding." })
|
|
414
|
+
] }),
|
|
415
|
+
/* @__PURE__ */ jsxs2(Collapsible, { open: scopesOpen, onOpenChange: setScopesOpen, children: [
|
|
416
|
+
/* @__PURE__ */ jsx2(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs2(Button, { type: "button", variant: "outline", size: "sm", disabled: consentBatches.length === 0, children: [
|
|
417
|
+
/* @__PURE__ */ jsx2(
|
|
418
|
+
ChevronDownIcon,
|
|
419
|
+
{
|
|
420
|
+
className: cn("size-3.5 transition-transform", scopesOpen ? "rotate-180" : "")
|
|
421
|
+
}
|
|
422
|
+
),
|
|
423
|
+
"View scopes",
|
|
424
|
+
selectedCount > 0 ? /* @__PURE__ */ jsx2(Badge, { variant: "secondary", className: "ml-1", children: selectedCount }) : null
|
|
425
|
+
] }) }),
|
|
426
|
+
/* @__PURE__ */ jsx2(CollapsibleContent, { className: "pt-3", children: consentBatches.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "text-[11px] text-muted-foreground", children: "Select at least one Google API to preview the OAuth consent." }) : /* @__PURE__ */ jsx2("div", { className: "space-y-3", children: consentBatches.map((batch) => /* @__PURE__ */ jsxs2("div", { className: "space-y-1.5", children: [
|
|
427
|
+
/* @__PURE__ */ jsx2("span", { className: "text-[11px] font-semibold text-foreground", children: batch.label }),
|
|
428
|
+
/* @__PURE__ */ jsx2("ul", { className: "space-y-1", children: batch.apiScopes.map((scope) => /* @__PURE__ */ jsx2(
|
|
429
|
+
"li",
|
|
430
|
+
{
|
|
431
|
+
className: "rounded-md border border-border bg-muted/20 px-2.5 py-1 font-mono text-[11px] break-all text-muted-foreground",
|
|
432
|
+
children: scope
|
|
433
|
+
},
|
|
434
|
+
scope
|
|
435
|
+
)) })
|
|
436
|
+
] }, batch.id)) }) })
|
|
437
|
+
] })
|
|
438
|
+
] });
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// src/react/AddOpenApiSource.tsx
|
|
442
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
443
|
+
var GOOGLE_BUNDLE_BASE_URL = "https://www.googleapis.com/";
|
|
444
|
+
var GOOGLE_BUNDLE_FAVICON = "https://fonts.gstatic.com/s/i/productlogos/googleg/v6/192px.svg";
|
|
445
|
+
var googleBundleDefaultPresetIds = new Set(
|
|
446
|
+
googleOpenApiPresets.filter((preset) => preset.featured).map((preset) => preset.id)
|
|
447
|
+
);
|
|
448
|
+
var googleBundleUrls = (selectedPresetIds, customUrls) => {
|
|
449
|
+
const fromPresets = googleOpenApiPresets.flatMap(
|
|
450
|
+
(preset) => preset.url && selectedPresetIds.has(preset.id) ? [preset.url] : []
|
|
451
|
+
);
|
|
452
|
+
return [.../* @__PURE__ */ new Set([...fromPresets, ...customUrls])];
|
|
453
|
+
};
|
|
454
|
+
var ErrorMessage = Schema.Struct({ message: Schema.String });
|
|
455
|
+
var decodeErrorMessage = Schema.decodeUnknownOption(ErrorMessage);
|
|
456
|
+
var errorMessageFromExit = (exit, fallback) => Option.match(Option.flatMap(Exit.findErrorOption(exit), decodeErrorMessage), {
|
|
457
|
+
onNone: () => fallback,
|
|
458
|
+
onSome: ({ message }) => message
|
|
459
|
+
});
|
|
460
|
+
var isIntegrationAlreadyExistsExit = (exit) => Option.match(Exit.findErrorOption(exit), {
|
|
461
|
+
onNone: () => false,
|
|
462
|
+
onSome: Predicate.isTagged("IntegrationAlreadyExistsError")
|
|
463
|
+
});
|
|
464
|
+
var integrationExistsMessage = (slug) => `An integration named "${slug}" already exists. To add more authentication, update your existing integration.`;
|
|
465
|
+
function resolveOAuthUrl(url, baseUrl) {
|
|
466
|
+
if (!url) return url;
|
|
467
|
+
try {
|
|
468
|
+
new URL(url);
|
|
469
|
+
return url;
|
|
470
|
+
} catch {
|
|
471
|
+
if (!baseUrl) return url;
|
|
472
|
+
try {
|
|
473
|
+
return new URL(url, baseUrl).toString();
|
|
474
|
+
} catch {
|
|
475
|
+
return url;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
var standardOidcIdentityScopes = ["openid", "email", "profile"];
|
|
480
|
+
var identityScopesForPreset = (identityScopes) => {
|
|
481
|
+
if (identityScopes === false) return [];
|
|
482
|
+
return identityScopes === "auto" ? standardOidcIdentityScopes : identityScopes;
|
|
483
|
+
};
|
|
484
|
+
var resolvedOAuthScopes = (apiScopes, identityScopes) => {
|
|
485
|
+
const merged = new Set(apiScopes);
|
|
486
|
+
for (const scope of identityScopesForPreset(identityScopes)) merged.add(scope);
|
|
487
|
+
return [...merged];
|
|
488
|
+
};
|
|
489
|
+
var isGoogleDiscoveryUrl2 = (url) => {
|
|
490
|
+
const trimmed = url.trim();
|
|
491
|
+
if (!URL.canParse(trimmed)) return false;
|
|
492
|
+
const parsed = new URL(trimmed);
|
|
493
|
+
const host = parsed.hostname.toLowerCase();
|
|
494
|
+
if (!host.endsWith("googleapis.com")) return false;
|
|
495
|
+
return parsed.pathname.includes("/discovery/") || parsed.pathname.includes("$discovery");
|
|
496
|
+
};
|
|
497
|
+
var normalizePresetUrl = (url) => {
|
|
498
|
+
const trimmed = url.trim();
|
|
499
|
+
if (!URL.canParse(trimmed)) return trimmed.replace(/\/$/, "");
|
|
500
|
+
const parsed = new URL(trimmed);
|
|
501
|
+
parsed.hash = "";
|
|
502
|
+
parsed.searchParams.sort();
|
|
503
|
+
return parsed.toString().replace(/\/$/, "");
|
|
504
|
+
};
|
|
505
|
+
var specInputForAdd = (input) => {
|
|
506
|
+
const value = input.trim();
|
|
507
|
+
const parsed = Effect.runSyncExit(
|
|
508
|
+
Effect.try({
|
|
509
|
+
try: () => new URL(value),
|
|
510
|
+
catch: () => null
|
|
511
|
+
})
|
|
512
|
+
);
|
|
513
|
+
return Exit.isSuccess(parsed) ? isGoogleDiscoveryUrl2(value) ? { kind: "googleDiscovery", url: value } : { kind: "url", url: value } : { kind: "blob", value };
|
|
514
|
+
};
|
|
515
|
+
var headerPrefix = (preset, headerName) => {
|
|
516
|
+
const label = preset.label.toLowerCase();
|
|
517
|
+
if (headerName.toLowerCase() === "authorization") {
|
|
518
|
+
if (label.includes("bearer")) return "Bearer ";
|
|
519
|
+
if (label.includes("basic")) return "Basic ";
|
|
520
|
+
}
|
|
521
|
+
return void 0;
|
|
522
|
+
};
|
|
523
|
+
var apiKeyTemplateFromHeaderPreset = (preset, slug) => {
|
|
524
|
+
const headers = {};
|
|
525
|
+
for (const headerName of preset.secretHeaders) {
|
|
526
|
+
const prefix = headerPrefix(preset, headerName);
|
|
527
|
+
headers[headerName] = prefix ? [prefix, variable(TOKEN_VARIABLE)] : [variable(TOKEN_VARIABLE)];
|
|
528
|
+
}
|
|
529
|
+
return { slug, type: "apiKey", headers };
|
|
530
|
+
};
|
|
531
|
+
var oauthTemplateFromPreset = (preset, baseUrl, slug, scopes) => ({
|
|
532
|
+
slug,
|
|
533
|
+
type: "oauth",
|
|
534
|
+
authorizationUrl: resolveOAuthUrl(
|
|
535
|
+
Option.getOrElse(preset.authorizationUrl, () => ""),
|
|
536
|
+
baseUrl
|
|
537
|
+
),
|
|
538
|
+
tokenUrl: resolveOAuthUrl(preset.tokenUrl, baseUrl),
|
|
539
|
+
scopes: [...scopes]
|
|
540
|
+
});
|
|
541
|
+
var expandServerOptions = (server) => expandServerUrlOptions(server).map((value) => ({ value, label: value }));
|
|
542
|
+
var firstBaseUrlForPreview = (preview) => {
|
|
543
|
+
const firstServer = preview.servers[0];
|
|
544
|
+
return firstServer ? expandServerUrlOptions(firstServer)[0] ?? "" : "";
|
|
545
|
+
};
|
|
546
|
+
var detectedAuthenticationTemplates = (headerPresets, oauth2Presets, baseUrl) => {
|
|
547
|
+
const templates = [];
|
|
548
|
+
headerPresets.forEach((preset, index) => {
|
|
549
|
+
templates.push(
|
|
550
|
+
apiKeyTemplateFromHeaderPreset(preset, AuthTemplateSlug.make(`apikey-${index}`))
|
|
551
|
+
);
|
|
552
|
+
});
|
|
553
|
+
for (const preset of oauth2Presets) {
|
|
554
|
+
const scopes = resolvedOAuthScopes(Object.keys(preset.scopes), preset.identityScopes);
|
|
555
|
+
templates.push(
|
|
556
|
+
oauthTemplateFromPreset(
|
|
557
|
+
preset,
|
|
558
|
+
baseUrl,
|
|
559
|
+
AuthTemplateSlug.make(`oauth-${preset.securitySchemeName}`),
|
|
560
|
+
scopes
|
|
561
|
+
)
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
return templates;
|
|
565
|
+
};
|
|
566
|
+
function AddOpenApiSource(props) {
|
|
567
|
+
const isGoogleBundlePreset = props.initialPreset === GOOGLE_BUNDLE_PRESET_ID;
|
|
568
|
+
const [specUrl, setSpecUrl] = useState2(props.initialUrl ?? "");
|
|
569
|
+
const [selectedPresetIds, setSelectedPresetIds] = useState2(
|
|
570
|
+
googleBundleDefaultPresetIds
|
|
571
|
+
);
|
|
572
|
+
const [customDiscoveryUrls, setCustomDiscoveryUrls] = useState2([]);
|
|
573
|
+
const [analyzing, setAnalyzing] = useState2(false);
|
|
574
|
+
const [analyzeError, setAnalyzeError] = useState2(null);
|
|
575
|
+
const [preview, setPreview] = useState2(null);
|
|
576
|
+
const [baseUrl, setBaseUrl] = useState2(isGoogleBundlePreset ? GOOGLE_BUNDLE_BASE_URL : "");
|
|
577
|
+
const identityFallbackName = isGoogleBundlePreset ? "Google" : preview ? Option.getOrElse(preview.title, () => "") : "";
|
|
578
|
+
const identity = useIntegrationIdentity({
|
|
579
|
+
fallbackName: identityFallbackName,
|
|
580
|
+
fallbackNamespace: props.initialNamespace ?? (isGoogleBundlePreset ? "google" : void 0)
|
|
581
|
+
});
|
|
582
|
+
const bundleDiscoveryUrls = useMemo2(
|
|
583
|
+
() => googleBundleUrls(selectedPresetIds, customDiscoveryUrls),
|
|
584
|
+
[selectedPresetIds, customDiscoveryUrls]
|
|
585
|
+
);
|
|
586
|
+
const toggleBundlePreset = useCallback((presetId, checked) => {
|
|
587
|
+
setSelectedPresetIds((current) => {
|
|
588
|
+
const next = new Set(current);
|
|
589
|
+
if (checked) next.add(presetId);
|
|
590
|
+
else next.delete(presetId);
|
|
591
|
+
return next;
|
|
592
|
+
});
|
|
593
|
+
}, []);
|
|
594
|
+
const addCustomDiscoveryUrl = useCallback((url) => {
|
|
595
|
+
setCustomDiscoveryUrls(
|
|
596
|
+
(current) => current.includes(url) ? current : [...current, url]
|
|
597
|
+
);
|
|
598
|
+
}, []);
|
|
599
|
+
const removeCustomDiscoveryUrl = useCallback((url) => {
|
|
600
|
+
setCustomDiscoveryUrls(
|
|
601
|
+
(current) => current.filter((entry) => entry !== url)
|
|
602
|
+
);
|
|
603
|
+
}, []);
|
|
604
|
+
const [adding, setAdding] = useState2(false);
|
|
605
|
+
const [addError, setAddError] = useState2(null);
|
|
606
|
+
const doPreview = useAtomSet(previewOpenApiSpec, { mode: "promiseExit" });
|
|
607
|
+
const doAdd = useAtomSet(addOpenApiSpec, { mode: "promiseExit" });
|
|
608
|
+
const handleAnalyzeRef = useRef(() => {
|
|
609
|
+
});
|
|
610
|
+
useEffect(() => {
|
|
611
|
+
if (isGoogleBundlePreset) return;
|
|
612
|
+
const trimmed = specUrl.trim();
|
|
613
|
+
if (!trimmed) return;
|
|
614
|
+
if (preview) return;
|
|
615
|
+
const handle = setTimeout(() => {
|
|
616
|
+
handleAnalyzeRef.current();
|
|
617
|
+
}, 400);
|
|
618
|
+
return () => clearTimeout(handle);
|
|
619
|
+
}, [specUrl, preview, isGoogleBundlePreset]);
|
|
620
|
+
const servers = preview?.servers ?? [];
|
|
621
|
+
const baseUrlOptions = Array.from(
|
|
622
|
+
new Map(servers.flatMap(expandServerOptions).map((option) => [option.value, option])).values()
|
|
623
|
+
);
|
|
624
|
+
const previewPresetIcon = openApiPresets.find(
|
|
625
|
+
(preset) => preset.url && normalizePresetUrl(preset.url) === normalizePresetUrl(specUrl)
|
|
626
|
+
)?.icon ?? null;
|
|
627
|
+
const resolvedBaseUrl = baseUrl.trim();
|
|
628
|
+
const resolvedSourceId = slugifyNamespace(identity.namespace) || (preview ? Option.getOrElse(preview.title, () => "openapi") : "openapi");
|
|
629
|
+
const resolvedDisplayName = identity.name.trim() || (preview ? Option.getOrElse(preview.title, () => resolvedSourceId) : resolvedSourceId);
|
|
630
|
+
const authenticationTemplate = useMemo2(
|
|
631
|
+
() => detectedAuthenticationTemplates(
|
|
632
|
+
preview?.headerPresets ?? [],
|
|
633
|
+
preview?.oauth2Presets ?? [],
|
|
634
|
+
resolvedBaseUrl
|
|
635
|
+
),
|
|
636
|
+
[preview, resolvedBaseUrl]
|
|
637
|
+
);
|
|
638
|
+
const detectedMethodLabels = useMemo2(
|
|
639
|
+
() => [
|
|
640
|
+
...(preview?.headerPresets ?? []).map((preset) => preset.label),
|
|
641
|
+
...(preview?.oauth2Presets ?? []).map((preset) => preset.label)
|
|
642
|
+
],
|
|
643
|
+
[preview]
|
|
644
|
+
);
|
|
645
|
+
const [authMethods, setAuthMethods] = useState2([]);
|
|
646
|
+
const seededFromRef = useRef(null);
|
|
647
|
+
useEffect(() => {
|
|
648
|
+
if (seededFromRef.current === authenticationTemplate) return;
|
|
649
|
+
seededFromRef.current = authenticationTemplate;
|
|
650
|
+
setAuthMethods(
|
|
651
|
+
authenticationTemplate.map((template) => ({
|
|
652
|
+
value: editorValueFromAuthentication(template),
|
|
653
|
+
seedSlug: String(template.slug)
|
|
654
|
+
}))
|
|
655
|
+
);
|
|
656
|
+
}, [authenticationTemplate]);
|
|
657
|
+
const setAuthMethodAt = useCallback((index, next) => {
|
|
658
|
+
setAuthMethods(
|
|
659
|
+
(current) => current.map((row, i) => i === index ? { ...row, value: next } : row)
|
|
660
|
+
);
|
|
661
|
+
}, []);
|
|
662
|
+
const removeAuthMethodAt = useCallback((index) => {
|
|
663
|
+
setAuthMethods(
|
|
664
|
+
(current) => current.filter((_row, i) => i !== index)
|
|
665
|
+
);
|
|
666
|
+
}, []);
|
|
667
|
+
const addAuthMethod = useCallback(() => {
|
|
668
|
+
setAuthMethods((current) => [
|
|
669
|
+
...current,
|
|
670
|
+
{
|
|
671
|
+
value: {
|
|
672
|
+
kind: "apikey",
|
|
673
|
+
placements: [{ carrier: "header", name: "Authorization", prefix: "" }]
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
]);
|
|
677
|
+
}, []);
|
|
678
|
+
const editedAuthenticationTemplate = useMemo2(() => {
|
|
679
|
+
const templates = [];
|
|
680
|
+
authMethods.forEach((row, index) => {
|
|
681
|
+
const slug = row.seedSlug ?? (row.value.kind === "oauth" ? `oauth-${index}` : `apikey-${index}`);
|
|
682
|
+
const template = authenticationFromEditorValue(row.value, slug);
|
|
683
|
+
if (template !== null) templates.push(template);
|
|
684
|
+
});
|
|
685
|
+
return templates;
|
|
686
|
+
}, [authMethods]);
|
|
687
|
+
const integrationsResult = useAtomValue(integrationsOptimisticAtom);
|
|
688
|
+
const slugAlreadyExists = useMemo2(
|
|
689
|
+
() => AsyncResult.isSuccess(integrationsResult) && integrationsResult.value.some((integration) => integration.slug === resolvedSourceId),
|
|
690
|
+
[integrationsResult, resolvedSourceId]
|
|
691
|
+
);
|
|
692
|
+
const hasPreviewOrBundle = isGoogleBundlePreset ? bundleDiscoveryUrls.length > 0 : preview !== null;
|
|
693
|
+
const canAdd = hasPreviewOrBundle && resolvedBaseUrl.length > 0 && !slugAlreadyExists;
|
|
694
|
+
const handleAnalyze = async () => {
|
|
695
|
+
setAnalyzing(true);
|
|
696
|
+
setAnalyzeError(null);
|
|
697
|
+
setAddError(null);
|
|
698
|
+
const exit = await doPreview({ payload: { spec: specUrl } });
|
|
699
|
+
if (Exit.isFailure(exit)) {
|
|
700
|
+
setAnalyzeError(errorMessageFromExit(exit, "Failed to parse spec"));
|
|
701
|
+
setAnalyzing(false);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const result = exit.value;
|
|
705
|
+
setPreview(result);
|
|
706
|
+
setBaseUrl(firstBaseUrlForPreview(result));
|
|
707
|
+
setAnalyzing(false);
|
|
708
|
+
};
|
|
709
|
+
handleAnalyzeRef.current = handleAnalyze;
|
|
710
|
+
const ensureIntegration = useCallback(async () => {
|
|
711
|
+
const specForAdd = isGoogleBundlePreset ? { kind: "googleDiscoveryBundle", urls: [...bundleDiscoveryUrls] } : specInputForAdd(specUrl);
|
|
712
|
+
const exit = await doAdd({
|
|
713
|
+
payload: {
|
|
714
|
+
spec: specForAdd,
|
|
715
|
+
slug: resolvedSourceId,
|
|
716
|
+
description: resolvedDisplayName,
|
|
717
|
+
baseUrl: resolvedBaseUrl,
|
|
718
|
+
...!isGoogleBundlePreset && editedAuthenticationTemplate.length > 0 ? {
|
|
719
|
+
authenticationTemplate: editedAuthenticationTemplate.map((entry) => ({
|
|
720
|
+
...entry,
|
|
721
|
+
slug: String(entry.slug)
|
|
722
|
+
}))
|
|
723
|
+
} : {}
|
|
724
|
+
},
|
|
725
|
+
reactivityKeys: integrationWriteKeys
|
|
726
|
+
});
|
|
727
|
+
if (Exit.isFailure(exit)) {
|
|
728
|
+
setAddError(
|
|
729
|
+
isIntegrationAlreadyExistsExit(exit) ? integrationExistsMessage(resolvedSourceId) : errorMessageFromExit(exit, "Failed to add integration")
|
|
730
|
+
);
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
return exit.value.slug;
|
|
734
|
+
}, [
|
|
735
|
+
isGoogleBundlePreset,
|
|
736
|
+
bundleDiscoveryUrls,
|
|
737
|
+
specUrl,
|
|
738
|
+
doAdd,
|
|
739
|
+
resolvedSourceId,
|
|
740
|
+
resolvedDisplayName,
|
|
741
|
+
resolvedBaseUrl,
|
|
742
|
+
editedAuthenticationTemplate
|
|
743
|
+
]);
|
|
744
|
+
const handleAdd = async () => {
|
|
745
|
+
setAdding(true);
|
|
746
|
+
setAddError(null);
|
|
747
|
+
const integration = await ensureIntegration();
|
|
748
|
+
if (!integration) {
|
|
749
|
+
setAdding(false);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
props.onComplete(String(integration));
|
|
753
|
+
};
|
|
754
|
+
return /* @__PURE__ */ jsxs3("div", { className: "flex flex-1 flex-col gap-6", children: [
|
|
755
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
756
|
+
/* @__PURE__ */ jsx3("h1", { className: "text-xl font-semibold text-foreground", children: isGoogleBundlePreset ? "Add Google" : "Add OpenAPI Integration" }),
|
|
757
|
+
isGoogleBundlePreset ? /* @__PURE__ */ jsx3("p", { className: "mt-1 text-[13px] text-muted-foreground", children: "Bundle Google APIs into one integration from their Discovery documents and register their methods as tools under a single shared OAuth consent." }) : null
|
|
758
|
+
] }),
|
|
759
|
+
isGoogleBundlePreset ? /* @__PURE__ */ jsx3(
|
|
760
|
+
GoogleProductPicker,
|
|
761
|
+
{
|
|
762
|
+
selectedPresetIds,
|
|
763
|
+
onToggle: toggleBundlePreset,
|
|
764
|
+
customUrls: customDiscoveryUrls,
|
|
765
|
+
onAddCustomUrl: addCustomDiscoveryUrl,
|
|
766
|
+
onRemoveCustomUrl: removeCustomDiscoveryUrl
|
|
767
|
+
}
|
|
768
|
+
) : !preview ? /* @__PURE__ */ jsx3(CardStack2, { children: /* @__PURE__ */ jsx3(CardStackContent2, { className: "border-t-0", children: /* @__PURE__ */ jsxs3("div", { className: "space-y-2 p-3", children: [
|
|
769
|
+
/* @__PURE__ */ jsx3(FieldLabel2, { children: "OpenAPI Spec" }),
|
|
770
|
+
/* @__PURE__ */ jsxs3("div", { className: "relative", children: [
|
|
771
|
+
/* @__PURE__ */ jsx3(
|
|
772
|
+
Textarea,
|
|
773
|
+
{
|
|
774
|
+
value: specUrl,
|
|
775
|
+
onChange: (e) => setSpecUrl(e.target.value),
|
|
776
|
+
placeholder: "https://api.example.com/openapi.json",
|
|
777
|
+
rows: 3,
|
|
778
|
+
maxRows: 10,
|
|
779
|
+
className: "font-mono text-sm"
|
|
780
|
+
}
|
|
781
|
+
),
|
|
782
|
+
analyzing && /* @__PURE__ */ jsx3("div", { className: "pointer-events-none absolute right-2 top-2", children: /* @__PURE__ */ jsx3(IOSSpinner, { className: "size-4" }) })
|
|
783
|
+
] }),
|
|
784
|
+
/* @__PURE__ */ jsx3("p", { className: "text-[11px] text-muted-foreground", children: "Paste a URL or raw JSON/YAML content." })
|
|
785
|
+
] }) }) }) : null,
|
|
786
|
+
isGoogleBundlePreset ? /* @__PURE__ */ jsx3(
|
|
787
|
+
OpenApiSourceDetailsFields,
|
|
788
|
+
{
|
|
789
|
+
title: "Google",
|
|
790
|
+
description: `${bundleDiscoveryUrls.length} Google API${bundleDiscoveryUrls.length !== 1 ? "s" : ""} \xB7 one shared OAuth consent`,
|
|
791
|
+
identity,
|
|
792
|
+
baseUrl: resolvedBaseUrl,
|
|
793
|
+
onBaseUrlChange: setBaseUrl,
|
|
794
|
+
faviconIcon: GOOGLE_BUNDLE_FAVICON,
|
|
795
|
+
faviconUrl: resolvedBaseUrl,
|
|
796
|
+
baseUrlMissingMessage: "A base URL is required to make requests."
|
|
797
|
+
}
|
|
798
|
+
) : preview ? /* @__PURE__ */ jsx3(
|
|
799
|
+
OpenApiSourceDetailsFields,
|
|
800
|
+
{
|
|
801
|
+
title: Option.getOrElse(preview.title, () => "API"),
|
|
802
|
+
description: `${Option.getOrElse(preview.version, () => "")}${Option.isSome(preview.version) ? " \xB7 " : ""}${preview.operationCount} operation${preview.operationCount !== 1 ? "s" : ""}${preview.tags.length > 0 ? ` \xB7 ${preview.tags.length} tag${preview.tags.length !== 1 ? "s" : ""}` : ""}`,
|
|
803
|
+
identity,
|
|
804
|
+
baseUrl: resolvedBaseUrl,
|
|
805
|
+
onBaseUrlChange: setBaseUrl,
|
|
806
|
+
baseUrlOptions,
|
|
807
|
+
specUrl,
|
|
808
|
+
onSpecUrlChange: (value) => {
|
|
809
|
+
setSpecUrl(value);
|
|
810
|
+
setPreview(null);
|
|
811
|
+
setBaseUrl("");
|
|
812
|
+
},
|
|
813
|
+
faviconIcon: previewPresetIcon,
|
|
814
|
+
faviconUrl: resolvedBaseUrl,
|
|
815
|
+
baseUrlMissingMessage: "A base URL is required to make requests."
|
|
816
|
+
}
|
|
817
|
+
) : null,
|
|
818
|
+
analyzeError && /* @__PURE__ */ jsx3("div", { className: "rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2", children: /* @__PURE__ */ jsx3("p", { className: "text-[12px] text-destructive", children: analyzeError }) }),
|
|
819
|
+
preview && !isGoogleBundlePreset && /* @__PURE__ */ jsxs3("section", { className: "space-y-3", children: [
|
|
820
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between gap-3", children: [
|
|
821
|
+
/* @__PURE__ */ jsx3(FieldLabel2, { children: "How does this API authenticate?" }),
|
|
822
|
+
/* @__PURE__ */ jsxs3(Button2, { type: "button", variant: "outline", size: "sm", onClick: addAuthMethod, children: [
|
|
823
|
+
/* @__PURE__ */ jsx3(PlusIcon2, {}),
|
|
824
|
+
"Add method"
|
|
825
|
+
] })
|
|
826
|
+
] }),
|
|
827
|
+
authMethods.length === 0 ? /* @__PURE__ */ jsx3("p", { className: "text-[11px] text-muted-foreground", children: "No authentication detected. Add a method, or add the integration without auth and connect an account from the integration page later." }) : /* @__PURE__ */ jsx3("div", { className: "flex flex-col gap-3", children: authMethods.map((row, index) => /* @__PURE__ */ jsxs3(
|
|
828
|
+
"div",
|
|
829
|
+
{
|
|
830
|
+
className: "space-y-2 rounded-lg border border-border/60 bg-muted/20 p-3",
|
|
831
|
+
children: [
|
|
832
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
|
|
833
|
+
/* @__PURE__ */ jsxs3("span", { className: "text-xs font-medium text-muted-foreground", children: [
|
|
834
|
+
"Method ",
|
|
835
|
+
index + 1,
|
|
836
|
+
detectedMethodLabels[index] ? ` \xB7 ${detectedMethodLabels[index]}` : ""
|
|
837
|
+
] }),
|
|
838
|
+
/* @__PURE__ */ jsx3(
|
|
839
|
+
Button2,
|
|
840
|
+
{
|
|
841
|
+
type: "button",
|
|
842
|
+
variant: "ghost",
|
|
843
|
+
size: "icon-sm",
|
|
844
|
+
"aria-label": "Remove method",
|
|
845
|
+
className: "text-muted-foreground hover:text-foreground",
|
|
846
|
+
onClick: () => removeAuthMethodAt(index),
|
|
847
|
+
children: /* @__PURE__ */ jsx3(XIcon2, {})
|
|
848
|
+
}
|
|
849
|
+
)
|
|
850
|
+
] }),
|
|
851
|
+
/* @__PURE__ */ jsx3(
|
|
852
|
+
AuthTemplateEditor,
|
|
853
|
+
{
|
|
854
|
+
value: row.value,
|
|
855
|
+
onChange: (next) => setAuthMethodAt(index, next)
|
|
856
|
+
}
|
|
857
|
+
)
|
|
858
|
+
]
|
|
859
|
+
},
|
|
860
|
+
index
|
|
861
|
+
)) }),
|
|
862
|
+
/* @__PURE__ */ jsx3("p", { className: "text-[11px] text-muted-foreground", children: "Every method here is registered with the integration. Connect an account from the integration page after adding." })
|
|
863
|
+
] }),
|
|
864
|
+
hasPreviewOrBundle && slugAlreadyExists && !adding && /* @__PURE__ */ jsx3("div", { className: "rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2", children: /* @__PURE__ */ jsxs3("p", { className: "text-[12px] text-destructive", children: [
|
|
865
|
+
'An integration named "',
|
|
866
|
+
resolvedSourceId,
|
|
867
|
+
'" already exists. To add more authentication, update your existing integration.',
|
|
868
|
+
" ",
|
|
869
|
+
/* @__PURE__ */ jsx3(
|
|
870
|
+
Link,
|
|
871
|
+
{
|
|
872
|
+
to: "/integrations/$namespace",
|
|
873
|
+
params: { namespace: resolvedSourceId },
|
|
874
|
+
className: "font-medium underline underline-offset-2",
|
|
875
|
+
children: "Open it"
|
|
876
|
+
}
|
|
877
|
+
)
|
|
878
|
+
] }) }),
|
|
879
|
+
addError && /* @__PURE__ */ jsx3("div", { className: "rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2", children: /* @__PURE__ */ jsx3("p", { className: "text-[12px] text-destructive", children: addError }) }),
|
|
880
|
+
/* @__PURE__ */ jsxs3(FloatActions, { children: [
|
|
881
|
+
/* @__PURE__ */ jsx3(Button2, { variant: "ghost", onClick: () => props.onCancel(), disabled: adding, children: "Cancel" }),
|
|
882
|
+
(hasPreviewOrBundle || isGoogleBundlePreset) && /* @__PURE__ */ jsxs3(Button2, { onClick: () => void handleAdd(), disabled: !canAdd || adding, children: [
|
|
883
|
+
adding && /* @__PURE__ */ jsx3(Spinner, { className: "size-3.5" }),
|
|
884
|
+
adding ? "Adding\u2026" : isGoogleBundlePreset ? "Connect Google" : "Add integration"
|
|
885
|
+
] })
|
|
886
|
+
] })
|
|
887
|
+
] });
|
|
888
|
+
}
|
|
889
|
+
export {
|
|
890
|
+
AddOpenApiSource as default,
|
|
891
|
+
resolveOAuthUrl
|
|
892
|
+
};
|
|
893
|
+
//# sourceMappingURL=AddOpenApiSource-7M52SRUX.js.map
|