@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.
Files changed (181) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -64
  3. package/dist/cloudflare/catalogSettings.generated.d.ts +84 -0
  4. package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -0
  5. package/dist/cloudflare/catalogSettings.generated.js +175 -0
  6. package/dist/cloudflare/index.d.ts +3 -2
  7. package/dist/cloudflare/index.d.ts.map +1 -1
  8. package/dist/cloudflare/index.js +4 -1
  9. package/dist/cloudflare/sentinel.d.ts +60 -2
  10. package/dist/cloudflare/sentinel.d.ts.map +1 -1
  11. package/dist/cloudflare/sentinel.js +37 -2
  12. package/dist/cloudflare/worker.d.ts +391 -63
  13. package/dist/cloudflare/worker.d.ts.map +1 -1
  14. package/dist/cloudflare/worker.js +394 -108
  15. package/dist/dev/index.d.ts +1 -1
  16. package/dist/dev/index.d.ts.map +1 -1
  17. package/dist/dev/index.js +1 -1
  18. package/dist/dev/keypair.d.ts +2 -0
  19. package/dist/dev/keypair.d.ts.map +1 -1
  20. package/dist/dev/keypair.js +2 -0
  21. package/dist/index.d.ts +6 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +5 -0
  24. package/dist/internal/aiDefault.d.ts +205 -0
  25. package/dist/internal/aiDefault.d.ts.map +1 -0
  26. package/dist/internal/aiDefault.js +335 -0
  27. package/dist/internal/aiProviders.d.ts +7 -2
  28. package/dist/internal/aiProviders.d.ts.map +1 -1
  29. package/dist/internal/aiProviders.js +19 -6
  30. package/dist/internal/appSettings.d.ts +82 -4
  31. package/dist/internal/appSettings.d.ts.map +1 -1
  32. package/dist/internal/appSettings.js +204 -29
  33. package/dist/internal/audit.d.ts +108 -0
  34. package/dist/internal/audit.d.ts.map +1 -0
  35. package/dist/internal/audit.js +235 -0
  36. package/dist/internal/capabilities.d.ts +2261 -815
  37. package/dist/internal/capabilities.d.ts.map +1 -1
  38. package/dist/internal/capabilities.js +122 -1034
  39. package/dist/internal/catalog/commerce.d.ts +394 -0
  40. package/dist/internal/catalog/commerce.d.ts.map +1 -0
  41. package/dist/internal/catalog/commerce.js +477 -0
  42. package/dist/internal/catalog/comms.d.ts +375 -0
  43. package/dist/internal/catalog/comms.d.ts.map +1 -0
  44. package/dist/internal/catalog/comms.js +520 -0
  45. package/dist/internal/catalog/index.d.ts +2008 -0
  46. package/dist/internal/catalog/index.d.ts.map +1 -0
  47. package/dist/internal/catalog/index.js +19 -0
  48. package/dist/internal/catalog/inference.d.ts +393 -0
  49. package/dist/internal/catalog/inference.d.ts.map +1 -0
  50. package/dist/internal/catalog/inference.js +460 -0
  51. package/dist/internal/catalog/infra.d.ts +458 -0
  52. package/dist/internal/catalog/infra.d.ts.map +1 -0
  53. package/dist/internal/catalog/infra.js +608 -0
  54. package/dist/internal/catalog/search.d.ts +400 -0
  55. package/dist/internal/catalog/search.d.ts.map +1 -0
  56. package/dist/internal/catalog/search.js +463 -0
  57. package/dist/internal/devProviders.d.ts +2 -2
  58. package/dist/internal/devProviders.d.ts.map +1 -1
  59. package/dist/internal/devProviders.js +19 -1
  60. package/dist/internal/events.d.ts +65 -3
  61. package/dist/internal/events.d.ts.map +1 -1
  62. package/dist/internal/events.js +110 -5
  63. package/dist/internal/gatewaySettings.d.ts +3 -2
  64. package/dist/internal/gatewaySettings.d.ts.map +1 -1
  65. package/dist/internal/gatewaySettings.js +12 -6
  66. package/dist/internal/identity.d.ts +41 -4
  67. package/dist/internal/identity.d.ts.map +1 -1
  68. package/dist/internal/identity.js +48 -10
  69. package/dist/internal/index.d.ts +6 -1
  70. package/dist/internal/index.d.ts.map +1 -1
  71. package/dist/internal/index.js +6 -1
  72. package/dist/internal/members.d.ts +3 -2
  73. package/dist/internal/members.d.ts.map +1 -1
  74. package/dist/internal/oauthBuiltIns.d.ts +101 -0
  75. package/dist/internal/oauthBuiltIns.d.ts.map +1 -0
  76. package/dist/internal/oauthBuiltIns.js +122 -0
  77. package/dist/internal/oauthClient.d.ts +8 -50
  78. package/dist/internal/oauthClient.d.ts.map +1 -1
  79. package/dist/internal/oauthClient.js +2 -177
  80. package/dist/internal/oauthPresets.d.ts +32 -0
  81. package/dist/internal/oauthPresets.d.ts.map +1 -0
  82. package/dist/internal/oauthPresets.js +253 -0
  83. package/dist/internal/oauthScopes.d.ts +43 -100
  84. package/dist/internal/oauthScopes.d.ts.map +1 -1
  85. package/dist/internal/oauthScopes.js +83 -382
  86. package/dist/internal/oauthSpec.d.ts +103 -0
  87. package/dist/internal/oauthSpec.d.ts.map +1 -0
  88. package/dist/internal/oauthSpec.js +520 -0
  89. package/dist/internal/protocol.d.ts +97 -5
  90. package/dist/internal/protocol.d.ts.map +1 -1
  91. package/dist/internal/protocol.js +89 -1
  92. package/dist/internal/providerRegistry.d.ts +17 -6
  93. package/dist/internal/providerRegistry.d.ts.map +1 -1
  94. package/dist/internal/providerRegistry.js +71 -8
  95. package/dist/internal/providerSpec.d.ts +18 -7
  96. package/dist/internal/providerSpec.d.ts.map +1 -1
  97. package/dist/internal/providerSpec.js +420 -34
  98. package/dist/internal/remoteMcp.d.ts.map +1 -1
  99. package/dist/internal/remoteMcp.js +1 -5
  100. package/dist/internal/signInProviders.d.ts +104 -0
  101. package/dist/internal/signInProviders.d.ts.map +1 -0
  102. package/dist/internal/signInProviders.js +245 -0
  103. package/dist/internal/tools.d.ts +5 -4
  104. package/dist/internal/tools.d.ts.map +1 -1
  105. package/dist/internal/tools.js +4 -3
  106. package/dist/manifest.d.ts +267 -0
  107. package/dist/manifest.d.ts.map +1 -0
  108. package/dist/manifest.js +30 -0
  109. package/dist/server/ai.d.ts +111 -0
  110. package/dist/server/ai.d.ts.map +1 -0
  111. package/dist/server/ai.js +145 -0
  112. package/dist/server/aiBinding.d.ts +68 -0
  113. package/dist/server/aiBinding.d.ts.map +1 -0
  114. package/dist/server/aiBinding.js +294 -0
  115. package/dist/server/attribution.d.ts +18 -5
  116. package/dist/server/attribution.d.ts.map +1 -1
  117. package/dist/server/attribution.js +19 -5
  118. package/dist/server/connectUrl.d.ts +16 -0
  119. package/dist/server/connectUrl.d.ts.map +1 -0
  120. package/dist/server/connectUrl.js +40 -0
  121. package/dist/server/context.d.ts +68 -0
  122. package/dist/server/context.d.ts.map +1 -0
  123. package/dist/server/context.js +117 -0
  124. package/dist/server/emailBinding.d.ts +72 -0
  125. package/dist/server/emailBinding.d.ts.map +1 -0
  126. package/dist/server/emailBinding.js +124 -0
  127. package/dist/server/emit.d.ts +1 -1
  128. package/dist/server/emit.d.ts.map +1 -1
  129. package/dist/server/emit.js +7 -7
  130. package/dist/server/env.d.ts +44 -0
  131. package/dist/server/env.d.ts.map +1 -0
  132. package/dist/server/env.js +1 -0
  133. package/dist/server/eventsEndpoint.d.ts +48 -0
  134. package/dist/server/eventsEndpoint.d.ts.map +1 -0
  135. package/dist/server/eventsEndpoint.js +145 -0
  136. package/dist/server/everyApp.d.ts +40 -11
  137. package/dist/server/everyApp.d.ts.map +1 -1
  138. package/dist/server/everyApp.js +116 -6
  139. package/dist/server/fromMcpTools.d.ts +89 -0
  140. package/dist/server/fromMcpTools.d.ts.map +1 -0
  141. package/dist/server/fromMcpTools.js +192 -0
  142. package/dist/server/gatewayFetch.d.ts +42 -18
  143. package/dist/server/gatewayFetch.d.ts.map +1 -1
  144. package/dist/server/gatewayFetch.js +138 -19
  145. package/dist/server/gatewayUrl.d.ts +13 -0
  146. package/dist/server/gatewayUrl.d.ts.map +1 -0
  147. package/dist/server/gatewayUrl.js +34 -0
  148. package/dist/server/index.d.ts +16 -4
  149. package/dist/server/index.d.ts.map +1 -1
  150. package/dist/server/index.js +13 -3
  151. package/dist/server/integrationFetch.d.ts +92 -73
  152. package/dist/server/integrationFetch.d.ts.map +1 -1
  153. package/dist/server/integrationFetch.js +206 -319
  154. package/dist/server/listMembers.d.ts +1 -1
  155. package/dist/server/listMembers.d.ts.map +1 -1
  156. package/dist/server/listMembers.js +8 -11
  157. package/dist/server/refusal.d.ts +55 -0
  158. package/dist/server/refusal.d.ts.map +1 -0
  159. package/dist/server/refusal.js +193 -0
  160. package/dist/server/sendEmail.d.ts +1 -1
  161. package/dist/server/sendEmail.d.ts.map +1 -1
  162. package/dist/server/sendEmail.js +7 -7
  163. package/dist/server/tools.d.ts +7 -2
  164. package/dist/server/tools.d.ts.map +1 -1
  165. package/dist/server/tools.js +2 -1
  166. package/dist/server/toolsEndpoint.d.ts +3 -1
  167. package/dist/server/toolsEndpoint.d.ts.map +1 -1
  168. package/dist/server/toolsEndpoint.js +19 -7
  169. package/dist/server/webhooks.d.ts +56 -0
  170. package/dist/server/webhooks.d.ts.map +1 -0
  171. package/dist/server/webhooks.js +205 -0
  172. package/dist/tanstack-start/index.d.ts +9 -0
  173. package/dist/tanstack-start/index.d.ts.map +1 -0
  174. package/dist/tanstack-start/index.js +29 -0
  175. package/dist/testing/index.d.ts +93 -0
  176. package/dist/testing/index.d.ts.map +1 -0
  177. package/dist/testing/index.js +156 -0
  178. package/package.json +35 -14
  179. package/dist/server/authorizeUrl.d.ts +0 -43
  180. package/dist/server/authorizeUrl.d.ts.map +0 -1
  181. package/dist/server/authorizeUrl.js +0 -110
@@ -1,33 +1,15 @@
1
- import { CATALOG, ConfigurationError, GATEWAY_ERROR_HEADER, INTEGRATION_PROXY_PREFIX, OAUTH_TOKEN_BODY_LIMIT, allowedScopes, apiKeyHosts, authorizationFor, capabilitySettings, carriesToken, declaredScopeSets, describeTokenEndpoints, devProviderFile, grantedScopes, injectClientCredentials, integrationProxyPath, oauthClientCredentials, oauthClientOrigin, requestedScopes, requestedTokenScopes, resolveDevProvider, rewriteTokenRequestScope, scopeExcess, tokenEndpointFor, tokenGrantType, EMAIL_SENDERS_ENV, chooseEmailProvider, parseEmailList, parseEmailSender, } from "../internal/index.js";
2
- import { applyAttribution, checkAttribution, } from "./attribution.js";
3
- import { BINDING_ORIGIN, streamingRequest, } from "./gatewayFetch.js";
4
- /**
5
- * A `fetch` for one of the gateway's capabilities. Call the provider's real
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
- checkAttribution(env, target, `integrationFetch("${provider}")`);
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 = values["EVERY_APP_GATEWAY"];
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
- const local = binding || !integration ? null : localCredentials(integration, values);
39
- if (!binding && !local && integration) {
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 = binding
73
- ? [...integration.hosts]
74
- : apiKeyHosts(integration, values);
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 developer's own key or client from `.dev.vars` (with EVERYAPP_DEV
97
- * set), under the gateway's names; null until every setting it needs is.
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 localCredentials(integration, values) {
100
- if (!values["EVERYAPP_DEV"])
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
- * A call to an OAuth client's token endpoint: over the binding, where the
109
- * gateway puts its client on it, or in local dev straight to the provider
110
- * with the developer's client put on it by the gateway's own rule.
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
- async function oauthClientCall({ provider, capability, outbound, upstream, headers, values, binding, target, client, }) {
113
- // With the binding the gateway resolves the provider's origin (Mastodon's
114
- // instance) and refuses any other, so any origin stands in for it here.
115
- const settings = binding && capability.hostSetting
116
- ? { [capability.hostSetting]: upstream.origin }
117
- : values;
118
- const endpoint = tokenEndpointFor(capability, upstream, settings);
119
- const method = outbound.method.toUpperCase();
120
- if (!endpoint || !endpoint.methods.includes(method)) {
121
- throw new TypeError(`integrationFetch("${provider}") only reaches ${capability.label}'s token endpoints (${describeTokenEndpoints(capability)}), not ${method} ${upstream.origin}${upstream.pathname}. Call its API with the user's token directly.`);
122
- }
123
- if (binding) {
124
- applyAttribution(headers, target);
125
- return binding.fetch(streamingRequest(`${BINDING_ORIGIN}${integrationProxyPath(provider, upstream)}`, outbound, headers));
126
- }
127
- applyAttribution(headers, undefined);
128
- // The gateway's scope checks, in its order, answering its refusals: an
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
- let injected = injectClientCredentials({ url: upstream, headers, body }, endpoint, client);
148
- if (!injected.ok)
149
- throw new TypeError(injected.problem);
150
- if (policy && sets) {
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
- const response = await fetch(new Request(injected.url, {
120
+ headers.delete("x-http-method-override");
121
+ return fetch(new Request(upstream.toString(), {
160
122
  method,
161
- headers: injected.headers,
162
- body: injected.body,
123
+ headers,
124
+ body: method === "GET" || method === "HEAD" ? null : outbound.body,
163
125
  signal: outbound.signal,
164
- // As the gateway: the client never follows a redirect.
126
+ // As the gateway: the token never follows a redirect.
165
127
  redirect: "manual",
128
+ ...(method === "GET" || method === "HEAD" ? {} : { duplex: "half" }),
166
129
  }));
167
- if (!policy || !sets || !response.ok)
168
- return response;
169
- return checkTokenResponse(policy, capability, sets, response);
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
- 400: "Bad Request",
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 scope sets the manifest declares for `id`, read from
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), or the gateway's refusal, in its
188
- * order: the client not listed for writing (`capability_not_requested`),
189
- * then a void grant (`integration_scopes_undeclared`: the variable
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 devScopeSets(policy, id, label, values) {
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
- capabilities = undefined;
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
- return {
235
- code: "integration_scopes_undeclared",
236
- problem: `This app lists ${label} without the scope sets it uses. Add scopes: [...] (one or more of: ${offered}) to its ${JSON.stringify(id)} entry in everyapp.config.ts and start everyapp dev again.`,
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 OAuth client's status
362
- * carries its public `clientId` for the authorize URL.
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
- export async function integrationStatus({ env, provider, }) {
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 = values["EVERY_APP_GATEWAY"];
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
- requested: false,
388
- configured: false,
389
- ready: false,
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 response = await binding.fetch(`${BINDING_ORIGIN}${INTEGRATION_PROXY_PREFIX}/${provider}`);
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 devOAuthClientStatus(provider, integration, values);
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 client in local dev, with no gateway: the developer's own, from
423
- * `.dev.vars`, and for a client with scope sets the manifest's, from
424
- * EVERYAPP_DEV_CAPABILITIES, shaped as the gateway's status is: an app
425
- * that doesn't list the client is told nothing, and a void grant is not
426
- * ready and hides the client id.
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 devOAuthClientStatus(id, capability, values) {
429
- const client = values["EVERYAPP_DEV"]
430
- ? oauthClientCredentials(capability, values)
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: true,
443
- configured: Boolean(client),
363
+ requested: false,
364
+ configured: false,
444
365
  ready: false,
445
- scopeSets: [],
366
+ scopes: [],
367
+ connectPath: null,
368
+ connection: null,
446
369
  };
447
370
  }
448
- const asked = policy && grant ? requestedScopes(policy, grant.sets) : null;
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: Boolean(client),
452
- ready: Boolean(client),
453
- ...(client ? { clientId: client.clientId } : {}),
454
- ...(client && origin ? { origin } : {}),
455
- ...(client && grant && asked
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: target, }: ListMembersOptions): Promise<ListMembersResult>;
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,EAEvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAK1B,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,MAAM,GACZ,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAoBjD"}
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"}