Monorepo for Tangled
tangled.org
1import { Container } from "@cloudflare/containers";
2
3export interface Env {
4 BOBBIN: DurableObjectNamespace<BobbinContainer>;
5 RATE_LIMITER: RateLimit;
6 BOBBIN_HYDRANT_URL: string;
7 BOBBIN_SLINGSHOT_URL: string;
8 BOBBIN_LOG: string;
9}
10
11export class BobbinContainer extends Container {
12 defaultPort = 8090;
13 enableInternet = true;
14 // Bobbin maintains an in-memory index rebuilt from Hydrant replay on every
15 // restart, so we want to avoid sleeping where possible. Override
16 // onActivityExpired() to keep the container alive indefinitely.
17 sleepAfter = "24h";
18
19 onActivityExpired(): boolean {
20 // Keep the container running; bobbin's in-memory index is expensive to rebuild.
21 return true;
22 }
23
24 onError(error: Error) {
25 console.error("bobbin container error:", error);
26 }
27}
28
29const INDEX = `This is bobbin, Tangled's stateless XRPC API service: https://tangled.org/tangled.org/core/tree/master/bobbin`;
30
31export default {
32 async fetch(request: Request, env: Env): Promise<Response> {
33 const url = new URL(request.url);
34 if (url.pathname === "/" || url.pathname === "") {
35 return new Response(INDEX, { headers: { "Content-Type": "text/plain" } });
36 }
37
38 const ip = request.headers.get("cf-connecting-ip") ?? "unknown";
39 const { success } = await env.RATE_LIMITER.limit({ key: ip });
40 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",
51 },
52 },
53 );
54 }
55
56 const container = env.BOBBIN.getByName("primary");
57 await container.startAndWaitForPorts({
58 startOptions: {
59 envVars: {
60 BOBBIN_HYDRANT_URL: env.BOBBIN_HYDRANT_URL,
61 BOBBIN_SLINGSHOT_URL: env.BOBBIN_SLINGSHOT_URL,
62 BOBBIN_LOG: env.BOBBIN_LOG,
63 BOBBIN_LOG_FORMAT: "json",
64 },
65 },
66 });
67 return container.fetch(request);
68 },
69} satisfies ExportedHandler<Env>;