Webhooks for the AT Protocol airglow.run
atproto atprotocol automation webhook
12
fork

Configure Feed

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

at refactor/registry-pattern 109 lines 4.6 kB view raw view rendered
1# Dev server and SSR architecture 2 3Airglow uses HonoX for file-based routing with islands architecture. This creates a split-runtime situation during development: the server-side code runs on Node (via Vite), while production runs on Bun. 4 5## How HonoX dev mode works 6 7``` 8Browser request 9 10Vite dev server (Node) 11 12HonoX vite plugin (SSR middleware) 13 14app/server.ts → routes → lib/* 15 16HTML response (SSR) + client JS (HMR) 17``` 18 19`vp dev` (Vite+) starts a Vite dev server. HonoX registers a middleware via `configureServer` that intercepts all requests and runs them through the Hono app using Vite's SSR module runner. Route files in `app/routes/` are discovered and registered automatically. 20 21The key constraint: **all server-side code executes in Node**, not Bun. This means: 22 23- `bun:sqlite` is not available (see [sqlite-bun-compat.md](./sqlite-bun-compat.md)) 24- CJS packages must be loaded via `createRequire()` (see [oauth.md](./oauth.md)) 25- `.env` must be loaded manually for Node 26 27## HonoX's `noExternal: true` 28 29HonoX's Vite plugin unconditionally sets: 30 31```ts 32{ 33 ssr: { 34 noExternal: true; 35 } 36} 37``` 38 39This tells Vite to ESM-transform **all** `node_modules` during SSR instead of letting Node load them natively. This is required for HonoX's file-based routing to work (it needs to process route imports through Vite's module graph). But it breaks CJS packages that use `module.exports` or `require()`. 40 41Workarounds: 42 43- **Vite aliases** redirect `bun:sqlite` and `better-sqlite3` to a `createRequire()` shim 44- **`createRequire()`** loads `@atproto/oauth-client-node` bypassing Vite's transform 45 46## PDS proxy 47 48The local PDS runs in Docker on `localhost:3000` but is configured with hostname `pds.dev`. The browser needs to interact with the PDS (OAuth authorize page, asset loading), but `pds.dev` is unreachable. 49 50A custom Vite plugin (`pdsProxy` in `vite.config.ts`) solves this: 51 52``` 53Browser → http://127.0.0.1:5175/oauth/authorize 54 ↓ (Vite middleware, before HonoX) 55pdsProxy plugin 56 ↓ (http.request to localhost:3000) 57Local PDS 58 ↓ (response with pds.dev URLs rewritten to 127.0.0.1:5175) 59Browser 60``` 61 62### Plugin lifecycle 63 641. At plugin creation time, `getPdsIssuer()` synchronously fetches the PDS issuer URL via `execSync` + `curl`. This must be synchronous because Vite's `defineConfig` doesn't support async in all contexts. 652. In `configureServer`, the middleware is registered **before** HonoX's middleware (plugin order matters — `pdsProxy()` is listed first in the `plugins` array). 663. Requests matching `/oauth/`, `/.well-known/`, `/xrpc/`, `/@atproto/` are proxied. 67 68### Why `http.request()` and not `fetch()` 69 70Node's `fetch()` follows the Fetch spec and silently strips `sec-fetch-*` headers. The PDS validates these headers: 71 72- `sec-fetch-site: same-origin` — required for the PDS to accept the request 73- `sec-fetch-mode: navigate` — required for page loads (the browser sends this naturally) 74 75`http.request()` from `node:http` forwards headers as-is. 76 77### Response rewriting 78 79Text responses (HTML, JSON, JavaScript) have all occurrences of the PDS issuer URL (e.g. `https://pds.dev`) replaced with the app origin (`http://127.0.0.1:5175`). This ensures: 80 81- The PDS OAuth SPA's internal API calls go through the proxy 82- Redirect URLs in JSON responses point to the proxy 83- Location headers in 3xx responses are rewritten 84 85Additionally: 86 87- `Strict-Transport-Security` headers are stripped (we're on HTTP) 88- `upgrade-insecure-requests` is removed from CSP 89- `Secure` flag is stripped from cookies 90 91## Production 92 93In production, none of these workarounds apply: 94 95- `bun run start` runs the Hono app directly under Bun 96- `bun:sqlite` resolves natively 97- CJS packages are loaded by Bun's module system 98- No PDS proxy (the app and PDS are on reachable domains) 99- No `.env` loader needed (Bun auto-loads it) 100 101## Key configuration 102 103| Setting | Dev | Production | 104| ----------------- | --------------------------------- | --------------------------------- | 105| Runtime | Node (via Vite) | Bun | 106| SQLite driver | `better-sqlite3` (via alias) | `bun:sqlite` | 107| PDS access | Vite proxy + URL rewriting | Direct HTTPS | 108| `.env` loading | Manual loader in `lib/config.ts` | Bun auto-loads | 109| OAuth client type | Loopback (`http://localhost?...`) | Confidential (HTTPS metadata URL) |