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.

feat: oauth

Hugo 1deb63d5 ebf76d90

+745 -42
+4 -2
.env.example
··· 1 - PORT=3000 1 + PORT=5175 2 2 DATABASE_PATH=./data/airglow.db 3 - PUBLIC_URL=http://localhost:3000 3 + PUBLIC_URL=http://127.0.0.1:5175 4 + PDS_URL=http://localhost:3000 4 5 JETSTREAM_URL=wss://jetstream2.us-east.bsky.network/subscribe 6 + COOKIE_SECRET= 5 7 NSID_ALLOWLIST= 6 8 NSID_BLOCKLIST=
+4
app/global.d.ts
··· 1 1 import type {} from "hono"; 2 + import type { SessionUser } from "@/auth/middleware.js"; 2 3 3 4 type Head = { 4 5 title?: string; ··· 7 8 declare module "hono" { 8 9 interface ContextRenderer { 9 10 (content: string | Promise<string>, head?: Head): Response | Promise<Response>; 11 + } 12 + interface ContextVariableMap { 13 + user: SessionUser; 10 14 } 11 15 }
+60
app/routes/auth/callback.tsx
··· 1 + import { createRoute } from "honox/factory"; 2 + import { setSignedCookie } from "hono/cookie"; 3 + import { getOAuthClient } from "@/auth/client.js"; 4 + import { config } from "@/config.js"; 5 + import { db } from "@/db/index.js"; 6 + import { users } from "@/db/schema.js"; 7 + 8 + const COOKIE_NAME = "airglow_session"; 9 + 10 + export default createRoute(async (c) => { 11 + const params = new URL(c.req.url).searchParams; 12 + 13 + // Check for OAuth error response 14 + if (params.has("error")) { 15 + const description = params.get("error_description") || params.get("error"); 16 + return c.render( 17 + <div> 18 + <h1>Authentication failed</h1> 19 + <p>{description}</p> 20 + <a href="/auth/login">Try again</a> 21 + </div>, 22 + { title: "Error — Airglow" }, 23 + ); 24 + } 25 + 26 + try { 27 + const client = await getOAuthClient(); 28 + const { session, state } = await client.callback(params); 29 + 30 + const did = session.did; 31 + const handle = state || did; 32 + 33 + // Upsert user 34 + await db.insert(users).values({ did, handle, createdAt: new Date() }).onConflictDoUpdate({ 35 + target: users.did, 36 + set: { handle }, 37 + }); 38 + 39 + // Set session cookie 40 + await setSignedCookie(c, COOKIE_NAME, did, config.cookieSecret, { 41 + path: "/", 42 + httpOnly: true, 43 + secure: config.publicUrl.startsWith("https"), 44 + sameSite: "Lax", 45 + maxAge: 60 * 60 * 24 * 30, // 30 days 46 + }); 47 + 48 + return c.redirect("/dashboard"); 49 + } catch (err) { 50 + console.error("OAuth callback error:", err); 51 + return c.render( 52 + <div> 53 + <h1>Authentication failed</h1> 54 + <p>Something went wrong during authentication. Please try again.</p> 55 + <a href="/auth/login">Try again</a> 56 + </div>, 57 + { title: "Error — Airglow" }, 58 + ); 59 + } 60 + });
+49
app/routes/auth/login.tsx
··· 1 + import { createRoute } from "honox/factory"; 2 + import { getOAuthClient, resolveHandle, rewritePdsUrl } from "@/auth/client.js"; 3 + import { getSessionUser } from "@/auth/middleware.js"; 4 + 5 + export default createRoute(async (c) => { 6 + const user = await getSessionUser(c); 7 + if (user) return c.redirect("/dashboard"); 8 + 9 + const error = c.req.query("error"); 10 + 11 + return c.render( 12 + <div> 13 + <h1>Sign in to Airglow</h1> 14 + {error && <p style="color: red">{error}</p>} 15 + <form method="post" action="/auth/login"> 16 + <label> 17 + Handle or DID 18 + <input type="text" name="handle" placeholder="you.bsky.social" required autofocus /> 19 + </label> 20 + <button type="submit">Sign in with AT Protocol</button> 21 + </form> 22 + </div>, 23 + { title: "Sign in — Airglow" }, 24 + ); 25 + }); 26 + 27 + export const POST = createRoute(async (c) => { 28 + const body = await c.req.parseBody(); 29 + const handle = body.handle; 30 + 31 + if (typeof handle !== "string" || !handle.trim()) { 32 + return c.redirect("/auth/login?error=Handle+is+required"); 33 + } 34 + 35 + try { 36 + const input = handle.trim(); 37 + const did = await resolveHandle(input); 38 + const client = await getOAuthClient(); 39 + const url = await client.authorize(did, { 40 + state: input, 41 + }); 42 + // Rewrite PDS hostname for browser → goes through Vite proxy in dev 43 + const redirectUrl = rewritePdsUrl(url.toString(), "browser"); 44 + return c.redirect(redirectUrl); 45 + } catch (err) { 46 + console.error("OAuth authorize error:", err); 47 + return c.redirect("/auth/login?error=Could+not+start+authentication"); 48 + } 49 + });
+7
app/routes/auth/signout.ts
··· 1 + import { createRoute } from "honox/factory"; 2 + import { deleteCookie } from "hono/cookie"; 3 + 4 + export const POST = createRoute((c) => { 5 + deleteCookie(c, "airglow_session", { path: "/" }); 6 + return c.redirect("/"); 7 + });
+4
app/routes/dashboard/_middleware.ts
··· 1 + import { createMiddleware } from "hono/factory"; 2 + import { requireAuth } from "@/auth/middleware.js"; 3 + 4 + export default [createMiddleware(requireAuth)];
+24
app/routes/dashboard/index.tsx
··· 1 + import { createRoute } from "honox/factory"; 2 + 3 + export default createRoute((c) => { 4 + const user = c.get("user"); 5 + 6 + return c.render( 7 + <div> 8 + <header> 9 + <h1>Dashboard</h1> 10 + <p> 11 + Signed in as <strong>{user.handle}</strong> 12 + </p> 13 + <form method="post" action="/auth/signout"> 14 + <button type="submit">Sign out</button> 15 + </form> 16 + </header> 17 + <section> 18 + <h2>Subscriptions</h2> 19 + <p>No subscriptions yet.</p> 20 + </section> 21 + </div>, 22 + { title: "Dashboard — Airglow" }, 23 + ); 24 + });
+5 -3
app/routes/index.tsx
··· 1 1 import { createRoute } from "honox/factory"; 2 - import Counter from "../islands/Counter.js"; 2 + import { getSessionUser } from "@/auth/middleware.js"; 3 3 4 - export default createRoute((c) => { 4 + export default createRoute(async (c) => { 5 + const user = await getSessionUser(c); 6 + 5 7 return c.render( 6 8 <div> 7 9 <h1>Airglow</h1> 8 10 <p>Webhooks for the AT Protocol</p> 9 - <Counter /> 11 + {user ? <a href="/dashboard">Go to dashboard</a> : <a href="/auth/login">Sign in</a>} 10 12 </div>, 11 13 { title: "Airglow" }, 12 14 );
+12
app/server.ts
··· 1 1 import { createApp } from "honox/server"; 2 + import { getOAuthClient } from "@/auth/client.js"; 2 3 3 4 const app = createApp(); 5 + 6 + // OAuth discovery endpoints (production — loopback clients don't need these) 7 + app.get("/oauth/client-metadata.json", async (c) => { 8 + const client = await getOAuthClient(); 9 + return c.json(client.clientMetadata); 10 + }); 11 + 12 + app.get("/oauth/jwks.json", async (c) => { 13 + const client = await getOAuthClient(); 14 + return c.json(client.jwks); 15 + }); 4 16 5 17 export default app;
+144 -3
bun.lock
··· 5 5 "": { 6 6 "name": "squall", 7 7 "dependencies": { 8 + "@atproto/oauth-client-node": "^0.3.17", 8 9 "@vanilla-extract/css": "^1.20.1", 9 10 "@vanilla-extract/vite-plugin": "^5.2.2", 10 11 "drizzle-orm": "^0.45.2", ··· 14 15 "vite": "npm:@voidzero-dev/vite-plus-core@latest", 15 16 }, 16 17 "devDependencies": { 18 + "@types/better-sqlite3": "^7.6.13", 17 19 "@types/bun": "^1.3.11", 20 + "better-sqlite3": "^12.8.0", 18 21 "drizzle-kit": "^0.31.10", 19 22 "vite-plus": "latest", 20 23 }, ··· 25 28 "vitest": "npm:@voidzero-dev/vite-plus-test@latest", 26 29 }, 27 30 "packages": { 31 + "@atproto-labs/did-resolver": ["@atproto-labs/did-resolver@0.2.6", "", { "dependencies": { "@atproto-labs/fetch": "0.2.3", "@atproto-labs/pipe": "0.1.1", "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.3.0", "zod": "^3.23.8" } }, "sha512-2K1bC04nI2fmgNcvof+yA28IhGlpWn2JKYlPa7To9JTKI45FINCGkQSGiL2nyXlyzDJJ34fZ1aq6/IRFIOIiqg=="], 32 + 33 + "@atproto-labs/fetch": ["@atproto-labs/fetch@0.2.3", "", { "dependencies": { "@atproto-labs/pipe": "0.1.1" } }, "sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw=="], 34 + 35 + "@atproto-labs/fetch-node": ["@atproto-labs/fetch-node@0.2.0", "", { "dependencies": { "@atproto-labs/fetch": "0.2.3", "@atproto-labs/pipe": "0.1.1", "ipaddr.js": "^2.1.0", "undici": "^6.14.1" } }, "sha512-Krq09nH/aeoiU2s9xdHA0FjTEFWG9B5FFenipv1iRixCcPc7V3DhTNDawxG9gI8Ny0k4dBVS9WTRN/IDzBx86Q=="], 36 + 37 + "@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.3.6", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.3.0", "zod": "^3.23.8" } }, "sha512-qnSTXvOBNj1EHhp2qTWSX8MS5q3AwYU5LKlt5fBvSbCjgmTr2j0URHCv+ydrwO55KvsojIkTMgeMOh4YuY4fCA=="], 38 + 39 + "@atproto-labs/handle-resolver-node": ["@atproto-labs/handle-resolver-node@0.1.25", "", { "dependencies": { "@atproto-labs/fetch-node": "0.2.0", "@atproto-labs/handle-resolver": "0.3.6", "@atproto/did": "0.3.0" } }, "sha512-NY9WYM2VLd3IuMGRkkmvGBg8xqVEaK/fitv1vD8SMXqFTekdpjOLCCyv7EFtqVHouzmDcL83VOvWRfHVa8V9Yw=="], 40 + 41 + "@atproto-labs/identity-resolver": ["@atproto-labs/identity-resolver@0.3.6", "", { "dependencies": { "@atproto-labs/did-resolver": "0.2.6", "@atproto-labs/handle-resolver": "0.3.6" } }, "sha512-qoWqBDRobln0NR8L8dQjSp79E0chGkBhibEgxQa2f9WD+JbJdjQ0YvwwO5yeQn05pJoJmAwmI2wyJ45zjU7aWg=="], 42 + 43 + "@atproto-labs/pipe": ["@atproto-labs/pipe@0.1.1", "", {}, "sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg=="], 44 + 45 + "@atproto-labs/simple-store": ["@atproto-labs/simple-store@0.3.0", "", {}, "sha512-nOb6ONKBRJHRlukW1sVawUkBqReLlLx6hT35VS3imaNPwiXDxLnTK7lxw3Lrl9k5yugSBDQAkZAq3MPTEFSUBQ=="], 46 + 47 + "@atproto-labs/simple-store-memory": ["@atproto-labs/simple-store-memory@0.1.4", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "lru-cache": "^10.2.0" } }, "sha512-3mKY4dP8I7yKPFj9VKpYyCRzGJOi5CEpOLPlRhoJyLmgs3J4RzDrjn323Oakjz2Aj2JzRU/AIvWRAZVhpYNJHw=="], 48 + 49 + "@atproto/common-web": ["@atproto/common-web@0.4.19", "", { "dependencies": { "@atproto/lex-data": "^0.0.14", "@atproto/lex-json": "^0.0.14", "@atproto/syntax": "^0.5.1", "zod": "^3.23.8" } }, "sha512-3BTi58p5WpT+9/zb6UZrdsXcfPo5P45UJm0E4iwHLILr+jc37CuBj9JReDSZ4U0i9RTrI3ZkfySyZ9bd+LnMsw=="], 50 + 51 + "@atproto/did": ["@atproto/did@0.3.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-raUPzUGegtW/6OxwCmM8bhZvuIMzxG5t9oWsth6Tp91Kb5fTnHV2h/KKNF1C82doeA4BdXCErTyg7ISwLbQkzA=="], 52 + 53 + "@atproto/jwk": ["@atproto/jwk@0.6.0", "", { "dependencies": { "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-bDoJPvt7TrQVi/rBfBrSSpGykhtIriKxeYCYQTiPRKFfyRhbgpElF0wPXADjIswnbzZdOwbY63az4E/CFVT3Tw=="], 54 + 55 + "@atproto/jwk-jose": ["@atproto/jwk-jose@0.1.11", "", { "dependencies": { "@atproto/jwk": "0.6.0", "jose": "^5.2.0" } }, "sha512-i4Fnr2sTBYmMmHXl7NJh8GrCH+tDQEVWrcDMDnV5DjJfkgT17wIqvojIw9SNbSL4Uf0OtfEv6AgG0A+mgh8b5Q=="], 56 + 57 + "@atproto/jwk-webcrypto": ["@atproto/jwk-webcrypto@0.2.0", "", { "dependencies": { "@atproto/jwk": "0.6.0", "@atproto/jwk-jose": "0.1.11", "zod": "^3.23.8" } }, "sha512-UmgRrrEAkWvxwhlwe30UmDOdTEFidlIzBC7C3cCbeJMcBN1x8B3KH+crXrsTqfWQBG58mXgt8wgSK3Kxs2LhFg=="], 58 + 59 + "@atproto/lex-data": ["@atproto/lex-data@0.0.14", "", { "dependencies": { "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-53DUa9664SS76nGAMYopWsO10OH0AAdf7P/HSKB6Wzx3iqe6lk/K61QZnKxOG1LreYl5CfvIJU6eNf4txI6GlQ=="], 60 + 61 + "@atproto/lex-json": ["@atproto/lex-json@0.0.14", "", { "dependencies": { "@atproto/lex-data": "^0.0.14", "tslib": "^2.8.1" } }, "sha512-6lPkDKqe7teEu4WrN5q7400cvZKgYS3uwUMvzG3F9XkgVYhOwSDCtouV/nSLBbpvo3l9OP0kiigtclcNcyekww=="], 62 + 63 + "@atproto/lexicon": ["@atproto/lexicon@0.6.2", "", { "dependencies": { "@atproto/common-web": "^0.4.18", "@atproto/syntax": "^0.5.0", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-p3Ly6hinVZW0ETuAXZMeUGwuMm3g8HvQMQ41yyEE6AL0hAkfeKFaZKos6BdBrr6CjkpbrDZqE8M+5+QOceysMw=="], 64 + 65 + "@atproto/oauth-client": ["@atproto/oauth-client@0.6.0", "", { "dependencies": { "@atproto-labs/did-resolver": "^0.2.6", "@atproto-labs/fetch": "^0.2.3", "@atproto-labs/handle-resolver": "^0.3.6", "@atproto-labs/identity-resolver": "^0.3.6", "@atproto-labs/simple-store": "^0.3.0", "@atproto-labs/simple-store-memory": "^0.1.4", "@atproto/did": "^0.3.0", "@atproto/jwk": "^0.6.0", "@atproto/oauth-types": "^0.6.3", "@atproto/xrpc": "^0.7.7", "core-js": "^3", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-F7ZTKzFptXgyihMkd7QTdRSkrh4XqrS+qTw+V81k5Q6Bh3MB1L3ypvfSJ6v7SSUJa6XxoZYJTCahHC1e+ndE6Q=="], 66 + 67 + "@atproto/oauth-client-node": ["@atproto/oauth-client-node@0.3.17", "", { "dependencies": { "@atproto-labs/did-resolver": "^0.2.6", "@atproto-labs/handle-resolver-node": "^0.1.25", "@atproto-labs/simple-store": "^0.3.0", "@atproto/did": "^0.3.0", "@atproto/jwk": "^0.6.0", "@atproto/jwk-jose": "^0.1.11", "@atproto/jwk-webcrypto": "^0.2.0", "@atproto/oauth-client": "^0.6.0", "@atproto/oauth-types": "^0.6.3" } }, "sha512-67LNuKAlC35Exe7CB5S0QCAnEqr6fKV9Nvp64jAHFof1N+Vc9Ltt1K9oekE5Ctf7dvpGByrHRF0noUw9l9sWLA=="], 68 + 69 + "@atproto/oauth-types": ["@atproto/oauth-types@0.6.3", "", { "dependencies": { "@atproto/did": "^0.3.0", "@atproto/jwk": "^0.6.0", "zod": "^3.23.8" } }, "sha512-jdKuoPknJuh/WjI+mYk7agSbx9mNVMbS6Dr3k1z2YMY2oRiCQjxYBuo4MLKATbxj05nMQaZRWlHRUazoAu5Cng=="], 70 + 71 + "@atproto/syntax": ["@atproto/syntax@0.5.3", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-gzhlHOJHm5KXdCc17fXi1fXM81ccs5jJfNgCui84ay9JGvczxegpYHNqdMlv+iBuhtBzFIjgx6ChjRxN/kO8kQ=="], 72 + 73 + "@atproto/xrpc": ["@atproto/xrpc@0.7.7", "", { "dependencies": { "@atproto/lexicon": "^0.6.0", "zod": "^3.23.8" } }, "sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA=="], 74 + 28 75 "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], 29 76 30 77 "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], ··· 237 284 238 285 "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], 239 286 287 + "@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="], 288 + 240 289 "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], 241 290 242 291 "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], ··· 305 354 306 355 "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 307 356 357 + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], 358 + 308 359 "baseline-browser-mapping": ["baseline-browser-mapping@2.10.13", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw=="], 309 360 361 + "better-sqlite3": ["better-sqlite3@12.8.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ=="], 362 + 363 + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], 364 + 365 + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], 366 + 310 367 "brace-expansion": ["brace-expansion@2.0.3", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA=="], 311 368 312 369 "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], 313 370 371 + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], 372 + 314 373 "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], 315 374 316 375 "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], ··· 318 377 "cac": ["cac@7.0.0", "", {}, "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ=="], 319 378 320 379 "caniuse-lite": ["caniuse-lite@1.0.30001784", "", {}, "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw=="], 380 + 381 + "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], 321 382 322 383 "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 323 384 ··· 327 388 328 389 "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], 329 390 391 + "core-js": ["core-js@3.49.0", "", {}, "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg=="], 392 + 330 393 "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 331 394 332 395 "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], ··· 335 398 336 399 "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 337 400 401 + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], 402 + 338 403 "dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="], 404 + 405 + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], 339 406 340 407 "deep-object-diff": ["deep-object-diff@1.1.9", "", {}, "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA=="], 341 408 ··· 367 434 368 435 "electron-to-chromium": ["electron-to-chromium@1.5.331", "", {}, "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q=="], 369 436 437 + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], 438 + 370 439 "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], 371 440 372 441 "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], ··· 389 458 390 459 "eval": ["eval@0.1.8", "", { "dependencies": { "@types/node": "*", "require-like": ">= 0.1.1" } }, "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw=="], 391 460 461 + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], 462 + 392 463 "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 393 464 465 + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], 466 + 394 467 "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], 395 468 469 + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], 470 + 396 471 "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 397 472 398 473 "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], ··· 400 475 "get-amd-module-type": ["get-amd-module-type@6.0.1", "", { "dependencies": { "ast-module-types": "^6.0.1", "node-source-walk": "^7.0.1" } }, "sha512-MtjsmYiCXcYDDrGqtNbeIYdAl85n+5mSv2r3FbzER/YV3ZILw4HNNIw34HuV5pyl0jzs6GFYU1VHVEefhgcNHQ=="], 401 476 402 477 "get-tsconfig": ["get-tsconfig@4.13.7", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q=="], 478 + 479 + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], 403 480 404 481 "globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], 405 482 ··· 409 486 410 487 "honox": ["honox@0.1.55", "", { "dependencies": { "@babel/generator": "7.25.6", "@babel/parser": "7.25.6", "@babel/traverse": "7.25.6", "@babel/types": "7.25.6", "@hono/vite-dev-server": "^0.25.1", "jsonc-parser": "3.3.1", "precinct": "12.2.0" }, "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "^4.9.6" }, "peerDependencies": { "hono": ">=4.*" } }, "sha512-Khf2X/8Z+1iTg1p7rkSEUhmFLpCNKhcSSRV5pk/xo+LsiB1gNMUH869MsfhQW0n4cNsLMnz0APrHLGCmdfQ1Hg=="], 411 488 489 + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], 490 + 491 + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], 492 + 493 + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], 494 + 495 + "ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="], 496 + 412 497 "is-url": ["is-url@1.2.4", "", {}, "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="], 413 498 414 499 "is-url-superb": ["is-url-superb@4.0.0", "", {}, "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA=="], 415 500 416 501 "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 417 502 503 + "iso-datestring-validator": ["iso-datestring-validator@2.2.2", "", {}, "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA=="], 504 + 418 505 "javascript-stringify": ["javascript-stringify@2.1.0", "", {}, "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg=="], 506 + 507 + "jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], 419 508 420 509 "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], 421 510 ··· 457 546 458 547 "media-query-parser": ["media-query-parser@2.0.2", "", { "dependencies": { "@babel/runtime": "^7.12.5" } }, "sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w=="], 459 548 549 + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], 550 + 460 551 "minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], 461 552 462 553 "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], 554 + 555 + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], 463 556 464 557 "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], 465 558 ··· 471 564 472 565 "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 473 566 567 + "multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 568 + 474 569 "nanoid": ["nanoid@5.1.7", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-ua3NDgISf6jdwezAheMOk4mbE1LXjm1DfMUDMuJf4AqxLFK3ccGpgWizwa5YV7Yz9EpXwEaWoRXSb/BnV0t5dQ=="], 475 570 571 + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], 572 + 573 + "node-abi": ["node-abi@3.89.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA=="], 574 + 476 575 "node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="], 477 576 478 577 "node-source-walk": ["node-source-walk@7.0.1", "", { "dependencies": { "@babel/parser": "^7.26.7" } }, "sha512-3VW/8JpPqPvnJvseXowjZcirPisssnBuDikk6JIZ8jQzF7KJQX52iPFX4RYYxLycYH7IbMRSPUOga/esVjy5Yg=="], 479 578 480 579 "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], 580 + 581 + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], 481 582 482 583 "oxfmt": ["oxfmt@0.43.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.43.0", "@oxfmt/binding-android-arm64": "0.43.0", "@oxfmt/binding-darwin-arm64": "0.43.0", "@oxfmt/binding-darwin-x64": "0.43.0", "@oxfmt/binding-freebsd-x64": "0.43.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.43.0", "@oxfmt/binding-linux-arm-musleabihf": "0.43.0", "@oxfmt/binding-linux-arm64-gnu": "0.43.0", "@oxfmt/binding-linux-arm64-musl": "0.43.0", "@oxfmt/binding-linux-ppc64-gnu": "0.43.0", "@oxfmt/binding-linux-riscv64-gnu": "0.43.0", "@oxfmt/binding-linux-riscv64-musl": "0.43.0", "@oxfmt/binding-linux-s390x-gnu": "0.43.0", "@oxfmt/binding-linux-x64-gnu": "0.43.0", "@oxfmt/binding-linux-x64-musl": "0.43.0", "@oxfmt/binding-openharmony-arm64": "0.43.0", "@oxfmt/binding-win32-arm64-msvc": "0.43.0", "@oxfmt/binding-win32-ia32-msvc": "0.43.0", "@oxfmt/binding-win32-x64-msvc": "0.43.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-KTYNG5ISfHSdmeZ25Xzb3qgz9EmQvkaGAxgBY/p38+ZiAet3uZeu7FnMwcSQJg152Qwl0wnYAxDc+Z/H6cvrwA=="], 483 584 ··· 509 610 510 611 "postcss-values-parser": ["postcss-values-parser@6.0.2", "", { "dependencies": { "color-name": "^1.1.4", "is-url-superb": "^4.0.0", "quote-unquote": "^1.0.0" }, "peerDependencies": { "postcss": "^8.2.9" } }, "sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw=="], 511 612 613 + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], 614 + 512 615 "precinct": ["precinct@12.2.0", "", { "dependencies": { "@dependents/detective-less": "^5.0.1", "commander": "^12.1.0", "detective-amd": "^6.0.1", "detective-cjs": "^6.0.1", "detective-es6": "^5.0.1", "detective-postcss": "^7.0.1", "detective-sass": "^6.0.1", "detective-scss": "^5.0.1", "detective-stylus": "^5.0.1", "detective-typescript": "^14.0.0", "detective-vue2": "^2.2.0", "module-definition": "^6.0.1", "node-source-walk": "^7.0.1", "postcss": "^8.5.1", "typescript": "^5.7.3" }, "bin": { "precinct": "bin/cli.js" } }, "sha512-NFBMuwIfaJ4SocE9YXPU/n4AcNSoFMVFjP72nvl3cx69j/ke61/hPOWFREVxLkFhhEGnA8ZuVfTqJBa+PK3b5w=="], 616 + 617 + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], 513 618 514 619 "quote-unquote": ["quote-unquote@1.0.0", "", {}, "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg=="], 515 620 621 + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], 622 + 623 + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], 624 + 516 625 "require-like": ["require-like@0.1.2", "", {}, "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A=="], 517 626 518 627 "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], 519 628 520 - "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 629 + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], 630 + 631 + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], 521 632 522 633 "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 523 634 524 635 "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 525 636 637 + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], 638 + 639 + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], 640 + 526 641 "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], 527 642 528 643 "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], ··· 533 648 534 649 "std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="], 535 650 651 + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], 652 + 653 + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], 654 + 655 + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], 656 + 657 + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], 658 + 536 659 "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], 537 660 538 661 "tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="], ··· 547 670 548 671 "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], 549 672 673 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 674 + 550 675 "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], 676 + 677 + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], 551 678 552 679 "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 553 680 554 681 "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], 555 682 683 + "uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="], 684 + 685 + "undici": ["undici@6.24.1", "", {}, "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA=="], 686 + 556 687 "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], 557 688 689 + "unicode-segmenter": ["unicode-segmenter@0.14.5", "", {}, "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g=="], 690 + 558 691 "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], 692 + 693 + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], 559 694 560 695 "vite": ["@voidzero-dev/vite-plus-core@0.1.15", "", { "dependencies": { "@oxc-project/runtime": "=0.122.0", "@oxc-project/types": "=0.122.0", "lightningcss": "^1.30.2", "postcss": "^8.5.6" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@tsdown/css": "0.21.7", "@tsdown/exe": "0.21.7", "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "publint": "^0.3.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "typescript": "^5.0.0 || ^6.0.0", "unplugin-unused": "^0.5.0", "yaml": "^2.4.2" }, "optionalPeers": ["@arethetypeswrong/core", "@tsdown/css", "@tsdown/exe", "@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "publint", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "typescript", "unplugin-unused", "yaml"] }, "sha512-0qAbqwcvQwiC8xGKSSuFtsjJUEM4LZzpXF7dffRazghGEQ8HH8NAvVryp/PiMSFwreJlV3rujwL4amKjnwCHpg=="], 561 696 ··· 565 700 566 701 "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 567 702 703 + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], 704 + 568 705 "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], 569 706 570 707 "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], 571 708 572 709 "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 710 + 711 + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 573 712 574 713 "@babel/core/@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], 575 714 ··· 578 717 "@babel/core/@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], 579 718 580 719 "@babel/core/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], 720 + 721 + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 581 722 582 723 "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], 583 724 725 + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 726 + 584 727 "@babel/helper-module-imports/@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], 585 728 586 729 "@babel/helper-module-imports/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], ··· 596 739 "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], 597 740 598 741 "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], 599 - 600 - "@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], 601 742 602 743 "@vanilla-extract/integration/esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], 603 744
+160
lib/auth/client.ts
··· 1 + import type { NodeOAuthClient as NodeOAuthClientType } from "@atproto/oauth-client-node"; 2 + import { createRequire } from "node:module"; 3 + import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; 4 + import { dirname, resolve } from "node:path"; 5 + 6 + // Load via require() — HonoX forces Vite to ESM-transform all node_modules 7 + // during SSR, which breaks CJS packages. require() bypasses Vite's transform. 8 + const require = createRequire(import.meta.url); 9 + const { JoseKey, NodeOAuthClient, requestLocalLock } = require( 10 + "@atproto/oauth-client-node", 11 + ) as typeof import("@atproto/oauth-client-node"); 12 + import { config } from "../config.js"; 13 + import { sessionStore, stateStore } from "./storage.js"; 14 + 15 + const KEY_PATH = resolve("./data/oauth-key.json"); 16 + 17 + const pdsUrl = config.pdsUrl; 18 + const isLocalDev = 19 + Boolean(pdsUrl) || 20 + config.publicUrl.startsWith("http://localhost") || 21 + config.publicUrl.startsWith("http://127.0.0.1"); 22 + 23 + // Cached PDS hostname for URL rewriting (populated on first getOAuthClient call) 24 + let pdsHostname: string | null = null; 25 + 26 + async function loadOrCreateKey(): Promise<InstanceType<typeof JoseKey>> { 27 + if (existsSync(KEY_PATH)) { 28 + const raw = readFileSync(KEY_PATH, "utf-8"); 29 + return JoseKey.fromImportable(raw, "airglow-signing-key"); 30 + } 31 + 32 + const key = await JoseKey.generate(["ES256"]); 33 + mkdirSync(dirname(KEY_PATH), { recursive: true }); 34 + writeFileSync(KEY_PATH, JSON.stringify(key.privateJwk)); 35 + return key; 36 + } 37 + 38 + /** 39 + * Resolve a handle to a DID. In local dev, queries the local PDS since DNS 40 + * won't resolve local handles (e.g. alice.pds.dev). 41 + */ 42 + export async function resolveHandle(handle: string): Promise<string> { 43 + if (handle.startsWith("did:")) return handle; 44 + if (pdsUrl) { 45 + try { 46 + const res = await fetch( 47 + `${pdsUrl}/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`, 48 + ); 49 + if (res.ok) { 50 + const data = (await res.json()) as { did: string }; 51 + return data.did; 52 + } 53 + } catch { 54 + // fall through 55 + } 56 + } 57 + return handle; 58 + } 59 + 60 + /** 61 + * Rewrite a URL targeting the PDS public hostname to the local PDS. 62 + * The local PDS registers DIDs with its public hostname (e.g. "pds.dev") 63 + * which isn't reachable — this maps those URLs to the actual PDS_URL. 64 + */ 65 + export function rewritePdsUrl(urlStr: string, target: "server" | "browser" = "server"): string { 66 + const baseUrl = target === "browser" ? config.publicUrl : pdsUrl; 67 + if (!pdsHostname || !baseUrl) return urlStr; 68 + try { 69 + const url = new URL(urlStr); 70 + if (url.hostname === pdsHostname || url.hostname.endsWith(`.${pdsHostname}`)) { 71 + const local = new URL(baseUrl); 72 + url.protocol = local.protocol; 73 + url.hostname = local.hostname; 74 + url.port = local.port; 75 + return url.href; 76 + } 77 + } catch { 78 + // invalid URL, return as-is 79 + } 80 + return urlStr; 81 + } 82 + 83 + async function fetchPdsHostname(): Promise<string | null> { 84 + if (!pdsUrl) return null; 85 + try { 86 + const res = await fetch(`${pdsUrl}/xrpc/com.atproto.server.describeServer`); 87 + if (!res.ok) return null; 88 + const data = (await res.json()) as { availableUserDomains?: string[] }; 89 + const domain = data.availableUserDomains?.[0]; 90 + return domain?.startsWith(".") ? domain.slice(1) : (domain ?? null); 91 + } catch { 92 + return null; 93 + } 94 + } 95 + 96 + let client: NodeOAuthClientType | null = null; 97 + 98 + export async function getOAuthClient(): Promise<NodeOAuthClientType> { 99 + if (client) return client; 100 + 101 + const key = await loadOrCreateKey(); 102 + 103 + // Build a custom fetch that rewrites PDS URLs for local dev 104 + let devFetch: typeof fetch | undefined; 105 + if (isLocalDev && pdsUrl) { 106 + pdsHostname = await fetchPdsHostname(); 107 + if (pdsHostname) { 108 + devFetch = ((input: URL | RequestInfo, init?: RequestInit) => { 109 + const req = new Request(input, init); 110 + return fetch(new Request(rewritePdsUrl(req.url), req)); 111 + }) as typeof fetch; 112 + } 113 + } 114 + 115 + if (isLocalDev) { 116 + const redirectUri = config.publicUrl.replace("localhost", "127.0.0.1") + "/auth/callback"; 117 + client = new NodeOAuthClient({ 118 + clientMetadata: { 119 + client_id: `http://localhost?redirect_uri=${encodeURIComponent(redirectUri)}&scope=${encodeURIComponent("atproto transition:generic")}`, 120 + client_name: "Airglow", 121 + client_uri: config.publicUrl, 122 + redirect_uris: [redirectUri], 123 + scope: "atproto transition:generic", 124 + response_types: ["code"], 125 + grant_types: ["authorization_code", "refresh_token"], 126 + application_type: "native", 127 + token_endpoint_auth_method: "none", 128 + dpop_bound_access_tokens: true, 129 + }, 130 + stateStore, 131 + sessionStore, 132 + requestLock: requestLocalLock, 133 + allowHttp: true, 134 + fetch: devFetch, 135 + }); 136 + } else { 137 + client = new NodeOAuthClient({ 138 + clientMetadata: { 139 + client_id: `${config.publicUrl}/oauth/client-metadata.json`, 140 + client_name: "Airglow", 141 + client_uri: config.publicUrl, 142 + redirect_uris: [`${config.publicUrl}/auth/callback`], 143 + scope: "atproto transition:generic", 144 + response_types: ["code"], 145 + grant_types: ["authorization_code", "refresh_token"], 146 + application_type: "web", 147 + token_endpoint_auth_method: "private_key_jwt", 148 + token_endpoint_auth_signing_alg: "ES256", 149 + dpop_bound_access_tokens: true, 150 + jwks_uri: `${config.publicUrl}/oauth/jwks.json`, 151 + }, 152 + keyset: [key], 153 + stateStore, 154 + sessionStore, 155 + requestLock: requestLocalLock, 156 + }); 157 + } 158 + 159 + return client; 160 + }
+35
lib/auth/middleware.ts
··· 1 + import type { Context, Next } from "hono"; 2 + import { getSignedCookie } from "hono/cookie"; 3 + import { eq } from "drizzle-orm"; 4 + import { config } from "../config.js"; 5 + import { db } from "../db/index.js"; 6 + import { users } from "../db/schema.js"; 7 + 8 + const COOKIE_NAME = "airglow_session"; 9 + 10 + export type SessionUser = { 11 + id: number; 12 + did: string; 13 + handle: string; 14 + }; 15 + 16 + export async function getSessionUser(c: Context): Promise<SessionUser | null> { 17 + const did = await getSignedCookie(c, config.cookieSecret, COOKIE_NAME); 18 + if (!did) return null; 19 + 20 + const user = await db.query.users.findFirst({ 21 + where: eq(users.did, did), 22 + }); 23 + if (!user) return null; 24 + 25 + return { id: user.id, did: user.did, handle: user.handle }; 26 + } 27 + 28 + /** Middleware that requires authentication. Redirects to /auth/login if not signed in. */ 29 + export async function requireAuth(c: Context, next: Next) { 30 + const user = await getSessionUser(c); 31 + if (!user) return c.redirect("/auth/login"); 32 + 33 + c.set("user", user); 34 + return next(); 35 + }
+53
lib/auth/storage.ts
··· 1 + import type { 2 + NodeSavedSession, 3 + NodeSavedSessionStore, 4 + NodeSavedState, 5 + NodeSavedStateStore, 6 + } from "@atproto/oauth-client-node"; 7 + import { eq } from "drizzle-orm"; 8 + import { db } from "../db/index.js"; 9 + import { oauthSessions, oauthStates } from "../db/schema.js"; 10 + 11 + export const stateStore: NodeSavedStateStore = { 12 + async set(key: string, value: NodeSavedState) { 13 + await db 14 + .insert(oauthStates) 15 + .values({ key, value: JSON.stringify(value) }) 16 + .onConflictDoUpdate({ 17 + target: oauthStates.key, 18 + set: { value: JSON.stringify(value) }, 19 + }); 20 + }, 21 + async get(key: string) { 22 + const row = await db.query.oauthStates.findFirst({ 23 + where: eq(oauthStates.key, key), 24 + }); 25 + if (!row) return undefined; 26 + return JSON.parse(row.value) as NodeSavedState; 27 + }, 28 + async del(key: string) { 29 + await db.delete(oauthStates).where(eq(oauthStates.key, key)); 30 + }, 31 + }; 32 + 33 + export const sessionStore: NodeSavedSessionStore = { 34 + async set(key: string, value: NodeSavedSession) { 35 + await db 36 + .insert(oauthSessions) 37 + .values({ key, value: JSON.stringify(value) }) 38 + .onConflictDoUpdate({ 39 + target: oauthSessions.key, 40 + set: { value: JSON.stringify(value) }, 41 + }); 42 + }, 43 + async get(key: string) { 44 + const row = await db.query.oauthSessions.findFirst({ 45 + where: eq(oauthSessions.key, key), 46 + }); 47 + if (!row) return undefined; 48 + return JSON.parse(row.value) as NodeSavedSession; 49 + }, 50 + async del(key: string) { 51 + await db.delete(oauthSessions).where(eq(oauthSessions.key, key)); 52 + }, 53 + };
+18 -12
lib/config.ts
··· 1 + import { existsSync, readFileSync } from "node:fs"; 2 + 3 + // Bun auto-loads .env, but Vite SSR runs on Node which doesn't. 4 + // Load missing vars so config works in both contexts. 5 + if (typeof globalThis.Bun === "undefined" && existsSync(".env")) { 6 + for (const line of readFileSync(".env", "utf-8").split("\n")) { 7 + const m = line.match(/^([^#=\s]+)=(.*)$/); 8 + if (m?.[1] && !(m[1] in process.env)) process.env[m[1]] = m[2]; 9 + } 10 + } 11 + 1 12 function env(key: string, fallback: string): string { 2 13 return process.env[key] ?? fallback; 3 14 } 4 15 5 16 export const config = { 6 - port: Number(env("PORT", "3000")), 17 + port: Number(env("PORT", "5175")), 7 18 databasePath: env("DATABASE_PATH", "./data/airglow.db"), 8 - publicUrl: env("PUBLIC_URL", "http://localhost:3000"), 9 - jetstreamUrl: env( 10 - "JETSTREAM_URL", 11 - "wss://jetstream2.us-east.bsky.network/subscribe", 12 - ), 13 - nsidAllowlist: env("NSID_ALLOWLIST", "") 14 - .split(",") 15 - .filter(Boolean), 16 - nsidBlocklist: env("NSID_BLOCKLIST", "") 17 - .split(",") 18 - .filter(Boolean), 19 + publicUrl: env("PUBLIC_URL", "http://127.0.0.1:5175"), 20 + pdsUrl: process.env.PDS_URL?.replace(/\/$/, "") || "", 21 + jetstreamUrl: env("JETSTREAM_URL", "wss://jetstream2.us-east.bsky.network/subscribe"), 22 + cookieSecret: process.env.COOKIE_SECRET || crypto.randomUUID(), 23 + nsidAllowlist: env("NSID_ALLOWLIST", "").split(",").filter(Boolean), 24 + nsidBlocklist: env("NSID_BLOCKLIST", "").split(",").filter(Boolean), 19 25 } as const;
+4 -10
lib/db/migrations/meta/0000_snapshot.json
··· 71 71 "name": "delivery_logs_subscription_uri_subscriptions_uri_fk", 72 72 "tableFrom": "delivery_logs", 73 73 "tableTo": "subscriptions", 74 - "columnsFrom": [ 75 - "subscription_uri" 76 - ], 77 - "columnsTo": [ 78 - "uri" 79 - ], 74 + "columnsFrom": ["subscription_uri"], 75 + "columnsTo": ["uri"], 80 76 "onDelete": "cascade", 81 77 "onUpdate": "no action" 82 78 } ··· 288 284 "indexes": { 289 285 "users_did_unique": { 290 286 "name": "users_did_unique", 291 - "columns": [ 292 - "did" 293 - ], 287 + "columns": ["did"], 294 288 "isUnique": true 295 289 } 296 290 }, ··· 310 304 "internal": { 311 305 "indexes": {} 312 306 } 313 - } 307 + }
+1 -1
lib/db/migrations/meta/_journal.json
··· 10 10 "breakpoints": true 11 11 } 12 12 ] 13 - } 13 + }
+10
lib/db/sqlite-compat.ts
··· 1 + // Compatibility shim: loads better-sqlite3 via require() so Vite's SSR 2 + // (which runs on Node) can handle CJS modules. HonoX forces noExternal: true, 3 + // so all node_modules are ESM-transformed — require() bypasses this. 4 + // Only used during dev — production runs directly under Bun. 5 + import { createRequire } from "node:module"; 6 + 7 + const require = createRequire(import.meta.url); 8 + const BetterSqlite3 = require("better-sqlite3"); 9 + export default BetterSqlite3; 10 + export const Database = BetterSqlite3;
+11 -8
package.json
··· 1 1 { 2 2 "name": "airglow", 3 - "type": "module", 4 3 "private": true, 4 + "type": "module", 5 5 "scripts": { 6 6 "dev": "vp dev", 7 7 "build": "vp build --mode client", ··· 9 9 "db:generate": "drizzle-kit generate", 10 10 "db:migrate": "bun run lib/db/migrate.ts" 11 11 }, 12 - "devDependencies": { 13 - "@types/bun": "^1.3.11", 14 - "drizzle-kit": "^0.31.10", 15 - "vite-plus": "latest" 16 - }, 17 12 "dependencies": { 13 + "@atproto/oauth-client-node": "^0.3.17", 18 14 "@vanilla-extract/css": "^1.20.1", 19 15 "@vanilla-extract/vite-plugin": "^5.2.2", 20 16 "drizzle-orm": "^0.45.2", ··· 23 19 "nanoid": "^5.1.7", 24 20 "vite": "npm:@voidzero-dev/vite-plus-core@latest" 25 21 }, 26 - "packageManager": "bun@1.3.11", 22 + "devDependencies": { 23 + "@types/better-sqlite3": "^7.6.13", 24 + "@types/bun": "^1.3.11", 25 + "better-sqlite3": "^12.8.0", 26 + "drizzle-kit": "^0.31.10", 27 + "vite-plus": "latest" 28 + }, 27 29 "overrides": { 28 30 "vite": "npm:@voidzero-dev/vite-plus-core@latest", 29 31 "vitest": "npm:@voidzero-dev/vite-plus-test@latest" 30 - } 32 + }, 33 + "packageManager": "bun@1.3.11" 31 34 }
+140 -3
vite.config.ts
··· 1 + import { execSync } from "node:child_process"; 2 + import { request as httpRequest } from "node:http"; 1 3 import honox from "honox/vite"; 2 4 import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin"; 3 - import { defineConfig } from "vite"; 5 + import { defineConfig, type Plugin } from "vite"; 6 + 7 + const PDS_TARGET = "http://localhost:3000"; 8 + const APP_ORIGIN = "http://127.0.0.1:5175"; 9 + 10 + function getPdsIssuer(): string | null { 11 + try { 12 + const raw = execSync(`curl -sf ${PDS_TARGET}/.well-known/oauth-authorization-server`, { 13 + timeout: 3000, 14 + }).toString(); 15 + const data = JSON.parse(raw) as { issuer?: string }; 16 + return data.issuer ?? null; 17 + } catch { 18 + return null; 19 + } 20 + } 21 + 22 + function pdsProxy(): Plugin { 23 + const issuer = getPdsIssuer(); 24 + if (!issuer) { 25 + console.log("[pds-proxy] PDS not available, skipping proxy"); 26 + return { name: "pds-proxy" }; 27 + } 28 + 29 + const issuerUrl = new URL(issuer); 30 + const pdsUrl = new URL(PDS_TARGET); 31 + const proxyPaths = ["/oauth/", "/.well-known/", "/xrpc/", "/@atproto/"]; 32 + console.log(`[pds-proxy] Will proxy PDS (${issuer}) through ${APP_ORIGIN}`); 33 + 34 + return { 35 + name: "pds-proxy", 36 + configureServer(server) { 37 + server.middlewares.use((req, res, next) => { 38 + if (!req.url || !proxyPaths.some((p) => req.url!.startsWith(p))) { 39 + return next(); 40 + } 41 + 42 + // Build headers — override sec-fetch-* so PDS sees same-origin navigation 43 + const headers: Record<string, string | string[]> = {}; 44 + for (const [key, value] of Object.entries(req.headers)) { 45 + if (value != null) headers[key] = value; 46 + } 47 + delete headers.origin; 48 + headers["sec-fetch-site"] = "same-origin"; 49 + headers.host = issuerUrl.host; 50 + delete headers["accept-encoding"]; 51 + 52 + if (typeof headers.referer === "string") { 53 + try { 54 + const u = new URL(headers.referer); 55 + u.protocol = issuerUrl.protocol; 56 + u.hostname = issuerUrl.hostname; 57 + u.port = issuerUrl.port; 58 + headers.referer = u.href; 59 + } catch {} 60 + } 61 + 62 + // Use http.request — Node's fetch() strips sec-fetch-* headers per Fetch spec 63 + const proxyReq = httpRequest( 64 + { 65 + hostname: pdsUrl.hostname, 66 + port: pdsUrl.port, 67 + path: req.url, 68 + method: req.method, 69 + headers, 70 + }, 71 + (proxyRes) => { 72 + const resHeaders = { ...proxyRes.headers }; 73 + 74 + // Rewrite Location header 75 + if (typeof resHeaders.location === "string") { 76 + resHeaders.location = resHeaders.location.replaceAll(issuer, APP_ORIGIN); 77 + } 78 + 79 + // Strip HTTPS-only headers 80 + delete resHeaders["strict-transport-security"]; 81 + if (typeof resHeaders["content-security-policy"] === "string") { 82 + resHeaders["content-security-policy"] = resHeaders["content-security-policy"].replace( 83 + /upgrade-insecure-requests\s*;?\s*/g, 84 + "", 85 + ); 86 + } 87 + 88 + // Strip Secure from cookies 89 + if (resHeaders["set-cookie"]) { 90 + const cookies = Array.isArray(resHeaders["set-cookie"]) 91 + ? resHeaders["set-cookie"] 92 + : [resHeaders["set-cookie"]]; 93 + resHeaders["set-cookie"] = cookies.map((c) => c.replace(/;\s*Secure/gi, "")); 94 + } 95 + 96 + const contentType = resHeaders["content-type"] ?? ""; 97 + const isText = 98 + contentType.includes("json") || 99 + contentType.includes("html") || 100 + contentType.includes("javascript"); 101 + 102 + if (isText) { 103 + // Buffer text responses and rewrite PDS URLs 104 + delete resHeaders["content-length"]; 105 + delete resHeaders["content-encoding"]; 106 + const chunks: Buffer[] = []; 107 + proxyRes.on("data", (chunk: Buffer) => chunks.push(chunk)); 108 + proxyRes.on("end", () => { 109 + const body = Buffer.concat(chunks).toString("utf-8"); 110 + res.writeHead(proxyRes.statusCode!, resHeaders); 111 + res.end(body.replaceAll(issuer, APP_ORIGIN)); 112 + }); 113 + } else { 114 + res.writeHead(proxyRes.statusCode!, resHeaders); 115 + proxyRes.pipe(res); 116 + } 117 + }, 118 + ); 119 + 120 + proxyReq.on("error", (err) => { 121 + console.error("[pds-proxy] error:", err); 122 + if (!res.headersSent) { 123 + res.writeHead(502); 124 + res.end("PDS proxy error"); 125 + } 126 + }); 127 + 128 + // Pipe request body for POST requests 129 + req.pipe(proxyReq); 130 + }); 131 + }, 132 + }; 133 + } 4 134 5 135 export default defineConfig({ 6 136 fmt: {}, 7 - lint: {"options":{"typeAware":true,"typeCheck":true}}, 8 - plugins: [honox(), vanillaExtractPlugin()], 137 + lint: { options: { typeAware: true, typeCheck: true } }, 138 + server: { 139 + port: 5175, 140 + host: true, 141 + }, 142 + plugins: [pdsProxy(), honox(), vanillaExtractPlugin()], 9 143 resolve: { 10 144 alias: { 11 145 "@": "/lib", 146 + "bun:sqlite": "/lib/db/sqlite-compat.ts", 147 + "better-sqlite3": "/lib/db/sqlite-compat.ts", 148 + "drizzle-orm/bun-sqlite": "drizzle-orm/better-sqlite3", 12 149 }, 13 150 }, 14 151 });