@every-app/sdk 0.3.0-rc.9 → 0.3.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/README.md +75 -74
- package/dist/cloudflare/catalogSettings.generated.d.ts +19 -6
- package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -1
- package/dist/cloudflare/catalogSettings.generated.js +43 -14
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/index.d.ts.map +1 -1
- package/dist/cloudflare/worker.d.ts +63 -9
- package/dist/cloudflare/worker.d.ts.map +1 -1
- package/dist/cloudflare/worker.js +45 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/internal/aiDefault.d.ts +36 -79
- package/dist/internal/aiDefault.d.ts.map +1 -1
- package/dist/internal/aiDefault.js +47 -149
- package/dist/internal/aiProviders.d.ts +1 -1
- package/dist/internal/aiProviders.d.ts.map +1 -1
- package/dist/internal/aiProviders.js +2 -2
- package/dist/internal/appSettings.d.ts +140 -89
- package/dist/internal/appSettings.d.ts.map +1 -1
- package/dist/internal/appSettings.js +264 -156
- package/dist/internal/capabilities.d.ts +108 -503
- package/dist/internal/capabilities.d.ts.map +1 -1
- package/dist/internal/capabilities.js +29 -550
- package/dist/internal/catalog/commerce.js +1 -1
- package/dist/internal/catalog/index.d.ts +35 -0
- package/dist/internal/catalog/index.d.ts.map +1 -1
- package/dist/internal/catalog/infra.d.ts +35 -0
- package/dist/internal/catalog/infra.d.ts.map +1 -1
- package/dist/internal/catalog/infra.js +65 -0
- package/dist/internal/devProviders.d.ts +1 -19
- package/dist/internal/devProviders.d.ts.map +1 -1
- package/dist/internal/devProviders.js +7 -1
- package/dist/internal/events.d.ts +52 -1
- package/dist/internal/events.d.ts.map +1 -1
- package/dist/internal/events.js +65 -3
- package/dist/internal/gatewaySettings.d.ts +6 -16
- package/dist/internal/gatewaySettings.d.ts.map +1 -1
- package/dist/internal/gatewaySettings.js +5 -23
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +1 -0
- package/dist/internal/mcpOAuth.d.ts +210 -0
- package/dist/internal/mcpOAuth.d.ts.map +1 -0
- package/dist/internal/mcpOAuth.js +735 -0
- package/dist/internal/oauthClient.d.ts +7 -47
- package/dist/internal/oauthClient.d.ts.map +1 -1
- package/dist/internal/oauthClient.js +1 -115
- package/dist/internal/oauthPresets.d.ts +356 -20
- package/dist/internal/oauthPresets.d.ts.map +1 -1
- package/dist/internal/oauthPresets.js +183 -861
- package/dist/internal/oauthScopes.d.ts +43 -155
- package/dist/internal/oauthScopes.d.ts.map +1 -1
- package/dist/internal/oauthScopes.js +82 -475
- package/dist/internal/oauthSpec.d.ts +28 -71
- package/dist/internal/oauthSpec.d.ts.map +1 -1
- package/dist/internal/oauthSpec.js +172 -448
- package/dist/internal/protocol.d.ts +24 -14
- package/dist/internal/protocol.d.ts.map +1 -1
- package/dist/internal/protocol.js +26 -7
- package/dist/internal/providerApps.d.ts +2 -2
- package/dist/internal/providerApps.js +2 -2
- package/dist/internal/providerRegistry.d.ts +5 -64
- package/dist/internal/providerRegistry.d.ts.map +1 -1
- package/dist/internal/providerRegistry.js +19 -118
- package/dist/internal/providerSpec.d.ts.map +1 -1
- package/dist/internal/providerSpec.js +14 -36
- package/dist/internal/remoteMcp.d.ts +191 -47
- package/dist/internal/remoteMcp.d.ts.map +1 -1
- package/dist/internal/remoteMcp.js +338 -133
- package/dist/internal/signInProviders.d.ts +1 -1
- package/dist/internal/signInProviders.d.ts.map +1 -1
- package/dist/internal/tools.d.ts +15 -8
- package/dist/internal/tools.d.ts.map +1 -1
- package/dist/internal/tools.js +9 -8
- package/dist/manifest.d.ts +96 -40
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +1 -1
- package/dist/server/ai.d.ts +1 -93
- package/dist/server/ai.d.ts.map +1 -1
- package/dist/server/ai.js +17 -111
- package/dist/server/attribution.d.ts +10 -1
- package/dist/server/attribution.d.ts.map +1 -1
- package/dist/server/attribution.js +7 -1
- package/dist/server/connectUrl.d.ts +4 -9
- package/dist/server/connectUrl.d.ts.map +1 -1
- package/dist/server/connectUrl.js +17 -14
- package/dist/server/context.d.ts +12 -14
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js +23 -25
- 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 +16 -26
- package/dist/server/everyApp.d.ts.map +1 -1
- package/dist/server/everyApp.js +22 -46
- package/dist/server/gatewayFetch.d.ts +1 -1
- package/dist/server/gatewayFetch.js +1 -1
- package/dist/server/index.d.ts +6 -9
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -8
- package/dist/server/integrationFetch.d.ts +60 -72
- package/dist/server/integrationFetch.d.ts.map +1 -1
- package/dist/server/integrationFetch.js +120 -393
- package/dist/server/refusal.d.ts +0 -9
- package/dist/server/refusal.d.ts.map +1 -1
- package/dist/server/refusal.js +0 -22
- package/dist/server/tools.d.ts +17 -6
- package/dist/server/tools.d.ts.map +1 -1
- package/dist/server/tools.js +14 -4
- package/dist/server/toolsEndpoint.d.ts +2 -1
- package/dist/server/toolsEndpoint.d.ts.map +1 -1
- package/dist/server/toolsEndpoint.js +4 -3
- 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/testing/index.d.ts +2 -3
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +2 -6
- package/package.json +6 -1
- package/dist/server/aiBinding.d.ts +0 -68
- package/dist/server/aiBinding.d.ts.map +0 -1
- package/dist/server/aiBinding.js +0 -294
- package/dist/server/authorizeUrl.d.ts +0 -43
- package/dist/server/authorizeUrl.d.ts.map +0 -1
- package/dist/server/authorizeUrl.js +0 -106
- package/dist/server/emailBinding.d.ts +0 -72
- package/dist/server/emailBinding.d.ts.map +0 -1
- package/dist/server/emailBinding.js +0 -124
- package/dist/server/fromMcpTools.d.ts +0 -89
- package/dist/server/fromMcpTools.d.ts.map +0 -1
- package/dist/server/fromMcpTools.js +0 -192
package/dist/server/ai.js
CHANGED
|
@@ -1,116 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* default
|
|
7
|
-
*
|
|
8
|
-
* completions (and embeddings) to `/api/ai/default/v1/…` with `model:
|
|
9
|
-
* "default"` or no model at all, and the Gateway fills the model in.
|
|
2
|
+
* Where an app's model calls go behind the Gateway, for an OpenAI client's
|
|
3
|
+
* `baseURL` or a plain `fetch`, both sent with `createGatewayFetch()`:
|
|
4
|
+
* `aiBaseUrl("openai")` (a provider's OpenAI-compatible base), `aiBaseUrl()`
|
|
5
|
+
* (the Gateway's default model, `ai: { chat: true }`: send `model:
|
|
6
|
+
* "default"` or none and the Gateway fills in the workspace's model), and
|
|
7
|
+
* `providerBaseUrl("openai")` (a provider's root, for its own paths).
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* In local development with no Gateway, the developer's own key in
|
|
18
|
-
* `.dev.vars` decides the provider by the same order (createGatewayFetch's
|
|
19
|
-
* fallback). An app that needs one provider in particular keeps listing it
|
|
20
|
-
* under `providers` and calling `/api/ai/<provider>/…`.
|
|
9
|
+
* const client = new OpenAI({
|
|
10
|
+
* baseURL: aiBaseUrl(),
|
|
11
|
+
* fetch: createGatewayFetch({ env }),
|
|
12
|
+
* apiKey: "gateway-managed",
|
|
13
|
+
* });
|
|
14
|
+
* await client.chat.completions.create({ model: "default", messages });
|
|
21
15
|
*/
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export const AI_DEFAULT_BASE_URL = `${BINDING_ORIGIN}/api/ai/${AI_DEFAULT_PROVIDER}/v1`;
|
|
27
|
-
export function ai(options) {
|
|
28
|
-
const fetch = createGatewayFetch(options);
|
|
29
|
-
if (!fetch)
|
|
30
|
-
return null;
|
|
31
|
-
return {
|
|
32
|
-
baseURL: AI_DEFAULT_BASE_URL,
|
|
33
|
-
fetch,
|
|
34
|
-
model: AI_DEFAULT_MODEL,
|
|
35
|
-
chatCompletionsUrl: `${AI_DEFAULT_BASE_URL}/chat/completions`,
|
|
36
|
-
embeddingsUrl: `${AI_DEFAULT_BASE_URL}/embeddings`,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Whether the app can use a model right now, without calling one: gate
|
|
41
|
-
* an AI feature and show `setup` ("A chat model isn't set up on this
|
|
42
|
-
* Gateway: it needs OPENROUTER_API_KEY. An admin runs: pnpm everyapp
|
|
43
|
-
* configure inbox") up front instead of failing mid-task. `provider`
|
|
44
|
-
* defaults to `default`, the Gateway's default model (`ai: { chat: true }`);
|
|
45
|
-
* name one the manifest lists under `providers` to ask about it instead.
|
|
46
|
-
* The Gateway owns the wording: show `setup` and `command` as they are.
|
|
47
|
-
*
|
|
48
|
-
* Over the binding it sends `GET /api/ai/<provider>`; in local dev it
|
|
49
|
-
* answers from `.dev.vars` by the same rule `createGatewayFetch()` uses.
|
|
50
|
-
*/
|
|
51
|
-
export async function aiStatus({ env, provider = AI_DEFAULT_PROVIDER, for: explicitTarget, }) {
|
|
52
|
-
const target = attributionFor(env, explicitTarget, "aiStatus");
|
|
53
|
-
const values = env;
|
|
54
|
-
const binding = gatewayBinding(env);
|
|
55
|
-
const unavailable = {
|
|
56
|
-
provider,
|
|
57
|
-
requested: false,
|
|
58
|
-
configured: false,
|
|
59
|
-
ready: false,
|
|
60
|
-
resolved: null,
|
|
61
|
-
settings: [],
|
|
62
|
-
setup: null,
|
|
63
|
-
command: null,
|
|
64
|
-
};
|
|
65
|
-
if (binding && typeof binding.fetch === "function") {
|
|
66
|
-
const headers = new Headers();
|
|
67
|
-
if (target !== undefined)
|
|
68
|
-
applyAttribution(headers, target);
|
|
69
|
-
const response = await binding.fetch(`${BINDING_ORIGIN}/api/ai/${encodeURIComponent(provider)}`, { headers });
|
|
70
|
-
if (!response.ok)
|
|
71
|
-
return unavailable;
|
|
72
|
-
const body = (await response.json());
|
|
73
|
-
return {
|
|
74
|
-
...unavailable,
|
|
75
|
-
...body,
|
|
76
|
-
provider,
|
|
77
|
-
settings: Array.isArray(body.settings) ? body.settings : [],
|
|
78
|
-
setup: typeof body.setup === "string" ? body.setup : null,
|
|
79
|
-
command: typeof body.command === "string" ? body.command : null,
|
|
80
|
-
resolved: body.resolved ?? null,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
if (!values["EVERYAPP_DEV"])
|
|
84
|
-
return unavailable;
|
|
85
|
-
const isSet = (name) => typeof values[name] === "string" && values[name].trim() !== "";
|
|
86
|
-
const dev = (label, settings, configured, resolved, also = "") => ({
|
|
87
|
-
provider,
|
|
88
|
-
requested: true,
|
|
89
|
-
configured,
|
|
90
|
-
ready: configured,
|
|
91
|
-
resolved,
|
|
92
|
-
settings: configured ? [] : [...settings],
|
|
93
|
-
setup: configured
|
|
94
|
-
? null
|
|
95
|
-
: `${label} isn't set up for local development: set ${settings.join(" and ")}${also} in .dev.vars.`,
|
|
96
|
-
command: null,
|
|
97
|
-
});
|
|
98
|
-
if (provider === AI_DEFAULT_PROVIDER) {
|
|
99
|
-
const resolution = resolveAiDefault({ provider: null, isSet });
|
|
100
|
-
return dev("A chat model", resolution.settings, resolution.configured, resolution.configured
|
|
101
|
-
? {
|
|
102
|
-
provider: resolution.provider,
|
|
103
|
-
label: resolution.label,
|
|
104
|
-
model: resolution.model,
|
|
105
|
-
}
|
|
106
|
-
: null, " (or another model provider's key)");
|
|
107
|
-
}
|
|
108
|
-
const spec = devAiProvider(provider);
|
|
109
|
-
if (!spec)
|
|
110
|
-
return unavailable;
|
|
111
|
-
const settings = aiProviderSettings(spec);
|
|
112
|
-
return dev(CATALOG.get(provider)?.label ?? provider, settings, settings.every(isSet), null);
|
|
113
|
-
}
|
|
16
|
+
import { AI_DEFAULT_PROVIDER, CATALOG, ConfigurationError, } from "../internal/index.js";
|
|
17
|
+
import { BINDING_ORIGIN } from "./gatewayFetch.js";
|
|
18
|
+
/** The base URL of the default route, `…/api/ai/default/v1`. */
|
|
19
|
+
const AI_DEFAULT_BASE_URL = `${BINDING_ORIGIN}/api/ai/${AI_DEFAULT_PROVIDER}/v1`;
|
|
114
20
|
const CHAT_COMPLETIONS = "/chat/completions";
|
|
115
21
|
/**
|
|
116
22
|
* A provider's root behind the Gateway, `http://every-app-gateway.internal/api/ai/<provider>`:
|
|
@@ -124,7 +30,7 @@ export function providerBaseUrl(provider) {
|
|
|
124
30
|
/**
|
|
125
31
|
* The OpenAI-compatible base URL of `provider` behind the Gateway, for an
|
|
126
32
|
* OpenAI client's `baseURL` (with `fetch: createGatewayFetch(...)`):
|
|
127
|
-
* `default` (the default
|
|
33
|
+
* `default` (the default, and what it is when left out) is
|
|
128
34
|
* `…/api/ai/default/v1`; a catalog provider's is its root and the prefix
|
|
129
35
|
* of its chat route (`…/api/ai/openrouter/api/v1`, `…/api/ai/workers-ai/v1`,
|
|
130
36
|
* `…/api/ai/openai/v1`). Throws `ConfigurationError` for a provider with
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Whom a call through the gateway is for, which decides the organization
|
|
3
3
|
* it counts against. Left out, it comes from the request context: the
|
|
4
4
|
* request `everyApp()` is serving (its person, while the identity is good),
|
|
5
|
-
* or the organization
|
|
5
|
+
* or the organization the SDK's event delivery runs under. Given explicitly:
|
|
6
6
|
*
|
|
7
7
|
* - the inbound `Request`: its person, by the identity the gateway put on
|
|
8
8
|
* it (forwarded as is);
|
|
@@ -12,10 +12,19 @@
|
|
|
12
12
|
* checks is active and has a live member. An organization's own app may
|
|
13
13
|
* leave it out (the gateway knows its organization) and may only name
|
|
14
14
|
* its own.
|
|
15
|
+
* - `{ background: true, organizationId, userId }`: a call to an OAuth
|
|
16
|
+
* provider's API (`integrationFetch`) on the connection member `userId`
|
|
17
|
+
* made in this app, at any time, even while serving another person's
|
|
18
|
+
* request: the call carries that member (`x-everyapp-user`) and never
|
|
19
|
+
* the identity of the person being served. Only an organization's own
|
|
20
|
+
* app may name a member, and only one who may still open the app. No
|
|
21
|
+
* identity for the member reaches the app. Other gateway calls ignore
|
|
22
|
+
* `userId`.
|
|
15
23
|
*/
|
|
16
24
|
export type GatewayCallFor = Request | {
|
|
17
25
|
background: true;
|
|
18
26
|
organizationId?: string;
|
|
27
|
+
userId?: string;
|
|
19
28
|
};
|
|
20
29
|
/**
|
|
21
30
|
* A gateway call that doesn't say whom it is for where it must: from a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../../src/server/attribution.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../../src/server/attribution.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,MAAM,cAAc,GACtB,OAAO,GACP;IAAE,UAAU,EAAE,IAAI,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnE;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,0BAA0B;gBAC3B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,GAAG,SAAS,EAClC,IAAI,EAAE,MAAM,GACX,cAAc,GAAG,SAAS,CAI5B;AAmCD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,cAAc,GAAG,SAAS,GACjC,IAAI,CAYN;AASD,0EAA0E;AAC1E,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAO1D"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IDENTITY_HEADER, ORG_HEADER, SHARED_ENV } from "../internal/index.js";
|
|
1
|
+
import { IDENTITY_HEADER, ORG_HEADER, SHARED_ENV, USER_HEADER, } from "../internal/index.js";
|
|
2
2
|
import { attributionFromContext } from "./context.js";
|
|
3
3
|
/**
|
|
4
4
|
* A gateway call that doesn't say whom it is for where it must: from a
|
|
@@ -28,6 +28,9 @@ function checkAttribution(env, target, what) {
|
|
|
28
28
|
if (target === null || typeof target !== "object" || !target.background) {
|
|
29
29
|
throw new AttributionError(`${what}: \`for\` is the inbound Request or { background: true, organizationId }.`);
|
|
30
30
|
}
|
|
31
|
+
if (target.userId !== undefined && !nonEmpty(target.userId)) {
|
|
32
|
+
throw new AttributionError(`${what}: { background: true, userId } needs a non-empty user id.`);
|
|
33
|
+
}
|
|
31
34
|
if (target.organizationId !== undefined &&
|
|
32
35
|
!nonEmpty(target.organizationId)) {
|
|
33
36
|
throw new AttributionError(`${what}: { background: true, organizationId } needs a non-empty organization id.`);
|
|
@@ -46,6 +49,7 @@ function checkAttribution(env, target, what) {
|
|
|
46
49
|
export function applyAttribution(headers, target) {
|
|
47
50
|
headers.delete(IDENTITY_HEADER);
|
|
48
51
|
headers.delete(ORG_HEADER);
|
|
52
|
+
headers.delete(USER_HEADER);
|
|
49
53
|
if (target === undefined)
|
|
50
54
|
return;
|
|
51
55
|
if (isRequest(target)) {
|
|
@@ -56,6 +60,8 @@ export function applyAttribution(headers, target) {
|
|
|
56
60
|
}
|
|
57
61
|
if (target.organizationId)
|
|
58
62
|
headers.set(ORG_HEADER, target.organizationId);
|
|
63
|
+
if (target.userId)
|
|
64
|
+
headers.set(USER_HEADER, target.userId.trim());
|
|
59
65
|
}
|
|
60
66
|
function isSharedWorker(env) {
|
|
61
67
|
const value = env[SHARED_ENV];
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { ConnectionAccount } from "../internal/index.js";
|
|
2
1
|
export interface ConnectUrlOptions {
|
|
3
|
-
/** The Worker env: `GATEWAY_URL
|
|
2
|
+
/** The Worker env: `GATEWAY_URL` and the app's id, which every deployed Worker and `everyapp dev` set. */
|
|
4
3
|
env: object;
|
|
5
4
|
/** The OAuth provider's id (`google-workspace`, `github`). */
|
|
6
5
|
provider: string;
|
|
@@ -9,13 +8,9 @@ export interface ConnectUrlOptions {
|
|
|
9
8
|
* a path on the Gateway. Absent: Settings → Connected accounts.
|
|
10
9
|
*/
|
|
11
10
|
returnTo?: string;
|
|
12
|
-
/** Whose connection to make: the person's (default) or the organization's (owner or admin). */
|
|
13
|
-
account?: ConnectionAccount;
|
|
14
|
-
/** Scope sets to ask for; absent: the union every active app that lists the provider declares. */
|
|
15
|
-
sets?: readonly string[];
|
|
16
11
|
}
|
|
17
|
-
/** The path of the connect page for `provider` on the Gateway. */
|
|
12
|
+
/** The path of the connect page for `provider` on the Gateway, without the app. */
|
|
18
13
|
export declare function connectPath(provider: string): string;
|
|
19
|
-
/** The absolute URL that
|
|
20
|
-
export declare function connectUrl({ env, provider, returnTo,
|
|
14
|
+
/** The absolute URL that connects the person to `provider` in this app. */
|
|
15
|
+
export declare function connectUrl({ env, provider, returnTo, }: ConnectUrlOptions): string;
|
|
21
16
|
//# sourceMappingURL=connectUrl.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connectUrl.d.ts","sourceRoot":"","sources":["../../src/server/connectUrl.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"connectUrl.d.ts","sourceRoot":"","sources":["../../src/server/connectUrl.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,iBAAiB;IAChC,0GAA0G;IAC1G,GAAG,EAAE,MAAM,CAAC;IACZ,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,EACzB,GAAG,EACH,QAAQ,EACR,QAAQ,GACT,EAAE,iBAAiB,GAAG,MAAM,CAwB5B"}
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Where an app sends a person to connect an OAuth provider the Gateway
|
|
3
|
-
* runs the flow for (docs/design/2026-
|
|
4
|
-
* `<gateway>/connect/<id>?return=<back here
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the
|
|
8
|
-
*
|
|
3
|
+
* runs the flow for (docs/design/2026-11-oauth-simplify.md):
|
|
4
|
+
* `<gateway>/connect/<id>?app=<this app>&return=<back here>`. A
|
|
5
|
+
* connection belongs to one person and one app: the Gateway checks the
|
|
6
|
+
* session and that the person may open the app, asks the provider for
|
|
7
|
+
* exactly the scopes the app's `everyapp.config.ts` lists (plus the ones
|
|
8
|
+
* that name the account), finishes on its own callback and sends the
|
|
9
|
+
* browser back to `returnTo`, which must be a path on the Gateway or an
|
|
10
|
+
* https URL on one of its app hosts (in local dev also localhost).
|
|
9
11
|
*/
|
|
10
|
-
import { ConfigurationError } from "../internal/index.js";
|
|
11
|
-
/** The path of the connect page for `provider` on the Gateway. */
|
|
12
|
+
import { APP_ID_ENV, ConfigurationError } from "../internal/index.js";
|
|
13
|
+
/** The path of the connect page for `provider` on the Gateway, without the app. */
|
|
12
14
|
export function connectPath(provider) {
|
|
13
15
|
return `/connect/${encodeURIComponent(provider)}`;
|
|
14
16
|
}
|
|
15
|
-
/** The absolute URL that
|
|
16
|
-
export function connectUrl({ env, provider, returnTo,
|
|
17
|
+
/** The absolute URL that connects the person to `provider` in this app. */
|
|
18
|
+
export function connectUrl({ env, provider, returnTo, }) {
|
|
17
19
|
const values = env;
|
|
18
20
|
const gateway = values["GATEWAY_URL"];
|
|
19
21
|
if (typeof gateway !== "string" || !gateway.trim()) {
|
|
@@ -26,12 +28,13 @@ export function connectUrl({ env, provider, returnTo, account, sets, }) {
|
|
|
26
28
|
catch {
|
|
27
29
|
throw new ConfigurationError(`GATEWAY_URL isn't a URL: ${gateway}`);
|
|
28
30
|
}
|
|
31
|
+
const app = values[APP_ID_ENV];
|
|
32
|
+
if (typeof app !== "string" || !app.trim()) {
|
|
33
|
+
throw new ConfigurationError(`connectUrl() needs ${APP_ID_ENV} in the Worker env: a connection belongs to one app, and the deploy and everyapp dev set it.`);
|
|
34
|
+
}
|
|
29
35
|
const url = new URL(connectPath(provider), base.origin);
|
|
36
|
+
url.searchParams.set("app", app.trim());
|
|
30
37
|
if (returnTo)
|
|
31
38
|
url.searchParams.set("return", returnTo);
|
|
32
|
-
if (account)
|
|
33
|
-
url.searchParams.set("account", account);
|
|
34
|
-
if (sets && sets.length > 0)
|
|
35
|
-
url.searchParams.set("sets", sets.join(","));
|
|
36
39
|
return url.toString();
|
|
37
40
|
}
|
package/dist/server/context.d.ts
CHANGED
|
@@ -14,9 +14,9 @@ export interface ServedRequest {
|
|
|
14
14
|
*/
|
|
15
15
|
export declare const IDENTITY_EXPIRY_MARGIN_MS = 15000;
|
|
16
16
|
/**
|
|
17
|
-
* Code `request_context_missing`:
|
|
18
|
-
*
|
|
19
|
-
* `
|
|
17
|
+
* Code `request_context_missing`: a Gateway call with no `for`, outside
|
|
18
|
+
* any request `everyApp()` serves. Work no request started passes
|
|
19
|
+
* `for: { background: true }`.
|
|
20
20
|
*/
|
|
21
21
|
export declare class RequestContextError extends Error {
|
|
22
22
|
readonly code = "request_context_missing";
|
|
@@ -35,20 +35,18 @@ export declare function serveRequest<T>(served: ServedRequest, fn: () => T): T;
|
|
|
35
35
|
/**
|
|
36
36
|
* Run `fn` as `organizationId`'s background work: every Gateway call made
|
|
37
37
|
* inside it (and in what it awaits or schedules) is that organization's,
|
|
38
|
-
* with no person behind it.
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* organization's own request, the person stays attributed while their
|
|
44
|
-
* identity is good.
|
|
38
|
+
* with no person behind it. The SDK's event delivery (eventsEndpoint.ts)
|
|
39
|
+
* runs the app's `events.on` under it; apps pass `for` instead. Inside a
|
|
40
|
+
* request another organization's person is driving, it throws
|
|
41
|
+
* {@link OrganizationMismatchError}; inside that organization's own
|
|
42
|
+
* request, the person stays attributed while their identity is good.
|
|
45
43
|
*/
|
|
46
44
|
export declare function withOrganization<T>(organizationId: string, fn: () => T): T;
|
|
47
45
|
/**
|
|
48
|
-
* The verified user of the request being served
|
|
49
|
-
* `null` on a public route
|
|
50
|
-
*
|
|
51
|
-
*
|
|
46
|
+
* The verified user of the request being served (`everyApp()` reads it to
|
|
47
|
+
* reuse one verification): an `EveryAppUser`, or `null` on a public route
|
|
48
|
+
* and inside `withOrganization()`'s background work. Throws
|
|
49
|
+
* {@link RequestContextError} outside both.
|
|
52
50
|
*/
|
|
53
51
|
export declare function currentUser(): EveryAppUser | null;
|
|
54
52
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/server/context.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,oDAAoD;IACpD,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IAC1B,wEAAwE;IACxE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAUD;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,6BAA6B;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED;;;GAGG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,IAAI,uCAAuC;gBACxC,OAAO,EAAE,MAAM;CAI5B;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAErE;AAED
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/server/context.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,oDAAoD;IACpD,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IAC1B,wEAAwE;IACxE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAUD;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAEhD;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,6BAA6B;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED;;;GAGG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,IAAI,uCAAuC;gBACxC,OAAO,EAAE,MAAM;CAI5B;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAErE;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAc1E;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,IAAI,YAAY,GAAG,IAAI,CAQjD;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,MAAmB,GACvB,OAAO,GAAG;IAAE,UAAU,EAAE,IAAI,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAoBrE"}
|
package/dist/server/context.js
CHANGED
|
@@ -4,17 +4,17 @@
|
|
|
4
4
|
* `AsyncLocalStorage`, which every Every App Worker has (`nodejs_compat`).
|
|
5
5
|
*
|
|
6
6
|
* `everyApp()` opens it around the handler and around each tool call, with
|
|
7
|
-
* the verified user and the request the Gateway forwarded.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* is attributed to that person without a `for`.
|
|
7
|
+
* the verified user and the request the Gateway forwarded. Every call back
|
|
8
|
+
* to the Gateway made inside it (`integrationFetch`, `sendEmail`, `emit`,
|
|
9
|
+
* ...) is attributed to that person without a `for`.
|
|
11
10
|
*
|
|
12
11
|
* Work no request started (a cron, a Workflow step, a queue consumer, a
|
|
13
|
-
* Durable Object alarm) has no context of its own: it
|
|
14
|
-
* `
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
12
|
+
* Durable Object alarm) has no context of its own: it says whom each call
|
|
13
|
+
* is for, `for: { background: true }` (a shared app's Worker adds the
|
|
14
|
+
* `organizationId`). The SDK's own event delivery runs its handler under
|
|
15
|
+
* `withOrganization()`, which does the same for every call inside it. A
|
|
16
|
+
* Gateway call with neither is refused before it is made
|
|
17
|
+
* ({@link RequestContextError}): the SDK never acts as nobody.
|
|
18
18
|
*/
|
|
19
19
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
20
20
|
const scopes = new AsyncLocalStorage();
|
|
@@ -25,9 +25,9 @@ const scopes = new AsyncLocalStorage();
|
|
|
25
25
|
*/
|
|
26
26
|
export const IDENTITY_EXPIRY_MARGIN_MS = 15_000;
|
|
27
27
|
/**
|
|
28
|
-
* Code `request_context_missing`:
|
|
29
|
-
*
|
|
30
|
-
* `
|
|
28
|
+
* Code `request_context_missing`: a Gateway call with no `for`, outside
|
|
29
|
+
* any request `everyApp()` serves. Work no request started passes
|
|
30
|
+
* `for: { background: true }`.
|
|
31
31
|
*/
|
|
32
32
|
export class RequestContextError extends Error {
|
|
33
33
|
code = "request_context_missing";
|
|
@@ -54,13 +54,11 @@ export function serveRequest(served, fn) {
|
|
|
54
54
|
/**
|
|
55
55
|
* Run `fn` as `organizationId`'s background work: every Gateway call made
|
|
56
56
|
* inside it (and in what it awaits or schedules) is that organization's,
|
|
57
|
-
* with no person behind it.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* organization's own request, the person stays attributed while their
|
|
63
|
-
* identity is good.
|
|
57
|
+
* with no person behind it. The SDK's event delivery (eventsEndpoint.ts)
|
|
58
|
+
* runs the app's `events.on` under it; apps pass `for` instead. Inside a
|
|
59
|
+
* request another organization's person is driving, it throws
|
|
60
|
+
* {@link OrganizationMismatchError}; inside that organization's own
|
|
61
|
+
* request, the person stays attributed while their identity is good.
|
|
64
62
|
*/
|
|
65
63
|
export function withOrganization(organizationId, fn) {
|
|
66
64
|
if (typeof organizationId !== "string" || organizationId.trim() === "") {
|
|
@@ -74,15 +72,15 @@ export function withOrganization(organizationId, fn) {
|
|
|
74
72
|
return scopes.run({ ...parent, organizationId }, fn);
|
|
75
73
|
}
|
|
76
74
|
/**
|
|
77
|
-
* The verified user of the request being served
|
|
78
|
-
* `null` on a public route
|
|
79
|
-
*
|
|
80
|
-
*
|
|
75
|
+
* The verified user of the request being served (`everyApp()` reads it to
|
|
76
|
+
* reuse one verification): an `EveryAppUser`, or `null` on a public route
|
|
77
|
+
* and inside `withOrganization()`'s background work. Throws
|
|
78
|
+
* {@link RequestContextError} outside both.
|
|
81
79
|
*/
|
|
82
80
|
export function currentUser() {
|
|
83
81
|
const scope = scopes.getStore();
|
|
84
82
|
if (!scope) {
|
|
85
|
-
throw new RequestContextError("currentUser() was called outside a request everyApp() serves
|
|
83
|
+
throw new RequestContextError("currentUser() was called outside a request everyApp() serves: there is no user here.");
|
|
86
84
|
}
|
|
87
85
|
return scope.served?.user ?? null;
|
|
88
86
|
}
|
|
@@ -99,7 +97,7 @@ export function currentUser() {
|
|
|
99
97
|
export function attributionFromContext(what, now = Date.now()) {
|
|
100
98
|
const scope = scopes.getStore();
|
|
101
99
|
if (!scope) {
|
|
102
|
-
throw new RequestContextError(`${what} was called with no \`for\`, outside a request everyApp() serves
|
|
100
|
+
throw new RequestContextError(`${what} was called with no \`for\`, outside a request everyApp() serves: the call would be nobody's. Pass for: request, or for: { background: true } for work no request started (a cron, a Workflow step, a queue consumer).`);
|
|
103
101
|
}
|
|
104
102
|
const served = scope.served;
|
|
105
103
|
if (served?.user) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app side of the `app` event sink (docs/design/2026-11-birds-decisions/app-event-sink.md).
|
|
3
|
+
*
|
|
4
|
+
* `everyApp()` answers the reserved `POST /__everyapp/events` here, before
|
|
5
|
+
* any app code runs and whether or not the app passed `events`:
|
|
6
|
+
*
|
|
7
|
+
* - the request needs the Gateway's system token for this app (purpose
|
|
8
|
+
* `events.deliver`), whose `bh` claim is the SHA-256 of this exact body,
|
|
9
|
+
* so a token opens one delivery and nothing else;
|
|
10
|
+
* - the envelope must name the organization this Worker serves
|
|
11
|
+
* (`EVERY_APP_ORG_ID`), when it is pinned;
|
|
12
|
+
* - only then is `events.on` called, under `withOrganization()`, so every
|
|
13
|
+
* Gateway call it makes is that organization's background work.
|
|
14
|
+
*
|
|
15
|
+
* The perimeter refuses the path from outside and strips inbound
|
|
16
|
+
* `x-everyapp-*` headers, so only the Gateway's event bus reaches it, over
|
|
17
|
+
* the service binding.
|
|
18
|
+
*/
|
|
19
|
+
import { type AppEventEnvelope, type RequestIdentityOptions } from "../internal/index.js";
|
|
20
|
+
import type { ExecutionContextLike } from "./everyApp.js";
|
|
21
|
+
/** One event the Gateway delivered: the webhook envelope, plus the delivery's id and attempt. */
|
|
22
|
+
export interface AppEvent extends AppEventEnvelope {
|
|
23
|
+
delivery: {
|
|
24
|
+
id: string;
|
|
25
|
+
attempt: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export type AppEventHandler<TEnv = unknown> = (event: AppEvent, env: TEnv, ctx: ExecutionContextLike) => unknown | Promise<unknown>;
|
|
29
|
+
/**
|
|
30
|
+
* `everyApp(handler, { events: { on } })`. `on` is one function for every
|
|
31
|
+
* event, or one per exact type (`"member.removed"`). An event with no
|
|
32
|
+
* handler is acknowledged and dropped. A handler that throws answers 500,
|
|
33
|
+
* and the Gateway retries the delivery with its usual backoff.
|
|
34
|
+
*/
|
|
35
|
+
export interface AppEventsOptions<TEnv = unknown> {
|
|
36
|
+
on: AppEventHandler<TEnv> | Partial<Record<string, AppEventHandler<TEnv>>>;
|
|
37
|
+
}
|
|
38
|
+
export interface EventsEndpointOptions {
|
|
39
|
+
events?: AppEventsOptions<never>;
|
|
40
|
+
/** How to verify the Gateway's token (keys, issuer, audience, dev opt-in). */
|
|
41
|
+
identity: RequestIdentityOptions;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Answer a request to the reserved events path, or return null for any
|
|
45
|
+
* other path so the app's own handler runs.
|
|
46
|
+
*/
|
|
47
|
+
export declare function handleEventsRequest(request: Request, env: unknown, ctx: ExecutionContextLike, options: EventsEndpointOptions): Promise<Response | null>;
|
|
48
|
+
//# sourceMappingURL=eventsEndpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eventsEndpoint.d.ts","sourceRoot":"","sources":["../../src/server/eventsEndpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EASL,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC5B,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAE1D,iGAAiG;AACjG,MAAM,WAAW,QAAS,SAAQ,gBAAgB;IAChD,QAAQ,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAED,MAAM,MAAM,eAAe,CAAC,IAAI,GAAG,OAAO,IAAI,CAC5C,KAAK,EAAE,QAAQ,EACf,GAAG,EAAE,IAAI,EACT,GAAG,EAAE,oBAAoB,KACtB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB,CAAC,IAAI,GAAG,OAAO;IAC9C,EAAE,EAAE,eAAe,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;CAC5E;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACjC,8EAA8E;IAC9E,QAAQ,EAAE,sBAAsB,CAAC;CAClC;AAwCD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,oBAAoB,EACzB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CA4F1B"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app side of the `app` event sink (docs/design/2026-11-birds-decisions/app-event-sink.md).
|
|
3
|
+
*
|
|
4
|
+
* `everyApp()` answers the reserved `POST /__everyapp/events` here, before
|
|
5
|
+
* any app code runs and whether or not the app passed `events`:
|
|
6
|
+
*
|
|
7
|
+
* - the request needs the Gateway's system token for this app (purpose
|
|
8
|
+
* `events.deliver`), whose `bh` claim is the SHA-256 of this exact body,
|
|
9
|
+
* so a token opens one delivery and nothing else;
|
|
10
|
+
* - the envelope must name the organization this Worker serves
|
|
11
|
+
* (`EVERY_APP_ORG_ID`), when it is pinned;
|
|
12
|
+
* - only then is `events.on` called, under `withOrganization()`, so every
|
|
13
|
+
* Gateway call it makes is that organization's background work.
|
|
14
|
+
*
|
|
15
|
+
* The perimeter refuses the path from outside and strips inbound
|
|
16
|
+
* `x-everyapp-*` headers, so only the Gateway's event bus reaches it, over
|
|
17
|
+
* the service binding.
|
|
18
|
+
*/
|
|
19
|
+
import { APP_EVENTS_PATH, ConfigurationError, EVENT_HEADERS, IdentityError, SYSTEM_HEADER, bodyHash, resolveVerifyOptions, verifySystemJwt, } from "../internal/index.js";
|
|
20
|
+
import { withOrganization } from "./context.js";
|
|
21
|
+
/** The most body the endpoint reads: an envelope's data is at most 16 KiB. */
|
|
22
|
+
const MAX_BODY_BYTES = 64 * 1024;
|
|
23
|
+
function answer(status, body) {
|
|
24
|
+
return Response.json(body, {
|
|
25
|
+
status,
|
|
26
|
+
headers: { "cache-control": "no-store" },
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function isEnvelope(value) {
|
|
30
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const v = value;
|
|
34
|
+
const org = v.organization;
|
|
35
|
+
return (typeof v.id === "string" &&
|
|
36
|
+
typeof v.type === "string" &&
|
|
37
|
+
typeof v.version === "number" &&
|
|
38
|
+
typeof v.time === "string" &&
|
|
39
|
+
!!org &&
|
|
40
|
+
typeof org === "object" &&
|
|
41
|
+
typeof org.id === "string" &&
|
|
42
|
+
!!v.data &&
|
|
43
|
+
typeof v.data === "object" &&
|
|
44
|
+
!Array.isArray(v.data));
|
|
45
|
+
}
|
|
46
|
+
function handlerFor(on, type) {
|
|
47
|
+
if (typeof on === "function")
|
|
48
|
+
return on;
|
|
49
|
+
return Object.prototype.hasOwnProperty.call(on, type) ? on[type] : undefined;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Answer a request to the reserved events path, or return null for any
|
|
53
|
+
* other path so the app's own handler runs.
|
|
54
|
+
*/
|
|
55
|
+
export async function handleEventsRequest(request, env, ctx, options) {
|
|
56
|
+
const { pathname } = new URL(request.url);
|
|
57
|
+
if (pathname !== APP_EVENTS_PATH)
|
|
58
|
+
return null;
|
|
59
|
+
if (request.method !== "POST") {
|
|
60
|
+
return answer(405, { error: "method_not_allowed" });
|
|
61
|
+
}
|
|
62
|
+
let verify;
|
|
63
|
+
try {
|
|
64
|
+
verify = resolveVerifyOptions(options.identity);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error instanceof ConfigurationError) {
|
|
68
|
+
console.error(error);
|
|
69
|
+
return answer(500, { error: "misconfigured", message: error.message });
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
let claims;
|
|
74
|
+
try {
|
|
75
|
+
claims = await verifySystemJwt(request.headers.get(SYSTEM_HEADER) ?? "", {
|
|
76
|
+
...verify,
|
|
77
|
+
purpose: "events.deliver",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error instanceof IdentityError) {
|
|
82
|
+
return answer(401, { error: "unauthenticated", message: error.message });
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
const declared = Number(request.headers.get("content-length") ?? "0");
|
|
87
|
+
if (declared > MAX_BODY_BYTES) {
|
|
88
|
+
return answer(413, { error: "too_large" });
|
|
89
|
+
}
|
|
90
|
+
const body = await request.text();
|
|
91
|
+
if (body.length > MAX_BODY_BYTES) {
|
|
92
|
+
return answer(413, { error: "too_large" });
|
|
93
|
+
}
|
|
94
|
+
// The token was minted for this exact body: another body, even one the
|
|
95
|
+
// Gateway sent, is refused.
|
|
96
|
+
if (claims.bh !== (await bodyHash(body))) {
|
|
97
|
+
return answer(401, {
|
|
98
|
+
error: "unauthenticated",
|
|
99
|
+
message: "the token was minted for another delivery",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
let parsed;
|
|
103
|
+
try {
|
|
104
|
+
parsed = JSON.parse(body);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
parsed = undefined;
|
|
108
|
+
}
|
|
109
|
+
if (!isEnvelope(parsed)) {
|
|
110
|
+
return answer(400, { error: "invalid_request" });
|
|
111
|
+
}
|
|
112
|
+
const pinned = options.identity.organizationId;
|
|
113
|
+
if (pinned && parsed.organization.id !== pinned) {
|
|
114
|
+
return answer(403, {
|
|
115
|
+
error: "forbidden",
|
|
116
|
+
message: `the event is for organization "${parsed.organization.id}", and this Worker serves "${pinned}"`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (!options.events) {
|
|
120
|
+
// The manifest subscribes, but the Worker passes no `events` to
|
|
121
|
+
// everyApp(): say so, and let the Gateway retry once it does.
|
|
122
|
+
return answer(404, {
|
|
123
|
+
error: "no_handler",
|
|
124
|
+
message: "everyApp() was given no events: { on }",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
const handler = handlerFor(options.events.on, parsed.type);
|
|
128
|
+
if (!handler)
|
|
129
|
+
return answer(200, { ok: true, handled: false });
|
|
130
|
+
const event = {
|
|
131
|
+
...parsed,
|
|
132
|
+
delivery: {
|
|
133
|
+
id: request.headers.get(EVENT_HEADERS.delivery) ?? "",
|
|
134
|
+
attempt: Number(request.headers.get(EVENT_HEADERS.attempt) ?? "0") || 0,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
try {
|
|
138
|
+
await withOrganization(parsed.organization.id, () => handler(event, env, ctx));
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
console.error(error);
|
|
142
|
+
return answer(500, { error: "handler_failed" });
|
|
143
|
+
}
|
|
144
|
+
return answer(200, { ok: true, handled: true });
|
|
145
|
+
}
|