Monorepo for Tangled tangled.org
1

Configure Feed

Select the types of activity you want to include in your feed.

bobbin/worker: add cors headers

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>

+57 -16
+57 -16
bobbin/worker/src/index.ts
··· 16 16 // onActivityExpired() to keep the container alive indefinitely. 17 17 sleepAfter = "24h"; 18 18 19 - onActivityExpired(): boolean { 20 - // Keep the container running; bobbin's in-memory index is expensive to rebuild. 21 - return true; 19 + async onActivityExpired(): Promise<void> { 20 + // Keep the container running; bobbin's in-memory index is expensive to 21 + // rebuild. Renew the timeout instead of stopping so we're pinged again later. 22 + this.renewActivityTimeout(); 22 23 } 23 24 24 25 onError(error: Error) { ··· 28 29 29 30 const INDEX = `This is bobbin, Tangled's stateless XRPC API service: https://tangled.org/tangled.org/core/tree/master/bobbin`; 30 31 32 + const CORS_HEADERS = { 33 + "Access-Control-Allow-Origin": "*", 34 + "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS", 35 + "Access-Control-Allow-Headers": "Content-Type, Authorization", 36 + "Access-Control-Max-Age": "86400", 37 + }; 38 + 39 + function withCors(response: Response): Response { 40 + const headers = new Headers(response.headers); 41 + for (const [name, value] of Object.entries(CORS_HEADERS)) { 42 + headers.set(name, value); 43 + } 44 + 45 + // Responses with these statuses must not carry a body; a non-null body 46 + // crashes workerd, so null it out. 47 + const body = [101, 204, 205, 304].includes(response.status) 48 + ? null 49 + : response.body; 50 + 51 + return new Response(body, { 52 + status: response.status, 53 + statusText: response.statusText, 54 + headers, 55 + }); 56 + } 57 + 31 58 export default { 32 59 async fetch(request: Request, env: Env): Promise<Response> { 60 + if (request.method === "OPTIONS") { 61 + return new Response(null, { status: 204, headers: CORS_HEADERS }); 62 + } 63 + 33 64 const url = new URL(request.url); 34 65 if (url.pathname === "/" || url.pathname === "") { 35 - return new Response(INDEX, { headers: { "Content-Type": "text/plain" } }); 66 + return withCors( 67 + new Response(INDEX, { headers: { "Content-Type": "text/plain" } }), 68 + ); 36 69 } 37 70 38 71 const ip = request.headers.get("cf-connecting-ip") ?? "unknown"; 39 72 const { success } = await env.RATE_LIMITER.limit({ key: ip }); 40 73 if (!success) { 41 - return new Response( 42 - JSON.stringify({ 43 - error: "RateLimitExceeded", 44 - message: "too many requests, slow down", 45 - }), 46 - { 47 - status: 429, 48 - headers: { 49 - "Content-Type": "application/json", 50 - "Retry-After": "60", 74 + return withCors( 75 + new Response( 76 + JSON.stringify({ 77 + error: "RateLimitExceeded", 78 + message: "too many requests, slow down", 79 + }), 80 + { 81 + status: 429, 82 + headers: { 83 + "Content-Type": "application/json", 84 + "Retry-After": "60", 85 + }, 51 86 }, 52 - }, 87 + ), 53 88 ); 54 89 } 55 90 ··· 64 99 }, 65 100 }, 66 101 }); 67 - return container.fetch(request); 102 + const response = await container.fetch(request); 103 + // A 101 is a protocol switch (e.g. WebSocket upgrade); return it untouched 104 + // so we don't strip the connection off the response. 105 + if (response.status === 101) { 106 + return response; 107 + } 108 + return withCors(response); 68 109 }, 69 110 } satisfies ExportedHandler<Env>;