export-runtime 0.0.13 → 0.0.15
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/bin/generate-types.mjs +256 -10
- package/handler.js +49 -8
- package/package.json +1 -1
package/bin/generate-types.mjs
CHANGED
|
@@ -42,6 +42,11 @@ const r2Bindings = cloudflareConfig.r2 || [];
|
|
|
42
42
|
const kvBindings = cloudflareConfig.kv || [];
|
|
43
43
|
const authConfig = cloudflareConfig.auth || null;
|
|
44
44
|
|
|
45
|
+
// Optional: Security configuration
|
|
46
|
+
const securityConfig = pkg.security || {};
|
|
47
|
+
const accessConfig = securityConfig.access || {};
|
|
48
|
+
const allowedOrigins = accessConfig.origin || []; // empty = allow all (default Workers behavior)
|
|
49
|
+
|
|
45
50
|
// Auth requires a D1 database for better-auth
|
|
46
51
|
const allD1Bindings = [...d1Bindings];
|
|
47
52
|
if (authConfig && !allD1Bindings.includes("AUTH_DB")) {
|
|
@@ -81,11 +86,11 @@ function discoverModules(dir, base = "") {
|
|
|
81
86
|
const modules = [];
|
|
82
87
|
if (!fs.existsSync(dir)) return modules;
|
|
83
88
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
84
|
-
if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
|
|
89
|
+
if (entry.name.startsWith("_") || entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
85
90
|
const fullPath = path.join(dir, entry.name);
|
|
86
91
|
if (entry.isDirectory()) {
|
|
87
92
|
modules.push(...discoverModules(fullPath, base ? `${base}/${entry.name}` : entry.name));
|
|
88
|
-
} else if (EXTENSIONS.includes(path.extname(entry.name))) {
|
|
93
|
+
} else if (EXTENSIONS.includes(path.extname(entry.name)) && !entry.name.endsWith(".d.ts")) {
|
|
89
94
|
const nameWithoutExt = entry.name.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
90
95
|
const routePath = nameWithoutExt === "index"
|
|
91
96
|
? base // index.ts → directory path ("" for root)
|
|
@@ -391,16 +396,21 @@ const wranglerLines = [
|
|
|
391
396
|
``,
|
|
392
397
|
];
|
|
393
398
|
|
|
394
|
-
// Add static assets configuration if main is specified
|
|
399
|
+
// Add static assets configuration if main is specified and directory exists
|
|
395
400
|
if (assetsDir) {
|
|
396
401
|
const normalizedAssetsDir = assetsDir.startsWith("./") ? assetsDir : `./${assetsDir}`;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
402
|
+
const absoluteAssetsDir = path.resolve(cwd, normalizedAssetsDir);
|
|
403
|
+
if (fs.existsSync(absoluteAssetsDir)) {
|
|
404
|
+
wranglerLines.push(
|
|
405
|
+
`[assets]`,
|
|
406
|
+
`directory = "${normalizedAssetsDir}"`,
|
|
407
|
+
`binding = "ASSETS"`,
|
|
408
|
+
`run_worker_first = true`,
|
|
409
|
+
``,
|
|
410
|
+
);
|
|
411
|
+
} else {
|
|
412
|
+
console.log(`Note: Assets directory "${normalizedAssetsDir}" not found. Run "vite build" first to enable static assets.`);
|
|
413
|
+
}
|
|
404
414
|
}
|
|
405
415
|
|
|
406
416
|
// Add Durable Objects for shared state
|
|
@@ -465,16 +475,252 @@ export const d1Bindings = ${JSON.stringify(allD1Bindings)};
|
|
|
465
475
|
export const r2Bindings = ${JSON.stringify(r2Bindings)};
|
|
466
476
|
export const kvBindings = ${JSON.stringify(kvBindings)};
|
|
467
477
|
export const authConfig = ${JSON.stringify(authConfig)};
|
|
478
|
+
export const securityConfig = ${JSON.stringify({ access: { origin: allowedOrigins } })};
|
|
468
479
|
`;
|
|
469
480
|
fs.writeFileSync(configPath, configContent);
|
|
470
481
|
|
|
471
482
|
const wranglerPath = path.join(cwd, "wrangler.toml");
|
|
472
483
|
fs.writeFileSync(wranglerPath, wranglerLines.join("\n"));
|
|
473
484
|
|
|
485
|
+
// --- Generate .export-client.d.ts ---
|
|
486
|
+
|
|
487
|
+
const clientDtsLines = [
|
|
488
|
+
`// Auto-generated client type definitions. Do not edit manually.`,
|
|
489
|
+
``,
|
|
490
|
+
`/** D1 query result from tagged template literal */`,
|
|
491
|
+
`export interface ExportD1Query<T = Record<string, unknown>> {`,
|
|
492
|
+
` /** Execute query and return all results */`,
|
|
493
|
+
` all(): Promise<{ results: T[]; success: boolean; meta: object }>;`,
|
|
494
|
+
` /** Execute query and return first result */`,
|
|
495
|
+
` first<K extends keyof T>(colName?: K): Promise<K extends keyof T ? T[K] : T | null>;`,
|
|
496
|
+
` /** Execute query without returning results */`,
|
|
497
|
+
` run(): Promise<{ success: boolean; meta: object }>;`,
|
|
498
|
+
` /** Execute query and return raw array results */`,
|
|
499
|
+
` raw<K extends keyof T = keyof T>(): Promise<K extends keyof T ? T[K][] : unknown[][]>;`,
|
|
500
|
+
` /** Thenable - defaults to .all() */`,
|
|
501
|
+
` then<TResult = { results: T[]; success: boolean; meta: object }>(`,
|
|
502
|
+
` onfulfilled?: (value: { results: T[]; success: boolean; meta: object }) => TResult | PromiseLike<TResult>,`,
|
|
503
|
+
` onrejected?: (reason: any) => TResult | PromiseLike<TResult>`,
|
|
504
|
+
` ): Promise<TResult>;`,
|
|
505
|
+
`}`,
|
|
506
|
+
``,
|
|
507
|
+
`/** D1 database proxy - use as tagged template literal */`,
|
|
508
|
+
`export interface ExportD1Proxy {`,
|
|
509
|
+
` <T = Record<string, unknown>>(strings: TemplateStringsArray, ...values: unknown[]): ExportD1Query<T>;`,
|
|
510
|
+
`}`,
|
|
511
|
+
``,
|
|
512
|
+
`/** R2 object metadata */`,
|
|
513
|
+
`export interface ExportR2ObjectMeta {`,
|
|
514
|
+
` key: string;`,
|
|
515
|
+
` size: number;`,
|
|
516
|
+
` etag: string;`,
|
|
517
|
+
` httpEtag: string;`,
|
|
518
|
+
` uploaded: Date;`,
|
|
519
|
+
` httpMetadata?: Record<string, string>;`,
|
|
520
|
+
` customMetadata?: Record<string, string>;`,
|
|
521
|
+
`}`,
|
|
522
|
+
``,
|
|
523
|
+
`/** R2 object with body */`,
|
|
524
|
+
`export interface ExportR2Object extends ExportR2ObjectMeta {`,
|
|
525
|
+
` body: ReadableStream<Uint8Array>;`,
|
|
526
|
+
` bodyUsed: boolean;`,
|
|
527
|
+
` arrayBuffer(): Promise<ArrayBuffer>;`,
|
|
528
|
+
` text(): Promise<string>;`,
|
|
529
|
+
` json<T = unknown>(): Promise<T>;`,
|
|
530
|
+
` blob(): Promise<Blob>;`,
|
|
531
|
+
`}`,
|
|
532
|
+
``,
|
|
533
|
+
`/** R2 list result */`,
|
|
534
|
+
`export interface ExportR2ListResult {`,
|
|
535
|
+
` objects: ExportR2ObjectMeta[];`,
|
|
536
|
+
` truncated: boolean;`,
|
|
537
|
+
` cursor?: string;`,
|
|
538
|
+
` delimitedPrefixes: string[];`,
|
|
539
|
+
`}`,
|
|
540
|
+
``,
|
|
541
|
+
`/** R2 bucket proxy */`,
|
|
542
|
+
`export interface ExportR2Proxy {`,
|
|
543
|
+
` get(key: string, options?: { type?: "arrayBuffer" | "text" | "json" | "stream" }): Promise<ExportR2Object | null>;`,
|
|
544
|
+
` put(key: string, value: string | ArrayBuffer | ReadableStream | Blob, options?: {`,
|
|
545
|
+
` httpMetadata?: Record<string, string>;`,
|
|
546
|
+
` customMetadata?: Record<string, string>;`,
|
|
547
|
+
` }): Promise<ExportR2ObjectMeta>;`,
|
|
548
|
+
` delete(key: string | string[]): Promise<void>;`,
|
|
549
|
+
` list(options?: {`,
|
|
550
|
+
` prefix?: string;`,
|
|
551
|
+
` limit?: number;`,
|
|
552
|
+
` cursor?: string;`,
|
|
553
|
+
` delimiter?: string;`,
|
|
554
|
+
` include?: ("httpMetadata" | "customMetadata")[];`,
|
|
555
|
+
` }): Promise<ExportR2ListResult>;`,
|
|
556
|
+
` head(key: string): Promise<ExportR2ObjectMeta | null>;`,
|
|
557
|
+
`}`,
|
|
558
|
+
``,
|
|
559
|
+
`/** KV list result */`,
|
|
560
|
+
`export interface ExportKVListResult {`,
|
|
561
|
+
` keys: { name: string; expiration?: number; metadata?: unknown }[];`,
|
|
562
|
+
` list_complete: boolean;`,
|
|
563
|
+
` cursor?: string;`,
|
|
564
|
+
`}`,
|
|
565
|
+
``,
|
|
566
|
+
`/** KV namespace proxy */`,
|
|
567
|
+
`export interface ExportKVProxy {`,
|
|
568
|
+
` get<T = string>(key: string, options?: { type?: "text" | "json" | "arrayBuffer" | "stream"; cacheTtl?: number }): Promise<T | null>;`,
|
|
569
|
+
` put(key: string, value: string | ArrayBuffer | ReadableStream, options?: {`,
|
|
570
|
+
` expiration?: number;`,
|
|
571
|
+
` expirationTtl?: number;`,
|
|
572
|
+
` metadata?: unknown;`,
|
|
573
|
+
` }): Promise<void>;`,
|
|
574
|
+
` delete(key: string): Promise<void>;`,
|
|
575
|
+
` list(options?: {`,
|
|
576
|
+
` prefix?: string;`,
|
|
577
|
+
` limit?: number;`,
|
|
578
|
+
` cursor?: string;`,
|
|
579
|
+
` }): Promise<ExportKVListResult>;`,
|
|
580
|
+
` getWithMetadata<T = string, M = unknown>(key: string, options?: { type?: "text" | "json" | "arrayBuffer" | "stream"; cacheTtl?: number }): Promise<{`,
|
|
581
|
+
` value: T | null;`,
|
|
582
|
+
` metadata: M | null;`,
|
|
583
|
+
` }>;`,
|
|
584
|
+
`}`,
|
|
585
|
+
``,
|
|
586
|
+
`/** Auth sign-in methods */`,
|
|
587
|
+
`export interface ExportAuthSignIn {`,
|
|
588
|
+
` /** Sign in with OAuth provider */`,
|
|
589
|
+
` social(provider: string, options?: { callbackURL?: string; scopes?: string[] }): Promise<{ redirectUrl?: string; token?: string; user?: object }>;`,
|
|
590
|
+
` /** Sign in with email and password */`,
|
|
591
|
+
` email(email: string, password: string, options?: object): Promise<{ success: boolean; token?: string; user?: object; error?: string }>;`,
|
|
592
|
+
`}`,
|
|
593
|
+
``,
|
|
594
|
+
`/** Auth sign-up methods */`,
|
|
595
|
+
`export interface ExportAuthSignUp {`,
|
|
596
|
+
` /** Sign up with email, password, and name */`,
|
|
597
|
+
` email(email: string, password: string, name?: string, options?: object): Promise<{ success: boolean; token?: string; user?: object; error?: string }>;`,
|
|
598
|
+
`}`,
|
|
599
|
+
``,
|
|
600
|
+
`/** Auth session */`,
|
|
601
|
+
`export interface ExportAuthSession {`,
|
|
602
|
+
` token: string;`,
|
|
603
|
+
` userId: string;`,
|
|
604
|
+
` expiresAt: Date;`,
|
|
605
|
+
`}`,
|
|
606
|
+
``,
|
|
607
|
+
`/** Auth user */`,
|
|
608
|
+
`export interface ExportAuthUser {`,
|
|
609
|
+
` id: string;`,
|
|
610
|
+
` email: string;`,
|
|
611
|
+
` name?: string;`,
|
|
612
|
+
` image?: string;`,
|
|
613
|
+
` emailVerified: boolean;`,
|
|
614
|
+
` createdAt: Date;`,
|
|
615
|
+
` updatedAt: Date;`,
|
|
616
|
+
`}`,
|
|
617
|
+
``,
|
|
618
|
+
`/** Auth client proxy */`,
|
|
619
|
+
`export interface ExportAuthProxy {`,
|
|
620
|
+
` signIn: ExportAuthSignIn;`,
|
|
621
|
+
` signUp: ExportAuthSignUp;`,
|
|
622
|
+
` signOut(): Promise<{ success: boolean }>;`,
|
|
623
|
+
` getSession(): Promise<ExportAuthSession | null>;`,
|
|
624
|
+
` getUser(): Promise<ExportAuthUser | null>;`,
|
|
625
|
+
` setToken(token: string): Promise<{ success: boolean }>;`,
|
|
626
|
+
` readonly isAuthenticated: boolean;`,
|
|
627
|
+
`}`,
|
|
628
|
+
``,
|
|
629
|
+
];
|
|
630
|
+
|
|
631
|
+
// Generate D1 type mapping
|
|
632
|
+
if (allD1Bindings.length > 0) {
|
|
633
|
+
clientDtsLines.push(`/** D1 database bindings */`);
|
|
634
|
+
clientDtsLines.push(`export interface ExportD1Bindings {`);
|
|
635
|
+
for (const name of allD1Bindings) {
|
|
636
|
+
clientDtsLines.push(` ${name}: ExportD1Proxy;`);
|
|
637
|
+
}
|
|
638
|
+
clientDtsLines.push(`}`);
|
|
639
|
+
clientDtsLines.push(``);
|
|
640
|
+
} else {
|
|
641
|
+
clientDtsLines.push(`export interface ExportD1Bindings {}`);
|
|
642
|
+
clientDtsLines.push(``);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Generate R2 type mapping
|
|
646
|
+
if (r2Bindings.length > 0) {
|
|
647
|
+
clientDtsLines.push(`/** R2 bucket bindings */`);
|
|
648
|
+
clientDtsLines.push(`export interface ExportR2Bindings {`);
|
|
649
|
+
for (const name of r2Bindings) {
|
|
650
|
+
clientDtsLines.push(` ${name}: ExportR2Proxy;`);
|
|
651
|
+
}
|
|
652
|
+
clientDtsLines.push(`}`);
|
|
653
|
+
clientDtsLines.push(``);
|
|
654
|
+
} else {
|
|
655
|
+
clientDtsLines.push(`export interface ExportR2Bindings {}`);
|
|
656
|
+
clientDtsLines.push(``);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Generate KV type mapping
|
|
660
|
+
if (kvBindings.length > 0) {
|
|
661
|
+
clientDtsLines.push(`/** KV namespace bindings */`);
|
|
662
|
+
clientDtsLines.push(`export interface ExportKVBindings {`);
|
|
663
|
+
for (const name of kvBindings) {
|
|
664
|
+
clientDtsLines.push(` ${name}: ExportKVProxy;`);
|
|
665
|
+
}
|
|
666
|
+
clientDtsLines.push(`}`);
|
|
667
|
+
clientDtsLines.push(``);
|
|
668
|
+
} else {
|
|
669
|
+
clientDtsLines.push(`export interface ExportKVBindings {}`);
|
|
670
|
+
clientDtsLines.push(``);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Generate main client interface
|
|
674
|
+
clientDtsLines.push(`/** Export client with typed storage bindings */`);
|
|
675
|
+
clientDtsLines.push(`export interface ExportClient {`);
|
|
676
|
+
clientDtsLines.push(` d1: ExportD1Bindings;`);
|
|
677
|
+
clientDtsLines.push(` r2: ExportR2Bindings;`);
|
|
678
|
+
clientDtsLines.push(` kv: ExportKVBindings;`);
|
|
679
|
+
clientDtsLines.push(` auth: ${authConfig ? 'ExportAuthProxy' : 'null'};`);
|
|
680
|
+
clientDtsLines.push(`}`);
|
|
681
|
+
clientDtsLines.push(``);
|
|
682
|
+
clientDtsLines.push(`declare const client: ExportClient;`);
|
|
683
|
+
clientDtsLines.push(`export default client;`);
|
|
684
|
+
clientDtsLines.push(``);
|
|
685
|
+
|
|
686
|
+
const clientDtsPath = path.join(cwd, ".export-client.d.ts");
|
|
687
|
+
fs.writeFileSync(clientDtsPath, clientDtsLines.join("\n"));
|
|
688
|
+
|
|
689
|
+
// --- Generate export.d.ts (module declaration helper) ---
|
|
690
|
+
|
|
691
|
+
const moduleDtsLines = [
|
|
692
|
+
`// Type declarations for your export worker.`,
|
|
693
|
+
`// Update the URL to match your deployed worker or local dev server.`,
|
|
694
|
+
`//`,
|
|
695
|
+
`// Usage in your client code:`,
|
|
696
|
+
`// import client, { myFunction } from "https://my-worker.workers.dev";`,
|
|
697
|
+
`// const result = await client.d1.MY_DB\`SELECT * FROM users\`;`,
|
|
698
|
+
``,
|
|
699
|
+
`declare module "http://localhost:8787" {`,
|
|
700
|
+
` export * from "./.export-client";`,
|
|
701
|
+
` export { default } from "./.export-client";`,
|
|
702
|
+
`}`,
|
|
703
|
+
``,
|
|
704
|
+
`// Add more module declarations for your deployed URLs:`,
|
|
705
|
+
`// declare module "https://my-worker.workers.dev" {`,
|
|
706
|
+
`// export * from "./.export-client";`,
|
|
707
|
+
`// export { default } from "./.export-client";`,
|
|
708
|
+
`// }`,
|
|
709
|
+
``,
|
|
710
|
+
];
|
|
711
|
+
|
|
712
|
+
const moduleDtsPath = path.join(cwd, "export.d.ts");
|
|
713
|
+
// Only write if it doesn't exist (user may have customized it)
|
|
714
|
+
if (!fs.existsSync(moduleDtsPath)) {
|
|
715
|
+
fs.writeFileSync(moduleDtsPath, moduleDtsLines.join("\n"));
|
|
716
|
+
console.log("Generated module declarations →", moduleDtsPath);
|
|
717
|
+
}
|
|
718
|
+
|
|
474
719
|
// --- Output summary ---
|
|
475
720
|
|
|
476
721
|
console.log(`Discovered ${modules.length} module(s): ${modules.map(m => m.routePath || "/").join(", ")}`);
|
|
477
722
|
console.log("Generated type definitions + minified core →", outPath);
|
|
478
723
|
console.log("Generated module map →", moduleMapPath);
|
|
479
724
|
console.log("Generated shared import module →", sharedModulePath);
|
|
725
|
+
console.log("Generated client types →", clientDtsPath);
|
|
480
726
|
console.log("Generated wrangler.toml →", wranglerPath);
|
package/handler.js
CHANGED
|
@@ -5,14 +5,14 @@ import { handleAuthRoute, getSessionFromRequest, verifySession } from "./auth.js
|
|
|
5
5
|
|
|
6
6
|
const JS = "application/javascript; charset=utf-8";
|
|
7
7
|
const TS = "application/typescript; charset=utf-8";
|
|
8
|
-
const CORS = { "Access-Control-Allow-Origin": "*" };
|
|
9
8
|
const IMMUTABLE = "public, max-age=31536000, immutable";
|
|
10
9
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
const createResponseHelpers = (corsHeaders) => ({
|
|
11
|
+
jsResponse: (body, extra = {}) =>
|
|
12
|
+
new Response(body, { headers: { "Content-Type": JS, ...corsHeaders, ...extra } }),
|
|
13
|
+
tsResponse: (body, status = 200) =>
|
|
14
|
+
new Response(body, { status, headers: { "Content-Type": TS, ...corsHeaders, "Cache-Control": "no-cache" } }),
|
|
15
|
+
});
|
|
16
16
|
|
|
17
17
|
export const createHandler = (moduleMap, generatedTypes, minifiedCore, coreId, minifiedSharedCore, exportConfig = {}) => {
|
|
18
18
|
// moduleMap: { routePath: moduleNamespace, ... }
|
|
@@ -28,7 +28,33 @@ export const createHandler = (moduleMap, generatedTypes, minifiedCore, coreId, m
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// Export configuration
|
|
31
|
-
const { d1Bindings = [], r2Bindings = [], kvBindings = [], authConfig = null } = exportConfig;
|
|
31
|
+
const { d1Bindings = [], r2Bindings = [], kvBindings = [], authConfig = null, securityConfig = {} } = exportConfig;
|
|
32
|
+
|
|
33
|
+
// Security: allowed origins (empty array = allow all)
|
|
34
|
+
const allowedOrigins = securityConfig?.access?.origin || [];
|
|
35
|
+
const hasOriginRestriction = allowedOrigins.length > 0;
|
|
36
|
+
|
|
37
|
+
// Check if origin is allowed
|
|
38
|
+
const isOriginAllowed = (origin) => {
|
|
39
|
+
if (!hasOriginRestriction) return true;
|
|
40
|
+
if (!origin) return false;
|
|
41
|
+
return allowedOrigins.includes(origin);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Get CORS headers for a request
|
|
45
|
+
const getCorsHeaders = (request) => {
|
|
46
|
+
const origin = request.headers.get("Origin");
|
|
47
|
+
if (!hasOriginRestriction) {
|
|
48
|
+
return { "Access-Control-Allow-Origin": "*" };
|
|
49
|
+
}
|
|
50
|
+
if (origin && isOriginAllowed(origin)) {
|
|
51
|
+
return {
|
|
52
|
+
"Access-Control-Allow-Origin": origin,
|
|
53
|
+
"Vary": "Origin",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {};
|
|
57
|
+
};
|
|
32
58
|
const hasClient = d1Bindings.length > 0 || r2Bindings.length > 0 || kvBindings.length > 0 || authConfig;
|
|
33
59
|
|
|
34
60
|
// Generate core code with config
|
|
@@ -331,9 +357,24 @@ export const createHandler = (moduleMap, generatedTypes, minifiedCore, coreId, m
|
|
|
331
357
|
async fetch(request, env) {
|
|
332
358
|
const url = new URL(request.url);
|
|
333
359
|
const isShared = url.searchParams.has("shared");
|
|
360
|
+
const origin = request.headers.get("Origin");
|
|
361
|
+
|
|
362
|
+
// --- Origin check ---
|
|
363
|
+
if (hasOriginRestriction && origin && !isOriginAllowed(origin)) {
|
|
364
|
+
return new Response("Forbidden: Origin not allowed", { status: 403 });
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Get CORS headers for this request
|
|
368
|
+
const corsHeaders = getCorsHeaders(request);
|
|
369
|
+
const { jsResponse, tsResponse } = createResponseHelpers(corsHeaders);
|
|
334
370
|
|
|
335
371
|
// --- WebSocket upgrade ---
|
|
336
372
|
if (request.headers.get("Upgrade") === "websocket") {
|
|
373
|
+
// Origin check for WebSocket (browsers send Origin header)
|
|
374
|
+
if (hasOriginRestriction && origin && !isOriginAllowed(origin)) {
|
|
375
|
+
return new Response("Forbidden: Origin not allowed", { status: 403 });
|
|
376
|
+
}
|
|
377
|
+
|
|
337
378
|
const pair = new WebSocketPair();
|
|
338
379
|
const [client, server] = Object.values(pair);
|
|
339
380
|
server.accept();
|
|
@@ -393,7 +434,7 @@ export const createHandler = (moduleMap, generatedTypes, minifiedCore, coreId, m
|
|
|
393
434
|
if (env?.ASSETS) {
|
|
394
435
|
return env.ASSETS.fetch(request);
|
|
395
436
|
}
|
|
396
|
-
return new Response("Not found", { status: 404 });
|
|
437
|
+
return new Response("Not found", { status: 404, headers: corsHeaders });
|
|
397
438
|
}
|
|
398
439
|
|
|
399
440
|
const { route, exportName } = resolved;
|