@decocms/runtime 1.0.0-alpha.14 → 1.0.0-alpha.16
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/package.json +1 -1
- package/src/cors.ts +140 -0
- package/src/index.ts +26 -1
package/package.json
CHANGED
package/src/cors.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
export type CORSOrigin =
|
|
2
|
+
| string
|
|
3
|
+
| string[]
|
|
4
|
+
| ((origin: string, req: Request) => string | null | undefined);
|
|
5
|
+
|
|
6
|
+
export interface CORSOptions {
|
|
7
|
+
/**
|
|
8
|
+
* The value of "Access-Control-Allow-Origin" CORS header.
|
|
9
|
+
* Can be a string, array of strings, or a function that returns the allowed origin.
|
|
10
|
+
* @default '*'
|
|
11
|
+
*/
|
|
12
|
+
origin?: CORSOrigin;
|
|
13
|
+
/**
|
|
14
|
+
* The value of "Access-Control-Allow-Methods" CORS header.
|
|
15
|
+
* @default ['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']
|
|
16
|
+
*/
|
|
17
|
+
allowMethods?: string[];
|
|
18
|
+
/**
|
|
19
|
+
* The value of "Access-Control-Allow-Headers" CORS header.
|
|
20
|
+
* @default []
|
|
21
|
+
*/
|
|
22
|
+
allowHeaders?: string[];
|
|
23
|
+
/**
|
|
24
|
+
* The value of "Access-Control-Max-Age" CORS header (in seconds).
|
|
25
|
+
*/
|
|
26
|
+
maxAge?: number;
|
|
27
|
+
/**
|
|
28
|
+
* The value of "Access-Control-Allow-Credentials" CORS header.
|
|
29
|
+
*/
|
|
30
|
+
credentials?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The value of "Access-Control-Expose-Headers" CORS header.
|
|
33
|
+
* @default []
|
|
34
|
+
*/
|
|
35
|
+
exposeHeaders?: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const DEFAULT_ALLOW_METHODS = ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH"];
|
|
39
|
+
|
|
40
|
+
const resolveOrigin = (
|
|
41
|
+
origin: CORSOrigin | undefined,
|
|
42
|
+
requestOrigin: string | null,
|
|
43
|
+
req: Request,
|
|
44
|
+
): string | null => {
|
|
45
|
+
if (!requestOrigin) return null;
|
|
46
|
+
|
|
47
|
+
if (origin === undefined || origin === "*") {
|
|
48
|
+
return "*";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (typeof origin === "string") {
|
|
52
|
+
return origin === requestOrigin ? origin : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (Array.isArray(origin)) {
|
|
56
|
+
return origin.includes(requestOrigin) ? requestOrigin : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof origin === "function") {
|
|
60
|
+
return origin(requestOrigin, req) ?? null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return null;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const setCORSHeaders = (
|
|
67
|
+
headers: Headers,
|
|
68
|
+
req: Request,
|
|
69
|
+
options: CORSOptions,
|
|
70
|
+
): void => {
|
|
71
|
+
const requestOrigin = req.headers.get("Origin");
|
|
72
|
+
const allowedOrigin = resolveOrigin(options.origin, requestOrigin, req);
|
|
73
|
+
|
|
74
|
+
if (allowedOrigin) {
|
|
75
|
+
headers.set("Access-Control-Allow-Origin", allowedOrigin);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (options.credentials) {
|
|
79
|
+
headers.set("Access-Control-Allow-Credentials", "true");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (options.exposeHeaders?.length) {
|
|
83
|
+
headers.set(
|
|
84
|
+
"Access-Control-Expose-Headers",
|
|
85
|
+
options.exposeHeaders.join(", "),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const handlePreflight = (
|
|
91
|
+
req: Request,
|
|
92
|
+
options: CORSOptions,
|
|
93
|
+
): Response => {
|
|
94
|
+
const headers = new Headers();
|
|
95
|
+
const requestOrigin = req.headers.get("Origin");
|
|
96
|
+
const allowedOrigin = resolveOrigin(options.origin, requestOrigin, req);
|
|
97
|
+
|
|
98
|
+
if (allowedOrigin) {
|
|
99
|
+
headers.set("Access-Control-Allow-Origin", allowedOrigin);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (options.credentials) {
|
|
103
|
+
headers.set("Access-Control-Allow-Credentials", "true");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const allowMethods = options.allowMethods ?? DEFAULT_ALLOW_METHODS;
|
|
107
|
+
headers.set("Access-Control-Allow-Methods", allowMethods.join(", "));
|
|
108
|
+
|
|
109
|
+
const requestHeaders = req.headers.get("Access-Control-Request-Headers");
|
|
110
|
+
if (options.allowHeaders?.length) {
|
|
111
|
+
headers.set(
|
|
112
|
+
"Access-Control-Allow-Headers",
|
|
113
|
+
options.allowHeaders.join(", "),
|
|
114
|
+
);
|
|
115
|
+
} else if (requestHeaders) {
|
|
116
|
+
// Mirror the requested headers if no explicit allowHeaders configured
|
|
117
|
+
headers.set("Access-Control-Allow-Headers", requestHeaders);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (options.maxAge !== undefined) {
|
|
121
|
+
headers.set("Access-Control-Max-Age", options.maxAge.toString());
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return new Response(null, { status: 204, headers });
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const withCORS = (
|
|
128
|
+
response: Response,
|
|
129
|
+
req: Request,
|
|
130
|
+
options: CORSOptions,
|
|
131
|
+
): Response => {
|
|
132
|
+
const newHeaders = new Headers(response.headers);
|
|
133
|
+
setCORSHeaders(newHeaders, req, options);
|
|
134
|
+
|
|
135
|
+
return new Response(response.body, {
|
|
136
|
+
status: response.status,
|
|
137
|
+
statusText: response.statusText,
|
|
138
|
+
headers: newHeaders,
|
|
139
|
+
});
|
|
140
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -10,12 +10,14 @@ import {
|
|
|
10
10
|
MCPServer,
|
|
11
11
|
} from "./tools.ts";
|
|
12
12
|
import type { Binding, ContractBinding, MCPBinding } from "./wrangler.ts";
|
|
13
|
+
import { type CORSOptions, handlePreflight, withCORS } from "./cors.ts";
|
|
13
14
|
export { proxyConnectionForId } from "./bindings.ts";
|
|
14
15
|
export {
|
|
15
16
|
createMCPFetchStub,
|
|
16
17
|
type CreateStubAPIOptions,
|
|
17
18
|
type ToolBinder,
|
|
18
19
|
} from "./mcp.ts";
|
|
20
|
+
export { type CORSOptions, type CORSOrigin } from "./cors.ts";
|
|
19
21
|
|
|
20
22
|
export interface DefaultEnv<TSchema extends z.ZodTypeAny = any> {
|
|
21
23
|
MESH_REQUEST_CONTEXT: RequestContext<TSchema>;
|
|
@@ -56,6 +58,11 @@ export interface UserDefaultExport<
|
|
|
56
58
|
env: TEnv,
|
|
57
59
|
ctx: ExecutionContext,
|
|
58
60
|
) => Promise<Response> | Response;
|
|
61
|
+
/**
|
|
62
|
+
* CORS configuration options.
|
|
63
|
+
* Set to `false` to disable CORS handling entirely.
|
|
64
|
+
*/
|
|
65
|
+
cors?: CORSOptions | false;
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
// 1. Map binding type to its interface
|
|
@@ -228,6 +235,8 @@ export const withRuntime = <TEnv, TSchema extends z.ZodTypeAny = never>(
|
|
|
228
235
|
userFns: UserDefaultExport<TEnv, TSchema>,
|
|
229
236
|
): ExportedHandler<TEnv & DefaultEnv<TSchema>> => {
|
|
230
237
|
const server = createMCPServer<TEnv, TSchema>(userFns);
|
|
238
|
+
const corsOptions = userFns.cors;
|
|
239
|
+
|
|
231
240
|
const fetcher = async (
|
|
232
241
|
req: Request,
|
|
233
242
|
env: TEnv & DefaultEnv<TSchema>,
|
|
@@ -265,22 +274,38 @@ export const withRuntime = <TEnv, TSchema extends z.ZodTypeAny = never>(
|
|
|
265
274
|
new Response("Not found", { status: 404 })
|
|
266
275
|
);
|
|
267
276
|
};
|
|
277
|
+
|
|
268
278
|
return {
|
|
269
279
|
fetch: async (
|
|
270
280
|
req: Request,
|
|
271
281
|
env: TEnv & DefaultEnv<TSchema>,
|
|
272
282
|
ctx: ExecutionContext,
|
|
273
283
|
) => {
|
|
284
|
+
// Handle CORS preflight (OPTIONS) requests
|
|
285
|
+
if (corsOptions !== false && req.method === "OPTIONS") {
|
|
286
|
+
const options = corsOptions ?? {};
|
|
287
|
+
return handlePreflight(req, options);
|
|
288
|
+
}
|
|
289
|
+
|
|
274
290
|
const bindings = withBindings({
|
|
275
291
|
env,
|
|
276
292
|
server,
|
|
277
293
|
tokenOrContext: req.headers.get("x-mesh-token") ?? undefined,
|
|
278
294
|
url: req.url,
|
|
279
295
|
});
|
|
280
|
-
|
|
296
|
+
|
|
297
|
+
const response = await State.run(
|
|
281
298
|
{ req, env: bindings, ctx },
|
|
282
299
|
async () => await fetcher(req, bindings, ctx),
|
|
283
300
|
);
|
|
301
|
+
|
|
302
|
+
// Add CORS headers to response
|
|
303
|
+
if (corsOptions !== false) {
|
|
304
|
+
const options = corsOptions ?? {};
|
|
305
|
+
return withCORS(response, req, options);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return response;
|
|
284
309
|
},
|
|
285
310
|
};
|
|
286
311
|
};
|