|
1 | 1 | import { handleUnfurlRequest } from "cloudflare-workers-unfurl"; |
2 | | -import { AutoRouter, cors, error, IRequest } from "itty-router"; |
| 2 | +import { AutoRouter, error, IRequest } from "itty-router"; |
3 | 3 | import { Environment } from "./types"; |
4 | 4 |
|
5 | 5 | // make sure our sync durable object is made available to cloudflare |
6 | 6 | export { TldrawDurableObject } from "./TldrawDurableObject"; |
7 | 7 |
|
8 | | -// we use itty-router (https://itty.dev/) to handle routing. in this example we turn on CORS because |
9 | | -// we're hosting the worker separately to the client. you should restrict this to your own domain. |
10 | | -const { preflight, corsify } = cors({ origin: "*" }); |
| 8 | +const ALLOWED_ORIGINS = [ |
| 9 | + "https://roamresearch.com", |
| 10 | + "http://localhost:3000", |
| 11 | + "app://obsidian.md", |
| 12 | +]; |
| 13 | + |
| 14 | +const isVercelPreviewUrl = (origin: string): boolean => |
| 15 | + /^https:\/\/.*-discourse-graph-[a-z0-9]+\.vercel\.app$/.test(origin); |
| 16 | + |
| 17 | +const isAllowedOrigin = (origin: string): boolean => |
| 18 | + ALLOWED_ORIGINS.includes(origin) || |
| 19 | + ALLOWED_ORIGINS.some((allowedOrigin) => origin.startsWith(allowedOrigin)) || |
| 20 | + isVercelPreviewUrl(origin); |
| 21 | + |
| 22 | +const setCorsHeaders = ({ |
| 23 | + request, |
| 24 | + response, |
| 25 | +}: { |
| 26 | + request: IRequest; |
| 27 | + response: Response; |
| 28 | +}): Response => { |
| 29 | + const origin = request.headers.get("origin"); |
| 30 | + if (origin && isAllowedOrigin(origin)) { |
| 31 | + response.headers.set("Access-Control-Allow-Origin", origin); |
| 32 | + response.headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); |
| 33 | + response.headers.set( |
| 34 | + "Access-Control-Allow-Headers", |
| 35 | + "Content-Type, Authorization, x-vercel-protection-bypass", |
| 36 | + ); |
| 37 | + } |
| 38 | + return response; |
| 39 | +}; |
| 40 | + |
| 41 | +const handlePreflight = (request: IRequest): Response => { |
| 42 | + const origin = request.headers.get("origin"); |
| 43 | + if (!origin || !isAllowedOrigin(origin)) { |
| 44 | + return error(403, "Origin not allowed"); |
| 45 | + } |
| 46 | + |
| 47 | + return new Response(null, { |
| 48 | + status: 204, |
| 49 | + headers: { |
| 50 | + "Access-Control-Allow-Origin": origin, |
| 51 | + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", |
| 52 | + "Access-Control-Allow-Headers": |
| 53 | + "Content-Type, Authorization, x-vercel-protection-bypass", |
| 54 | + "Access-Control-Max-Age": "86400", |
| 55 | + }, |
| 56 | + }); |
| 57 | +}; |
| 58 | + |
| 59 | +const enforceAllowedOrigin = (request: IRequest): Response | void => { |
| 60 | + if (request.method === "OPTIONS") return; |
| 61 | + const origin = request.headers.get("origin"); |
| 62 | + if (origin && !isAllowedOrigin(origin)) { |
| 63 | + return error(403, "Origin not allowed"); |
| 64 | + } |
| 65 | +}; |
| 66 | + |
11 | 67 | const router = AutoRouter<IRequest, [env: Environment, ctx: ExecutionContext]>({ |
12 | | - before: [preflight], |
13 | | - finally: [corsify], |
| 68 | + before: [enforceAllowedOrigin], |
14 | 69 | catch: (e) => { |
15 | 70 | console.error(e); |
16 | 71 | return error(e); |
17 | 72 | }, |
18 | 73 | }) |
| 74 | + .options("*", handlePreflight) |
19 | 75 | // requests to /connect are routed to the Durable Object, and handle realtime websocket syncing |
20 | | - .get("/connect/:roomId", (request, env) => { |
| 76 | + .get("/connect/:roomId", async (request, env) => { |
21 | 77 | const id = env.TLDRAW_DURABLE_OBJECT.idFromName(request.params.roomId); |
22 | 78 | const room = env.TLDRAW_DURABLE_OBJECT.get(id); |
23 | | - return room.fetch(request.url, { |
| 79 | + const response = await room.fetch(request.url, { |
24 | 80 | headers: request.headers, |
25 | 81 | body: request.body, |
26 | 82 | }); |
| 83 | + return setCorsHeaders({ request, response }); |
27 | 84 | }) |
28 | 85 |
|
29 | 86 | // bookmarks need to extract metadata from pasted URLs: |
30 | | - .get("/unfurl", handleUnfurlRequest); |
| 87 | + .get("/unfurl", async (request) => { |
| 88 | + const response = await handleUnfurlRequest(request); |
| 89 | + return setCorsHeaders({ request, response }); |
| 90 | + }); |
31 | 91 |
|
32 | 92 | // export our router for cloudflare |
33 | 93 | export default router; |
0 commit comments