@every-app/sdk 0.3.0-rc.1 → 0.3.0-rc.11
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/LICENSE +21 -0
- package/README.md +62 -64
- package/dist/cloudflare/catalogSettings.generated.d.ts +84 -0
- package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -0
- package/dist/cloudflare/catalogSettings.generated.js +175 -0
- package/dist/cloudflare/index.d.ts +3 -2
- package/dist/cloudflare/index.d.ts.map +1 -1
- package/dist/cloudflare/index.js +4 -1
- package/dist/cloudflare/sentinel.d.ts +60 -2
- package/dist/cloudflare/sentinel.d.ts.map +1 -1
- package/dist/cloudflare/sentinel.js +37 -2
- package/dist/cloudflare/worker.d.ts +391 -63
- package/dist/cloudflare/worker.d.ts.map +1 -1
- package/dist/cloudflare/worker.js +394 -108
- package/dist/dev/index.d.ts +1 -1
- package/dist/dev/index.d.ts.map +1 -1
- package/dist/dev/index.js +1 -1
- package/dist/dev/keypair.d.ts +2 -0
- package/dist/dev/keypair.d.ts.map +1 -1
- package/dist/dev/keypair.js +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/internal/aiDefault.d.ts +205 -0
- package/dist/internal/aiDefault.d.ts.map +1 -0
- package/dist/internal/aiDefault.js +335 -0
- package/dist/internal/aiProviders.d.ts +7 -2
- package/dist/internal/aiProviders.d.ts.map +1 -1
- package/dist/internal/aiProviders.js +19 -6
- package/dist/internal/appSettings.d.ts +82 -4
- package/dist/internal/appSettings.d.ts.map +1 -1
- package/dist/internal/appSettings.js +204 -29
- package/dist/internal/audit.d.ts +108 -0
- package/dist/internal/audit.d.ts.map +1 -0
- package/dist/internal/audit.js +235 -0
- package/dist/internal/capabilities.d.ts +2261 -815
- package/dist/internal/capabilities.d.ts.map +1 -1
- package/dist/internal/capabilities.js +122 -1034
- package/dist/internal/catalog/commerce.d.ts +394 -0
- package/dist/internal/catalog/commerce.d.ts.map +1 -0
- package/dist/internal/catalog/commerce.js +477 -0
- package/dist/internal/catalog/comms.d.ts +375 -0
- package/dist/internal/catalog/comms.d.ts.map +1 -0
- package/dist/internal/catalog/comms.js +520 -0
- package/dist/internal/catalog/index.d.ts +2008 -0
- package/dist/internal/catalog/index.d.ts.map +1 -0
- package/dist/internal/catalog/index.js +19 -0
- package/dist/internal/catalog/inference.d.ts +393 -0
- package/dist/internal/catalog/inference.d.ts.map +1 -0
- package/dist/internal/catalog/inference.js +460 -0
- package/dist/internal/catalog/infra.d.ts +458 -0
- package/dist/internal/catalog/infra.d.ts.map +1 -0
- package/dist/internal/catalog/infra.js +608 -0
- package/dist/internal/catalog/search.d.ts +400 -0
- package/dist/internal/catalog/search.d.ts.map +1 -0
- package/dist/internal/catalog/search.js +463 -0
- package/dist/internal/devProviders.d.ts +2 -2
- package/dist/internal/devProviders.d.ts.map +1 -1
- package/dist/internal/devProviders.js +19 -1
- package/dist/internal/events.d.ts +65 -3
- package/dist/internal/events.d.ts.map +1 -1
- package/dist/internal/events.js +110 -5
- package/dist/internal/gatewaySettings.d.ts +3 -2
- package/dist/internal/gatewaySettings.d.ts.map +1 -1
- package/dist/internal/gatewaySettings.js +12 -6
- package/dist/internal/identity.d.ts +41 -4
- package/dist/internal/identity.d.ts.map +1 -1
- package/dist/internal/identity.js +48 -10
- package/dist/internal/index.d.ts +6 -1
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +6 -1
- package/dist/internal/members.d.ts +3 -2
- package/dist/internal/members.d.ts.map +1 -1
- package/dist/internal/oauthBuiltIns.d.ts +101 -0
- package/dist/internal/oauthBuiltIns.d.ts.map +1 -0
- package/dist/internal/oauthBuiltIns.js +122 -0
- package/dist/internal/oauthClient.d.ts +8 -50
- package/dist/internal/oauthClient.d.ts.map +1 -1
- package/dist/internal/oauthClient.js +2 -177
- package/dist/internal/oauthPresets.d.ts +32 -0
- package/dist/internal/oauthPresets.d.ts.map +1 -0
- package/dist/internal/oauthPresets.js +253 -0
- package/dist/internal/oauthScopes.d.ts +43 -100
- package/dist/internal/oauthScopes.d.ts.map +1 -1
- package/dist/internal/oauthScopes.js +83 -382
- package/dist/internal/oauthSpec.d.ts +103 -0
- package/dist/internal/oauthSpec.d.ts.map +1 -0
- package/dist/internal/oauthSpec.js +520 -0
- package/dist/internal/protocol.d.ts +97 -5
- package/dist/internal/protocol.d.ts.map +1 -1
- package/dist/internal/protocol.js +89 -1
- package/dist/internal/providerRegistry.d.ts +17 -6
- package/dist/internal/providerRegistry.d.ts.map +1 -1
- package/dist/internal/providerRegistry.js +71 -8
- package/dist/internal/providerSpec.d.ts +18 -7
- package/dist/internal/providerSpec.d.ts.map +1 -1
- package/dist/internal/providerSpec.js +420 -34
- package/dist/internal/remoteMcp.d.ts.map +1 -1
- package/dist/internal/remoteMcp.js +1 -5
- package/dist/internal/signInProviders.d.ts +104 -0
- package/dist/internal/signInProviders.d.ts.map +1 -0
- package/dist/internal/signInProviders.js +245 -0
- package/dist/internal/tools.d.ts +5 -4
- package/dist/internal/tools.d.ts.map +1 -1
- package/dist/internal/tools.js +4 -3
- package/dist/manifest.d.ts +267 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +30 -0
- package/dist/server/ai.d.ts +111 -0
- package/dist/server/ai.d.ts.map +1 -0
- package/dist/server/ai.js +145 -0
- package/dist/server/aiBinding.d.ts +68 -0
- package/dist/server/aiBinding.d.ts.map +1 -0
- package/dist/server/aiBinding.js +294 -0
- package/dist/server/attribution.d.ts +18 -5
- package/dist/server/attribution.d.ts.map +1 -1
- package/dist/server/attribution.js +19 -5
- package/dist/server/connectUrl.d.ts +16 -0
- package/dist/server/connectUrl.d.ts.map +1 -0
- package/dist/server/connectUrl.js +40 -0
- package/dist/server/context.d.ts +68 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +117 -0
- package/dist/server/emailBinding.d.ts +72 -0
- package/dist/server/emailBinding.d.ts.map +1 -0
- package/dist/server/emailBinding.js +124 -0
- package/dist/server/emit.d.ts +1 -1
- package/dist/server/emit.d.ts.map +1 -1
- package/dist/server/emit.js +7 -7
- package/dist/server/env.d.ts +44 -0
- package/dist/server/env.d.ts.map +1 -0
- package/dist/server/env.js +1 -0
- package/dist/server/eventsEndpoint.d.ts +48 -0
- package/dist/server/eventsEndpoint.d.ts.map +1 -0
- package/dist/server/eventsEndpoint.js +145 -0
- package/dist/server/everyApp.d.ts +40 -11
- package/dist/server/everyApp.d.ts.map +1 -1
- package/dist/server/everyApp.js +116 -6
- package/dist/server/fromMcpTools.d.ts +89 -0
- package/dist/server/fromMcpTools.d.ts.map +1 -0
- package/dist/server/fromMcpTools.js +192 -0
- package/dist/server/gatewayFetch.d.ts +42 -18
- package/dist/server/gatewayFetch.d.ts.map +1 -1
- package/dist/server/gatewayFetch.js +138 -19
- package/dist/server/gatewayUrl.d.ts +13 -0
- package/dist/server/gatewayUrl.d.ts.map +1 -0
- package/dist/server/gatewayUrl.js +34 -0
- package/dist/server/index.d.ts +16 -4
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +13 -3
- package/dist/server/integrationFetch.d.ts +92 -73
- package/dist/server/integrationFetch.d.ts.map +1 -1
- package/dist/server/integrationFetch.js +206 -319
- package/dist/server/listMembers.d.ts +1 -1
- package/dist/server/listMembers.d.ts.map +1 -1
- package/dist/server/listMembers.js +8 -11
- package/dist/server/refusal.d.ts +55 -0
- package/dist/server/refusal.d.ts.map +1 -0
- package/dist/server/refusal.js +193 -0
- package/dist/server/sendEmail.d.ts +1 -1
- package/dist/server/sendEmail.d.ts.map +1 -1
- package/dist/server/sendEmail.js +7 -7
- package/dist/server/tools.d.ts +7 -2
- package/dist/server/tools.d.ts.map +1 -1
- package/dist/server/tools.js +2 -1
- package/dist/server/toolsEndpoint.d.ts +3 -1
- package/dist/server/toolsEndpoint.d.ts.map +1 -1
- package/dist/server/toolsEndpoint.js +19 -7
- package/dist/server/webhooks.d.ts +56 -0
- package/dist/server/webhooks.d.ts.map +1 -0
- package/dist/server/webhooks.js +205 -0
- package/dist/tanstack-start/index.d.ts +9 -0
- package/dist/tanstack-start/index.d.ts.map +1 -0
- package/dist/tanstack-start/index.js +29 -0
- package/dist/testing/index.d.ts +93 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +156 -0
- package/package.json +35 -14
- package/dist/server/authorizeUrl.d.ts +0 -43
- package/dist/server/authorizeUrl.d.ts.map +0 -1
- package/dist/server/authorizeUrl.js +0 -110
|
@@ -1,33 +1,15 @@
|
|
|
1
|
-
import { CATALOG, ConfigurationError, GATEWAY_ERROR_HEADER, INTEGRATION_PROXY_PREFIX,
|
|
2
|
-
import { applyAttribution,
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* URLs (`https://api.dataforseo.com/v3/…`); this sends them over the
|
|
7
|
-
* AppGateway binding, which checks the app's manifest lists the capability,
|
|
8
|
-
* injects the key the gateway holds and forwards the call. The app never
|
|
9
|
-
* sees the key. A refusal the gateway makes itself comes back as a response
|
|
10
|
-
* {@link gatewayRefusal} recognizes; anything else is the provider's answer.
|
|
11
|
-
*
|
|
12
|
-
* For an OAuth client (`linkedin`, `mastodon`, ...) it reaches the
|
|
13
|
-
* provider's token endpoints only, and the gateway puts its client on the
|
|
14
|
-
* request: send the code (or refresh token) without `client_id` or
|
|
15
|
-
* `client_secret`. Build the authorize URL with {@link authorizeUrl} from
|
|
16
|
-
* {@link integrationStatus}, and call the provider's API with the user's
|
|
17
|
-
* token directly.
|
|
18
|
-
*
|
|
19
|
-
* In local dev only, with no binding, the developer's own key or client
|
|
20
|
-
* from `.dev.vars` reaches the provider directly, under the gateway's names:
|
|
21
|
-
* a catalog provider's from the catalog, a provider registered on the
|
|
22
|
-
* gateway from its spec in `.everyapp/providers/<id>.json` (`everyapp
|
|
23
|
-
* provider show <id> --save` writes it), put on the call as the gateway's
|
|
24
|
-
* spec says.
|
|
25
|
-
*/
|
|
26
|
-
export function integrationFetch({ env, provider, for: target, }) {
|
|
1
|
+
import { APP_ID_ENV, CATALOG, ConfigurationError, GATEWAY_ERROR_HEADER, INTEGRATION_PROXY_PREFIX, apiKeyHosts, authorizationFor, capabilitySettings, devProviderFile, integrationProxyPath, providerSettingPrefix, resolveDevProvider, EMAIL_SENDERS_ENV, chooseEmailProvider, parseEmailList, parseEmailSender, } from "../internal/index.js";
|
|
2
|
+
import { applyAttribution, attributionFor, } from "./attribution.js";
|
|
3
|
+
import { connectPath } from "./connectUrl.js";
|
|
4
|
+
import { BINDING_ORIGIN, gatewayBinding, streamingRequest, } from "./gatewayFetch.js";
|
|
5
|
+
export function integrationFetch({ env, provider, for: explicitTarget, optional = false, }) {
|
|
27
6
|
const catalogEntry = fetchableCapability(provider);
|
|
28
|
-
|
|
7
|
+
const what = `integrationFetch("${provider}")`;
|
|
8
|
+
// Checked now, so a call nobody's fails where the fetch is made; and
|
|
9
|
+
// again per call, where an identity may since have neared its expiry.
|
|
10
|
+
attributionFor(env, explicitTarget, what);
|
|
29
11
|
const values = env;
|
|
30
|
-
const binding =
|
|
12
|
+
const binding = gatewayBinding(env);
|
|
31
13
|
if (binding && typeof binding.fetch !== "function") {
|
|
32
14
|
throw new ConfigurationError("EVERY_APP_GATEWAY is configured but is not a valid service binding.");
|
|
33
15
|
}
|
|
@@ -35,11 +17,18 @@ export function integrationFetch({ env, provider, for: target, }) {
|
|
|
35
17
|
// or in local dev, the registered row's spec `everyapp dev` hands over.
|
|
36
18
|
const integration = catalogEntry ??
|
|
37
19
|
(binding ? undefined : devRegisteredProvider(provider, values));
|
|
38
|
-
|
|
39
|
-
|
|
20
|
+
// An API-key provider in local dev: the developer's own key. An OAuth
|
|
21
|
+
// provider decides per call (a dev token, or `integration_not_connected`).
|
|
22
|
+
const local = binding || integration?.mode !== "api_key"
|
|
23
|
+
? null
|
|
24
|
+
: localAuthorization(integration, values);
|
|
25
|
+
if (!binding && integration?.mode === "api_key" && !local) {
|
|
26
|
+
if (optional)
|
|
27
|
+
return null;
|
|
40
28
|
throw new ConfigurationError(`${integration.label} is unavailable: deploy with the EVERY_APP_GATEWAY binding, or set ${capabilitySettings(integration).join(", ")} in .dev.vars for local development.`);
|
|
41
29
|
}
|
|
42
30
|
return async (input, init) => {
|
|
31
|
+
const target = attributionFor(env, explicitTarget, what);
|
|
43
32
|
const outbound = new Request(input, init);
|
|
44
33
|
const upstream = new URL(outbound.url);
|
|
45
34
|
const headers = new Headers(outbound.headers);
|
|
@@ -54,24 +43,13 @@ export function integrationFetch({ env, provider, for: target, }) {
|
|
|
54
43
|
applyAttribution(headers, target);
|
|
55
44
|
return binding.fetch(streamingRequest(`${BINDING_ORIGIN}${integrationProxyPath(provider, upstream)}`, outbound, headers));
|
|
56
45
|
}
|
|
57
|
-
if (integration.mode === "oauth_client") {
|
|
58
|
-
return oauthClientCall({
|
|
59
|
-
provider: provider,
|
|
60
|
-
capability: integration,
|
|
61
|
-
outbound,
|
|
62
|
-
upstream,
|
|
63
|
-
headers,
|
|
64
|
-
values,
|
|
65
|
-
...(binding
|
|
66
|
-
? { binding, target }
|
|
67
|
-
: { client: local }),
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
46
|
// In local dev a provider's host setting (`.dev.vars`) adds its host,
|
|
71
47
|
// as the gateway's setting does there.
|
|
72
|
-
const hosts =
|
|
73
|
-
? [...integration.
|
|
74
|
-
:
|
|
48
|
+
const hosts = integration.mode === "oauth_client"
|
|
49
|
+
? [...integration.api]
|
|
50
|
+
: binding
|
|
51
|
+
? [...integration.hosts]
|
|
52
|
+
: apiKeyHosts(integration, values);
|
|
75
53
|
if (upstream.protocol !== "https:" || !hosts.includes(upstream.host)) {
|
|
76
54
|
throw new TypeError(`integrationFetch("${provider}") only reaches ${hosts.join(", ")}, not ${upstream.host}.`);
|
|
77
55
|
}
|
|
@@ -80,6 +58,16 @@ export function integrationFetch({ env, provider, for: target, }) {
|
|
|
80
58
|
return binding.fetch(streamingRequest(`${BINDING_ORIGIN}${integrationProxyPath(provider, upstream)}`, outbound, headers));
|
|
81
59
|
}
|
|
82
60
|
applyAttribution(headers, undefined);
|
|
61
|
+
if (integration.mode === "oauth_client") {
|
|
62
|
+
return devHeldTokenCall({
|
|
63
|
+
provider,
|
|
64
|
+
capability: integration,
|
|
65
|
+
outbound,
|
|
66
|
+
upstream,
|
|
67
|
+
headers,
|
|
68
|
+
values,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
83
71
|
// The developer's own key, put where the provider's entry says, as the
|
|
84
72
|
// gateway would put its own.
|
|
85
73
|
const auth = local;
|
|
@@ -93,233 +81,100 @@ export function integrationFetch({ env, provider, for: target, }) {
|
|
|
93
81
|
};
|
|
94
82
|
}
|
|
95
83
|
/**
|
|
96
|
-
* The
|
|
97
|
-
*
|
|
84
|
+
* The `.dev.vars` name of a held token for `provider` in local dev
|
|
85
|
+
* (`GOOGLE_WORKSPACE_ACCESS_TOKEN`, `GITHUB_ACCESS_TOKEN`): a token the
|
|
86
|
+
* developer obtained themselves, standing in for the connection the
|
|
87
|
+
* gateway would hold. Dev-only: the gateway never reads it.
|
|
98
88
|
*/
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
return null;
|
|
102
|
-
if (integration.mode === "oauth_client") {
|
|
103
|
-
return oauthClientCredentials(integration, values);
|
|
104
|
-
}
|
|
105
|
-
return authorizationFor(integration, values);
|
|
89
|
+
export function devAccessTokenName(provider) {
|
|
90
|
+
return `${providerSettingPrefix(provider)}ACCESS_TOKEN`;
|
|
106
91
|
}
|
|
107
92
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
93
|
+
* Where a person connects `provider` in this app:
|
|
94
|
+
* `/connect/<id>?app=<EVERYAPP_APP_ID>`, as the gateway's refusal and
|
|
95
|
+
* status name it (without the app id when the env has none).
|
|
111
96
|
*/
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// app sees in `everyapp dev` what the gateway will say.
|
|
130
|
-
const policy = capability.scopePolicy;
|
|
131
|
-
const grant = policy
|
|
132
|
-
? devScopeSets(policy, provider, capability.label, values)
|
|
133
|
-
: null;
|
|
134
|
-
if (grant && "problem" in grant) {
|
|
135
|
-
return devRefusal(403, grant.code, grant.problem);
|
|
136
|
-
}
|
|
137
|
-
const sets = grant?.sets;
|
|
138
|
-
const body = method === "GET" ? null : await outbound.text();
|
|
139
|
-
if (body !== null &&
|
|
140
|
-
new TextEncoder().encode(body).byteLength > OAUTH_TOKEN_BODY_LIMIT) {
|
|
141
|
-
throw new TypeError(`A token request's body is at most ${OAUTH_TOKEN_BODY_LIMIT / 1024} KiB.`);
|
|
142
|
-
}
|
|
143
|
-
if (policy &&
|
|
144
|
-
["grant_type", "scope", "user_scope"].some((name) => upstream.searchParams.has(name))) {
|
|
145
|
-
return devRefusal(400, "integration_body_invalid", "Put grant_type, scope and user_scope in the token request's body, not its query string; the gateway decides on the body.");
|
|
97
|
+
function appConnectPath(provider, values) {
|
|
98
|
+
const app = stringValue(values[APP_ID_ENV]);
|
|
99
|
+
return app
|
|
100
|
+
? `${connectPath(provider)}?app=${encodeURIComponent(app)}`
|
|
101
|
+
: connectPath(provider);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* An app's call to an OAuth provider's API in local dev, with no
|
|
105
|
+
* gateway: the developer's own token from `.dev.vars` as the bearer,
|
|
106
|
+
* straight to the provider (the host was checked already), or the
|
|
107
|
+
* gateway's `integration_not_connected` refusal without one.
|
|
108
|
+
*/
|
|
109
|
+
async function devHeldTokenCall({ provider, capability, outbound, upstream, headers, values, }) {
|
|
110
|
+
const token = stringValue(values[devAccessTokenName(provider)]);
|
|
111
|
+
if (!token) {
|
|
112
|
+
const path = appConnectPath(provider, values);
|
|
113
|
+
return devRefusal(409, "integration_not_connected", `${capability.label} isn't connected here. In local dev, set ${devAccessTokenName(provider)} in .dev.vars to a token you obtained yourself; deployed, the person connects in the app at ${path}.`, { connectPath: path, reconnect: false });
|
|
146
114
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const refused = refuseTokenRequest(policy, capability, sets, injected);
|
|
152
|
-
if (refused)
|
|
153
|
-
return refused;
|
|
154
|
-
injected = {
|
|
155
|
-
ok: true,
|
|
156
|
-
...rewriteTokenRequestScope(policy, sets, injected),
|
|
157
|
-
};
|
|
115
|
+
const method = outbound.method.toUpperCase();
|
|
116
|
+
headers.set("authorization", `Bearer ${token}`);
|
|
117
|
+
for (const name of ["access_token", "token"]) {
|
|
118
|
+
upstream.searchParams.delete(name);
|
|
158
119
|
}
|
|
159
|
-
|
|
120
|
+
headers.delete("x-http-method-override");
|
|
121
|
+
return fetch(new Request(upstream.toString(), {
|
|
160
122
|
method,
|
|
161
|
-
headers
|
|
162
|
-
body:
|
|
123
|
+
headers,
|
|
124
|
+
body: method === "GET" || method === "HEAD" ? null : outbound.body,
|
|
163
125
|
signal: outbound.signal,
|
|
164
|
-
// As the gateway: the
|
|
126
|
+
// As the gateway: the token never follows a redirect.
|
|
165
127
|
redirect: "manual",
|
|
128
|
+
...(method === "GET" || method === "HEAD" ? {} : { duplex: "half" }),
|
|
166
129
|
}));
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The developer's own key from `.dev.vars` (with EVERYAPP_DEV set), under
|
|
133
|
+
* the gateway's names; null until every setting it needs is.
|
|
134
|
+
*/
|
|
135
|
+
function localAuthorization(integration, values) {
|
|
136
|
+
if (!values["EVERYAPP_DEV"])
|
|
137
|
+
return null;
|
|
138
|
+
return authorizationFor(integration, values);
|
|
170
139
|
}
|
|
171
140
|
/**
|
|
172
141
|
* The gateway's refusal shape (`{ error, code, message }`, the code in
|
|
173
142
|
* `x-everyapp-error`) from the local-dev twin, so {@link gatewayRefusal}
|
|
174
143
|
* reads it the same way.
|
|
175
144
|
*/
|
|
176
|
-
function devRefusal(status, code, message) {
|
|
177
|
-
const labels = {
|
|
178
|
-
|
|
179
|
-
403: "Forbidden",
|
|
180
|
-
502: "Bad Gateway",
|
|
181
|
-
};
|
|
182
|
-
return Response.json({ error: labels[status] ?? "Error", code, message }, { status, headers: { [GATEWAY_ERROR_HEADER]: code } });
|
|
145
|
+
function devRefusal(status, code, message, extra = {}) {
|
|
146
|
+
const labels = { 409: "Conflict" };
|
|
147
|
+
return Response.json({ error: labels[status] ?? "Error", code, message, ...extra }, { status, headers: { [GATEWAY_ERROR_HEADER]: code } });
|
|
183
148
|
}
|
|
184
149
|
/**
|
|
185
|
-
* The
|
|
150
|
+
* The manifest's entry for `id` in local dev, from
|
|
186
151
|
* EVERYAPP_DEV_CAPABILITIES (the manifest's `capabilities` as JSON, which
|
|
187
|
-
* `everyAppWorker()` writes in dev)
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* missing, the entry without usable `scopes`, or a set this registry
|
|
191
|
-
* doesn't offer). Fails closed like the gateway: a hand-rolled dev harness
|
|
192
|
-
* that passes no capabilities gets nothing.
|
|
152
|
+
* `everyAppWorker()` writes in dev): the entry, `null` when the manifest
|
|
153
|
+
* doesn't list it, `undefined` when the variable is missing or isn't JSON
|
|
154
|
+
* (a hand-rolled harness), so nothing is known.
|
|
193
155
|
*/
|
|
194
|
-
function
|
|
195
|
-
const offered = Object.keys(policy.sets).join(", ");
|
|
156
|
+
function devManifestEntry(id, values) {
|
|
196
157
|
const raw = values["EVERYAPP_DEV_CAPABILITIES"];
|
|
158
|
+
if (typeof raw !== "string")
|
|
159
|
+
return undefined;
|
|
197
160
|
let capabilities;
|
|
198
161
|
try {
|
|
199
|
-
capabilities =
|
|
200
|
-
typeof raw === "string" ? jsonRecord(JSON.parse(raw)) : undefined;
|
|
162
|
+
capabilities = jsonRecord(JSON.parse(raw));
|
|
201
163
|
}
|
|
202
164
|
catch {
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
if (!capabilities) {
|
|
206
|
-
return {
|
|
207
|
-
code: "integration_scopes_undeclared",
|
|
208
|
-
problem: `${label}'s scope sets aren't known here: everyapp dev passes the manifest's capabilities to the Worker as EVERYAPP_DEV_CAPABILITIES when everyapp.config.ts builds it with everyAppWorker() from @every-app/sdk/cloudflare.`,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
const entry = Object.hasOwn(capabilities, id)
|
|
212
|
-
? jsonRecord(capabilities[id])
|
|
213
|
-
: undefined;
|
|
214
|
-
// The gateway's check 2: listing the client for writing is the grant.
|
|
215
|
-
if (entry?.["access"] !== "write") {
|
|
216
|
-
return {
|
|
217
|
-
code: "capability_not_requested",
|
|
218
|
-
problem: `This app doesn't list ${label} in its capabilities. Add capabilities: { ${JSON.stringify(id)}: { access: "write" } } to everyapp.config.ts and start everyapp dev again.`,
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
const sets = declaredScopeSets(policy, entry);
|
|
222
|
-
if (sets)
|
|
223
|
-
return { sets };
|
|
224
|
-
const listed = entry["scopes"];
|
|
225
|
-
const unknown = Array.isArray(listed)
|
|
226
|
-
? listed.filter((name) => typeof name === "string" && !Object.hasOwn(policy.sets, name))
|
|
227
|
-
: [];
|
|
228
|
-
if (unknown.length > 0) {
|
|
229
|
-
return {
|
|
230
|
-
code: "integration_scopes_undeclared",
|
|
231
|
-
problem: `This app's ${label} entry names scope sets this SDK doesn't offer: ${unknown.join(", ")}. It offers: ${offered}. Fix everyapp.config.ts and start everyapp dev again, or update @every-app/sdk if it is older than the set.`,
|
|
232
|
-
};
|
|
165
|
+
return undefined;
|
|
233
166
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
167
|
+
if (!capabilities)
|
|
168
|
+
return undefined;
|
|
169
|
+
return Object.hasOwn(capabilities, id)
|
|
170
|
+
? (jsonRecord(capabilities[id]) ?? null)
|
|
171
|
+
: null;
|
|
238
172
|
}
|
|
239
173
|
function jsonRecord(value) {
|
|
240
174
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
241
175
|
? value
|
|
242
176
|
: undefined;
|
|
243
177
|
}
|
|
244
|
-
/**
|
|
245
|
-
* The gateway's checks on a token request before it goes out: the grant
|
|
246
|
-
* type must be one the policy completes, and a `scope` the body names must
|
|
247
|
-
* stay within the sets. Null when the request may go.
|
|
248
|
-
*/
|
|
249
|
-
function refuseTokenRequest(policy, capability, sets, request) {
|
|
250
|
-
const grantType = tokenGrantType(request);
|
|
251
|
-
if (!policy.grantTypes.includes(grantType)) {
|
|
252
|
-
const completes = policy.grantTypes.map((type) => type === null ? "a code exchange without grant_type" : type);
|
|
253
|
-
const asked = grantType === null
|
|
254
|
-
? "a request without grant_type"
|
|
255
|
-
: grantType === "other"
|
|
256
|
-
? "this request's grant_type"
|
|
257
|
-
: grantType;
|
|
258
|
-
return devRefusal(403, "integration_grant_not_allowed", `Through the gateway, ${capability.label} completes ${completes.join(" and ")} grants only; ${asked} isn't one of them. An app never gets app-level tokens with the operator's client.`);
|
|
259
|
-
}
|
|
260
|
-
const asked = requestedTokenScopes(policy, request);
|
|
261
|
-
const excess = asked ? scopeExcess(asked, allowedScopes(policy, sets)) : [];
|
|
262
|
-
if (excess.length > 0) {
|
|
263
|
-
return devRefusal(403, "integration_scope_not_allowed", `This app may ask ${capability.label} for ${sets.join(", ")} (${requestedScopes(policy, sets).scopes.join(", ")}); the request also asks ${excess.join(", ")}. Build the authorize URL and the token request from integrationStatus().scopes.`);
|
|
264
|
-
}
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* The gateway's check on a provider's 2xx answer: a token it carries must
|
|
269
|
-
* name scopes, and those must stay within the sets; on a refusal the
|
|
270
|
-
* answer is dropped, never forwarded, so no app holds a token outside its
|
|
271
|
-
* grant. An answer without a token (Slack's `ok: false`) is the app's to
|
|
272
|
-
* read. A non-2xx answer never reaches here.
|
|
273
|
-
*/
|
|
274
|
-
async function checkTokenResponse(policy, capability, sets, response) {
|
|
275
|
-
const invalid = () => devRefusal(502, "integration_response_invalid", `${capability.label} answered with something that isn't a token response (over ${OAUTH_TOKEN_BODY_LIMIT / 1024} KiB or not a JSON object); nothing was passed on.`);
|
|
276
|
-
const bytes = await response.arrayBuffer();
|
|
277
|
-
if (bytes.byteLength > OAUTH_TOKEN_BODY_LIMIT)
|
|
278
|
-
return invalid();
|
|
279
|
-
const text = new TextDecoder().decode(bytes);
|
|
280
|
-
let json;
|
|
281
|
-
try {
|
|
282
|
-
json = JSON.parse(text);
|
|
283
|
-
}
|
|
284
|
-
catch {
|
|
285
|
-
return invalid();
|
|
286
|
-
}
|
|
287
|
-
if (!json || typeof json !== "object" || Array.isArray(json)) {
|
|
288
|
-
return invalid();
|
|
289
|
-
}
|
|
290
|
-
// The body was decoded on read: its length and encoding headers no
|
|
291
|
-
// longer describe it.
|
|
292
|
-
const headers = new Headers(response.headers);
|
|
293
|
-
for (const name of [
|
|
294
|
-
"set-cookie",
|
|
295
|
-
"content-length",
|
|
296
|
-
"content-encoding",
|
|
297
|
-
"transfer-encoding",
|
|
298
|
-
]) {
|
|
299
|
-
headers.delete(name);
|
|
300
|
-
}
|
|
301
|
-
const forward = () => new Response(text, {
|
|
302
|
-
status: response.status,
|
|
303
|
-
statusText: response.statusText,
|
|
304
|
-
headers,
|
|
305
|
-
});
|
|
306
|
-
if (!carriesToken(json))
|
|
307
|
-
return forward();
|
|
308
|
-
const granted = grantedScopes(policy, json);
|
|
309
|
-
if (!granted) {
|
|
310
|
-
return devRefusal(502, "integration_scope_unverified", `${capability.label} issued a token without saying which scopes it carries, so the gateway can't hold it to this app's scope sets; nothing was passed on. Ask the person to connect again.`);
|
|
311
|
-
}
|
|
312
|
-
const excess = scopeExcess(granted, allowedScopes(policy, sets));
|
|
313
|
-
if (excess.length > 0) {
|
|
314
|
-
const remedy = {
|
|
315
|
-
google: "Ask for only the scopes integrationStatus() returns, without include_granted_scopes, and connect again; if the app needs more, add the scope set to everyapp.config.ts and deploy.",
|
|
316
|
-
microsoft: "On a shared Microsoft client a token carries everything the person consented to this client: have them remove this app's consent at https://myapplications.microsoft.com and connect again, or add the scope set to everyapp.config.ts and deploy.",
|
|
317
|
-
slack: "Slack scopes add up per workspace installation: reinstall the app in the workspace with only the status's scopes, or add the scope set to everyapp.config.ts and deploy.",
|
|
318
|
-
}[policy.dialect];
|
|
319
|
-
return devRefusal(403, "integration_scope_exceeded", `${capability.label} granted scopes this app doesn't declare: ${excess.join(", ")}. It declares ${sets.join(", ")} (${requestedScopes(policy, sets).scopes.join(", ")}). Nothing was passed on. ${remedy}`);
|
|
320
|
-
}
|
|
321
|
-
return forward();
|
|
322
|
-
}
|
|
323
178
|
/**
|
|
324
179
|
* A provider outside the catalog in local dev (no binding): the gateway
|
|
325
180
|
* row's spec from EVERYAPP_DEV_PROVIDERS (`.everyapp/providers/<id>.json`,
|
|
@@ -333,6 +188,9 @@ function devRegisteredProvider(provider, values) {
|
|
|
333
188
|
const resolved = resolveDevProvider(values, provider);
|
|
334
189
|
if (!resolved.found)
|
|
335
190
|
throw new ConfigurationError(resolved.problem);
|
|
191
|
+
if (resolved.capability.mode === "ai") {
|
|
192
|
+
throw new ConfigurationError(`${provider} is an AI provider: list it under providers and call it with createGatewayFetch(), not integrationFetch().`);
|
|
193
|
+
}
|
|
336
194
|
return resolved.capability;
|
|
337
195
|
}
|
|
338
196
|
/**
|
|
@@ -358,12 +216,64 @@ function stringValue(value) {
|
|
|
358
216
|
/**
|
|
359
217
|
* Whether the app can use an integration right now, without calling the
|
|
360
218
|
* provider: show "an admin needs to set up DataForSEO" (or "sending isn't
|
|
361
|
-
* set up") up front instead of failing mid-task. An
|
|
362
|
-
* carries
|
|
219
|
+
* set up", or "connect Google") up front instead of failing mid-task. An
|
|
220
|
+
* OAuth provider's status carries the person's connection in this app and
|
|
221
|
+
* where to connect.
|
|
222
|
+
*/
|
|
223
|
+
export async function integrationStatus({ env, provider, for: explicitTarget, }) {
|
|
224
|
+
const status = await readIntegrationStatus({
|
|
225
|
+
env,
|
|
226
|
+
provider,
|
|
227
|
+
...(explicitTarget === undefined ? {} : { for: explicitTarget }),
|
|
228
|
+
});
|
|
229
|
+
return withSetup(status, String(provider), env);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* `status` with its setup fields: the Gateway's as it sent them, or in
|
|
233
|
+
* local development (no binding) the `.dev.vars` names a status that
|
|
234
|
+
* isn't configured lacks.
|
|
363
235
|
*/
|
|
364
|
-
|
|
236
|
+
function withSetup(status, provider, values) {
|
|
237
|
+
const binding = gatewayBinding(values);
|
|
238
|
+
if (binding || !status.requested || status.configured) {
|
|
239
|
+
return {
|
|
240
|
+
...status,
|
|
241
|
+
settings: Array.isArray(status.settings) ? status.settings : [],
|
|
242
|
+
setup: typeof status.setup === "string" ? status.setup : null,
|
|
243
|
+
command: typeof status.command === "string" ? status.command : null,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const integration = CATALOG.get(provider) ?? devRegisteredProvider(provider, values);
|
|
247
|
+
const label = integration?.label ?? provider;
|
|
248
|
+
if (integration?.mode === "oauth_client") {
|
|
249
|
+
// In dev a token stands in for the connection; the client is the
|
|
250
|
+
// deployed gateway's alone.
|
|
251
|
+
const name = devAccessTokenName(provider);
|
|
252
|
+
return {
|
|
253
|
+
...status,
|
|
254
|
+
settings: [name],
|
|
255
|
+
setup: `${label} isn't connected for local development: set ${name} in .dev.vars to a token you obtained yourself.`,
|
|
256
|
+
command: null,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const settings = provider === "email"
|
|
260
|
+
? ["EMAIL_FROM"]
|
|
261
|
+
: integration
|
|
262
|
+
? [...capabilitySettings(integration)]
|
|
263
|
+
: [];
|
|
264
|
+
return {
|
|
265
|
+
...status,
|
|
266
|
+
settings,
|
|
267
|
+
setup: settings.length
|
|
268
|
+
? `${label} isn't set up for local development: set ${settings.join(" and ")} in .dev.vars.`
|
|
269
|
+
: `${label} isn't set up for local development.`,
|
|
270
|
+
command: null,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
async function readIntegrationStatus({ env, provider, for: explicitTarget, }) {
|
|
274
|
+
const target = attributionFor(env, explicitTarget, `integrationStatus("${String(provider)}")`);
|
|
365
275
|
const values = env;
|
|
366
|
-
const binding =
|
|
276
|
+
const binding = gatewayBinding(env);
|
|
367
277
|
// A provider registered on the gateway is the gateway's to answer for;
|
|
368
278
|
// without one, the catalog, or in local dev the row's spec from
|
|
369
279
|
// `.everyapp/providers/<id>.json`.
|
|
@@ -383,13 +293,34 @@ export async function integrationStatus({ env, provider, }) {
|
|
|
383
293
|
senders: [],
|
|
384
294
|
provider: null,
|
|
385
295
|
}
|
|
386
|
-
:
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
296
|
+
: integration?.mode === "oauth_client"
|
|
297
|
+
? {
|
|
298
|
+
requested: false,
|
|
299
|
+
configured: false,
|
|
300
|
+
ready: false,
|
|
301
|
+
scopes: [],
|
|
302
|
+
connectPath: null,
|
|
303
|
+
connection: null,
|
|
304
|
+
}
|
|
305
|
+
: {
|
|
306
|
+
requested: false,
|
|
307
|
+
configured: false,
|
|
308
|
+
ready: false,
|
|
309
|
+
});
|
|
391
310
|
if (binding && typeof binding.fetch === "function") {
|
|
392
|
-
const
|
|
311
|
+
const statusUrl = `${BINDING_ORIGIN}${INTEGRATION_PROXY_PREFIX}/${provider}`;
|
|
312
|
+
let response;
|
|
313
|
+
if (target === undefined) {
|
|
314
|
+
response = await binding.fetch(statusUrl);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
// Whose status: the person's identity (their connection in this
|
|
318
|
+
// app), the member background work names, or the organization's,
|
|
319
|
+
// as a call would carry it.
|
|
320
|
+
const headers = new Headers();
|
|
321
|
+
applyAttribution(headers, target);
|
|
322
|
+
response = await binding.fetch(statusUrl, { headers });
|
|
323
|
+
}
|
|
393
324
|
if (!response.ok)
|
|
394
325
|
return unavailable();
|
|
395
326
|
return (await response.json());
|
|
@@ -409,7 +340,7 @@ export async function integrationStatus({ env, provider, }) {
|
|
|
409
340
|
};
|
|
410
341
|
}
|
|
411
342
|
if (integration.mode === "oauth_client") {
|
|
412
|
-
return
|
|
343
|
+
return devOAuthStatus(String(provider), values);
|
|
413
344
|
}
|
|
414
345
|
const configured = Boolean(values["EVERYAPP_DEV"] && authorizationFor(integration, values));
|
|
415
346
|
return {
|
|
@@ -419,48 +350,36 @@ export async function integrationStatus({ env, provider, }) {
|
|
|
419
350
|
};
|
|
420
351
|
}
|
|
421
352
|
/**
|
|
422
|
-
* An OAuth
|
|
423
|
-
*
|
|
424
|
-
* EVERYAPP_DEV_CAPABILITIES,
|
|
425
|
-
*
|
|
426
|
-
*
|
|
353
|
+
* An OAuth provider in local dev, with no gateway, shaped as the
|
|
354
|
+
* gateway's status is: the scopes from the manifest's entry
|
|
355
|
+
* (EVERYAPP_DEV_CAPABILITIES), and a token in `.dev.vars` standing in for
|
|
356
|
+
* a connection that holds them all. An app whose manifest doesn't list
|
|
357
|
+
* the provider is told nothing.
|
|
427
358
|
*/
|
|
428
|
-
function
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
: null;
|
|
432
|
-
const origin = oauthClientOrigin(capability, values);
|
|
433
|
-
const policy = capability.scopePolicy;
|
|
434
|
-
const grant = policy
|
|
435
|
-
? devScopeSets(policy, id, capability.label, values)
|
|
436
|
-
: null;
|
|
437
|
-
if (grant && "problem" in grant) {
|
|
438
|
-
if (grant.code === "capability_not_requested") {
|
|
439
|
-
return { requested: false, configured: false, ready: false };
|
|
440
|
-
}
|
|
359
|
+
function devOAuthStatus(id, values) {
|
|
360
|
+
const entry = devManifestEntry(id, values);
|
|
361
|
+
if (entry === null) {
|
|
441
362
|
return {
|
|
442
|
-
requested:
|
|
443
|
-
configured:
|
|
363
|
+
requested: false,
|
|
364
|
+
configured: false,
|
|
444
365
|
ready: false,
|
|
445
|
-
|
|
366
|
+
scopes: [],
|
|
367
|
+
connectPath: null,
|
|
368
|
+
connection: null,
|
|
446
369
|
};
|
|
447
370
|
}
|
|
448
|
-
const
|
|
371
|
+
const listed = entry?.["scopes"];
|
|
372
|
+
const scopes = Array.isArray(listed)
|
|
373
|
+
? listed.filter((scope) => typeof scope === "string")
|
|
374
|
+
: [];
|
|
375
|
+
const token = Boolean(stringValue(values[devAccessTokenName(id)]));
|
|
449
376
|
return {
|
|
450
377
|
requested: true,
|
|
451
|
-
configured:
|
|
452
|
-
ready:
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
? {
|
|
457
|
-
scopeSets: grant.sets,
|
|
458
|
-
scopes: asked.scopes,
|
|
459
|
-
...(asked.userScopes.length > 0
|
|
460
|
-
? { userScopes: asked.userScopes }
|
|
461
|
-
: {}),
|
|
462
|
-
}
|
|
463
|
-
: {}),
|
|
378
|
+
configured: token,
|
|
379
|
+
ready: token,
|
|
380
|
+
scopes,
|
|
381
|
+
connectPath: appConnectPath(id, values),
|
|
382
|
+
connection: token ? { label: null, scopes, ready: true } : null,
|
|
464
383
|
};
|
|
465
384
|
}
|
|
466
385
|
/**
|
|
@@ -492,35 +411,3 @@ function devEmailStatus(values) {
|
|
|
492
411
|
provider: configured && choice.ok ? choice.provider : null,
|
|
493
412
|
};
|
|
494
413
|
}
|
|
495
|
-
/**
|
|
496
|
-
* A refusal the AppGateway binding made, thrown: what `sendEmail()` throws
|
|
497
|
-
* when the gateway won't send (`email_sender_not_allowed`,
|
|
498
|
-
* `email_budget_exhausted`, ...). The thrown twin of {@link gatewayRefusal}.
|
|
499
|
-
*/
|
|
500
|
-
export class GatewayRefusalError extends Error {
|
|
501
|
-
status;
|
|
502
|
-
code;
|
|
503
|
-
constructor({ status, code, message }) {
|
|
504
|
-
super(message);
|
|
505
|
-
this.name = "GatewayRefusalError";
|
|
506
|
-
this.status = status;
|
|
507
|
-
this.code = code;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* The gateway's own refusal in `response`, or null when the response is the
|
|
512
|
-
* provider's answer (which may be an error of its own). Reads the body only
|
|
513
|
-
* for a refusal, so call it before reading the body yourself.
|
|
514
|
-
*/
|
|
515
|
-
export async function gatewayRefusal(response) {
|
|
516
|
-
const code = response.headers.get(GATEWAY_ERROR_HEADER);
|
|
517
|
-
if (!code)
|
|
518
|
-
return null;
|
|
519
|
-
const body = await response.json().catch(() => null);
|
|
520
|
-
const message = body &&
|
|
521
|
-
typeof body === "object" &&
|
|
522
|
-
typeof body.message === "string"
|
|
523
|
-
? body.message
|
|
524
|
-
: `The Every App gateway refused the call (${code}).`;
|
|
525
|
-
return { status: response.status, code, message };
|
|
526
|
-
}
|
|
@@ -28,5 +28,5 @@ export interface ListMembersOptions {
|
|
|
28
28
|
* for a request answers its signed-in user alone, and background work
|
|
29
29
|
* answers no one.
|
|
30
30
|
*/
|
|
31
|
-
export declare function listMembers({ env, for:
|
|
31
|
+
export declare function listMembers({ env, for: explicitTarget, }: ListMembersOptions): Promise<ListMembersResult>;
|
|
32
32
|
//# sourceMappingURL=listMembers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listMembers.d.ts","sourceRoot":"","sources":["../../src/server/listMembers.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,iBAAiB,
|
|
1
|
+
{"version":3,"file":"listMembers.d.ts","sourceRoot":"","sources":["../../src/server/listMembers.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAS1B,MAAM,WAAW,kBAAkB;IACjC,8FAA8F;IAC9F,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,cAAc,CAAC;CACtB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,WAAW,CAAC,EAChC,GAAG,EACH,GAAG,EAAE,cAAc,GACpB,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAkBjD"}
|