A CLI for publishing standard.site documents to ATProto sequoia.pub
standard site lexicon cli publishing

Add cors support

Co-Authored-By: @stevedylan.dev

authored by

Heath Stewart and committed by tangled.org 9f834bf6 4cbb91ee

+64 -3
+10
docs/src/index.ts
··· 1 1 import { Hono } from "hono"; 2 + import { cors } from "hono/cors"; 2 3 import auth from "./routes/auth"; 3 4 import subscribe from "./routes/subscribe"; 5 + import "./lib/path-redirect"; 4 6 5 7 type Bindings = { 6 8 ASSETS: Fetcher; ··· 12 14 13 15 app.route("/oauth", auth); 14 16 app.route("/subscribe", subscribe); 17 + app.use("/subscribe", cors({ 18 + origin: (origin) => origin, 19 + credentials: true, 20 + })); 21 + app.use("/subscribe/*", cors({ 22 + origin: (origin) => origin, 23 + credentials: true, 24 + })); 15 25 16 26 app.get("/api/health", (c) => { 17 27 return c.json({ status: "ok" });
+1 -1
docs/src/lib/oauth-client.ts
··· 19 19 redirect_uris: [redirectUri], 20 20 grant_types: ["authorization_code", "refresh_token"], 21 21 response_types: ["code"], 22 - scope: "atproto transition:generic", 22 + scope: "atproto site.standard.graph.subscription", 23 23 token_endpoint_auth_method: "none", 24 24 application_type: "web", 25 25 dpop_bound_access_tokens: true,
+51
docs/src/lib/path-redirect.ts
··· 1 + // Cloudflare Workers compatibility patches for @atproto libraries. 2 + // 3 + // 1. Workers don't support `redirect: 'error'` — simulate it with 'manual'. 4 + // 2. Workers don't support the standard `cache` option in Request — strip it. 5 + 6 + function sanitizeInit(init?: RequestInit): RequestInit | undefined { 7 + if (!init) return init; 8 + const { cache, redirect, ...rest } = init; 9 + return { 10 + ...rest, 11 + // Workers only support 'follow' and 'manual' 12 + redirect: redirect === "error" ? "manual" : redirect, 13 + // Workers don't support standard cache modes — omit entirely 14 + ...(cache ? {} : {}), 15 + }; 16 + } 17 + 18 + const errorRedirectRequests = new WeakSet<Request>(); 19 + const OriginalRequest = globalThis.Request; 20 + 21 + globalThis.Request = class extends OriginalRequest { 22 + constructor( 23 + input: RequestInfo | URL, 24 + init?: RequestInit, 25 + ) { 26 + super(input, sanitizeInit(init)); 27 + if (init?.redirect === "error") { 28 + errorRedirectRequests.add(this); 29 + } 30 + } 31 + } as typeof Request; 32 + 33 + const originalFetch = globalThis.fetch; 34 + globalThis.fetch = (async ( 35 + input: RequestInfo | URL, 36 + init?: RequestInit, 37 + ): Promise<Response> => { 38 + const cleanInit = sanitizeInit(init); 39 + const response = await originalFetch(input, cleanInit); 40 + 41 + // Simulate redirect: 'error' — throw on 3xx 42 + const wantsRedirectError = 43 + init?.redirect === "error" || 44 + (input instanceof Request && errorRedirectRequests.has(input)); 45 + 46 + if (wantsRedirectError && response.status >= 300 && response.status < 400) { 47 + throw new TypeError("unexpected redirect"); 48 + } 49 + 50 + return response; 51 + }) as typeof fetch;
+2 -2
docs/src/routes/auth.ts
··· 27 27 redirect_uris: [redirectUri], 28 28 grant_types: ["authorization_code", "refresh_token"], 29 29 response_types: ["code"], 30 - scope: "atproto transition:generic", 30 + scope: "atproto site.standard.graph.subscription", 31 31 token_endpoint_auth_method: "none", 32 32 application_type: "web", 33 33 dpop_bound_access_tokens: true, ··· 44 44 45 45 const client = createOAuthClient(c.env.SEQUOIA_SESSIONS, c.env.CLIENT_URL); 46 46 const authUrl = await client.authorize(handle, { 47 - scope: "atproto transition:generic", 47 + scope: "atproto site.standard.graph.subscription", 48 48 }); 49 49 50 50 return c.redirect(authUrl.toString());