diff --git a/docs/src/index.ts b/docs/src/index.ts --- a/docs/src/index.ts +++ b/docs/src/index.ts @@ -1,6 +1,8 @@ import { Hono } from "hono"; +import { cors } from "hono/cors"; import auth from "./routes/auth"; import subscribe from "./routes/subscribe"; +import "./lib/path-redirect"; type Bindings = { ASSETS: Fetcher; @@ -12,6 +14,14 @@ app.route("/oauth", auth); app.route("/subscribe", subscribe); +app.use("/subscribe", cors({ + origin: (origin) => origin, + credentials: true, +})); +app.use("/subscribe/*", cors({ + origin: (origin) => origin, + credentials: true, +})); app.get("/api/health", (c) => { return c.json({ status: "ok" }); diff --git a/docs/src/lib/oauth-client.ts b/docs/src/lib/oauth-client.ts --- a/docs/src/lib/oauth-client.ts +++ b/docs/src/lib/oauth-client.ts @@ -19,7 +19,7 @@ redirect_uris: [redirectUri], grant_types: ["authorization_code", "refresh_token"], response_types: ["code"], - scope: "atproto transition:generic", + scope: "atproto site.standard.graph.subscription", token_endpoint_auth_method: "none", application_type: "web", dpop_bound_access_tokens: true, diff --git a/docs/src/lib/path-redirect.ts b/docs/src/lib/path-redirect.ts new file mode 100644 --- /dev/null +++ b/docs/src/lib/path-redirect.ts @@ -0,0 +1,51 @@ +// Cloudflare Workers compatibility patches for @atproto libraries. +// +// 1. Workers don't support `redirect: 'error'` — simulate it with 'manual'. +// 2. Workers don't support the standard `cache` option in Request — strip it. + +function sanitizeInit(init?: RequestInit): RequestInit | undefined { + if (!init) return init; + const { cache, redirect, ...rest } = init; + return { + ...rest, + // Workers only support 'follow' and 'manual' + redirect: redirect === "error" ? "manual" : redirect, + // Workers don't support standard cache modes — omit entirely + ...(cache ? {} : {}), + }; +} + +const errorRedirectRequests = new WeakSet(); +const OriginalRequest = globalThis.Request; + +globalThis.Request = class extends OriginalRequest { + constructor( + input: RequestInfo | URL, + init?: RequestInit, + ) { + super(input, sanitizeInit(init)); + if (init?.redirect === "error") { + errorRedirectRequests.add(this); + } + } +} as typeof Request; + +const originalFetch = globalThis.fetch; +globalThis.fetch = (async ( + input: RequestInfo | URL, + init?: RequestInit, +): Promise => { + const cleanInit = sanitizeInit(init); + const response = await originalFetch(input, cleanInit); + + // Simulate redirect: 'error' — throw on 3xx + const wantsRedirectError = + init?.redirect === "error" || + (input instanceof Request && errorRedirectRequests.has(input)); + + if (wantsRedirectError && response.status >= 300 && response.status < 400) { + throw new TypeError("unexpected redirect"); + } + + return response; +}) as typeof fetch; diff --git a/docs/src/routes/auth.ts b/docs/src/routes/auth.ts --- a/docs/src/routes/auth.ts +++ b/docs/src/routes/auth.ts @@ -27,7 +27,7 @@ redirect_uris: [redirectUri], grant_types: ["authorization_code", "refresh_token"], response_types: ["code"], - scope: "atproto transition:generic", + scope: "atproto site.standard.graph.subscription", token_endpoint_auth_method: "none", application_type: "web", dpop_bound_access_tokens: true, @@ -44,7 +44,7 @@ const client = createOAuthClient(c.env.SEQUOIA_SESSIONS, c.env.CLIENT_URL); const authUrl = await client.authorize(handle, { - scope: "atproto transition:generic", + scope: "atproto site.standard.graph.subscription", }); return c.redirect(authUrl.toString());