open-source, lexicon-agnostic PDS for AI agents. welcome-mat enrollment, AT Proto federation.
agents atprotocol pds cloudflare
7
fork

Configure Feed

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

Add CORS middleware for browser-based AT Protocol clients

Register Hono's built-in cors() middleware on all routes with
Access-Control-Allow-Origin: * so browser-based clients (PDSls,
atproto-browser, etc.) can access pds.solpbc.org. OPTIONS preflight
returns 204 before reaching requestCrawl middleware. Adds three
CORS tests covering GET, OPTIONS preflight, and POST error responses.

+53
+9
src/worker.ts
··· 2 2 export { SequencerDurableObject } from "./sequencer-do"; 3 3 4 4 import { Hono } from "hono"; 5 + import { cors } from "hono/cors"; 5 6 import { 6 7 RepoNotFoundError, 7 8 initDirectory, ··· 68 69 } 69 70 70 71 const app = new Hono<{ Bindings: Env }>(); 72 + 73 + app.use("*", cors({ 74 + origin: "*", 75 + allowMethods: ["GET", "HEAD", "POST", "OPTIONS"], 76 + allowHeaders: ["Content-Type", "Authorization", "DPoP"], 77 + exposeHeaders: ["Content-Type"], 78 + maxAge: 86400, 79 + })); 71 80 72 81 app.use("*", async (c, next) => { 73 82 await requestCrawl(c.env);
+44
test/worker-routes.test.ts
··· 96 96 await initDirectory(env.DIRECTORY); 97 97 }); 98 98 99 + describe("CORS middleware", () => { 100 + it("includes CORS headers on GET responses", async () => { 101 + const response = await worker.fetch("http://localhost/"); 102 + expect(response.status).toBe(200); 103 + expect(response.headers.get("access-control-allow-origin")).toBe("*"); 104 + }); 105 + 106 + it("handles OPTIONS preflight before route middleware", async () => { 107 + const response = await worker.fetch( 108 + new Request("http://localhost/xrpc/com.atproto.repo.getRecord", { 109 + method: "OPTIONS", 110 + headers: { 111 + origin: "https://example.com", 112 + "access-control-request-method": "GET", 113 + "access-control-request-headers": "Authorization, DPoP", 114 + }, 115 + }), 116 + ); 117 + 118 + expect(response.status).toBe(204); 119 + expect(response.headers.get("access-control-allow-origin")).toBe("*"); 120 + expect(response.headers.get("access-control-allow-methods")).toContain("GET"); 121 + expect(response.headers.get("access-control-allow-methods")).toContain("POST"); 122 + expect(response.headers.get("access-control-allow-headers")).toContain("Authorization"); 123 + expect(response.headers.get("access-control-allow-headers")).toContain("DPoP"); 124 + expect(response.headers.get("access-control-max-age")).toBe("86400"); 125 + }); 126 + 127 + it("includes CORS headers on POST error responses", async () => { 128 + const response = await worker.fetch( 129 + new Request("http://localhost/xrpc/com.atproto.repo.createRecord", { 130 + method: "POST", 131 + headers: { 132 + "content-type": "application/json", 133 + }, 134 + body: JSON.stringify({}), 135 + }), 136 + ); 137 + 138 + expect(response.status).toBe(401); 139 + expect(response.headers.get("access-control-allow-origin")).toBe("*"); 140 + }); 141 + }); 142 + 99 143 it("serves welcome and terms documents", async () => { 100 144 const welcome = await worker.fetch("http://localhost/.well-known/welcome.md"); 101 145 expect(welcome.status).toBe(200);