ANProto over ATProto -- using Bluesky PDSes to store ANProto messages and blobs

initial

Everett Bogue 56c63254

+6
.gitignore
··· 1 + node_modules 2 + db.sqlite 3 + .env 4 + *.log 5 + dist 6 + .DS_Store
+44
README.md
··· 1 + # ANProto over ATProto 2 + 3 + This project demonstrates ANProto messaging on top of the AT Protocol (Bluesky/ATProto) with OAuth auth. It is designed to be clear, concise, and "LLM-friendly" (easy for AI assistants to parse and understand context). 4 + 5 + ## Purpose 6 + 7 + To demonstrate a secure, production-ready (conceptually) OAuth flow using `@atproto/oauth-client-node`, including: 8 + - **Handle Resolution**: Converting `user.bsky.social` to a DID. 9 + - **Session Management**: Persisting sessions securely using a database. 10 + - **Scopes**: Requesting appropriate permissions (Standard vs. Transition). 11 + - **Token Management**: Handling access and refresh tokens automatically. 12 + 13 + ## Quick Start 14 + 15 + 1. **Install Dependencies**: 16 + ```bash 17 + npm install 18 + ``` 19 + 2. **Run the Server**: 20 + ```bash 21 + npm run dev 22 + ``` 23 + 3. **Open**: Visit `http://localhost:3000` 24 + 25 + ## Documentation 26 + 27 + - **[Architecture Overview](./docs/ARCHITECTURE.md)**: How the components (Client, DB, Storage, Express) fit together. 28 + - **[Understanding Scopes](./docs/SCOPES.md)**: Which permissions to ask for and why. 29 + - **[Handles vs. DIDs](./docs/HANDLES_AND_DIDS.md)**: How user identity works in ATProto. 30 + - **[Setup & Configuration](./docs/SETUP.md)**: Configuring the client metadata for localhost vs. production. 31 + 32 + ## Key Files 33 + 34 + - `src/index.ts`: The web server and route handlers. 35 + - `src/client.ts`: Configuration of the OAuth client. 36 + - `src/storage.ts`: Interface between the OAuth client and the database. 37 + - `src/db.ts`: SQLite database connection. 38 + 39 + ## "Do's and Don'ts" 40 + 41 + - **DO** use DIDs (`did:plc:...`) as the primary user key in your database, not handles. Handles are mutable. 42 + - **DO** persist the `state` and `session` data securely. 43 + - **DON'T** request `atproto` (full access) scope unless you absolutely need it. Prefer granular scopes if available (though currently `atproto` or `transition:generic` are common). 44 + - **DON'T** hardcode the PDS URL. Always resolve it from the user's DID/Handle.
+43
docs/ARCHITECTURE.md
··· 1 + # Architecture 2 + 3 + This reference app follows a standard server-side OAuth flow suited for a backend (Node.js/Express) application. 4 + 5 + ## Components 6 + 7 + 1. **Express Server (`src/index.ts`)** 8 + - Host web endpoints (`/`, `/login`, `/oauth/callback`). 9 + - Manages the browser session (cookie-based) using `iron-session`. 10 + - NOTE: The browser session is *separate* from the OAuth session. The browser session just remembers "Who is logged in here?" (by storing the DID). 11 + 12 + 2. **OAuth Client (`src/client.ts`)** 13 + - Instance of `NodeOAuthClient`. 14 + - Manages the complexity of the handshake, token exchanges, and key management (DPoP). 15 + - Uses `client-metadata` to define itself to the world (redirect URIs, etc.). 16 + 17 + 3. **Storage Adapters (`src/storage.ts`)** 18 + - **State Store**: Temporarily stores the random `state` parameter generated during the login request to prevent CSRF. 19 + - **Session Store**: Persists the actual Access and Refresh tokens (the "OAuth Session") mapped to the user's DID. 20 + 21 + 4. **Database (`src/db.ts`)** 22 + - A simple SQLite database to back the Storage Adapters. 23 + - In a real app, this would be Postgres, Redis, etc. 24 + 25 + ## The Flow 26 + 27 + 1. **Initiation**: 28 + - User enters Handle. 29 + - App calls `client.authorize(handle)`. 30 + - App redirects User to the PDS (e.g., bsky.social login page). 31 + 2. **Authentication**: 32 + - User logs in at the PDS. 33 + - User approves the app. 34 + 3. **Callback**: 35 + - PDS redirects User back to `/oauth/callback?code=...`. 36 + - App calls `client.callback(params)`. 37 + - `client` exchanges `code` for `tokens`. 38 + - `client` saves tokens to `SessionStore`. 39 + - App saves `session.did` to the browser cookie. 40 + 4. **Usage**: 41 + - On subsequent requests, App reads DID from browser cookie. 42 + - App loads OAuth tokens from `SessionStore` using the DID. 43 + - App creates an `Agent` to make API calls.
+38
docs/HANDLES_AND_DIDS.md
··· 1 + # Handles vs. DIDs 2 + 3 + In the AT Protocol, users have two identifiers: a **Handle** and a **DID** (Decentralized Identifier). 4 + 5 + ## Handle (`alice.bsky.social`) 6 + - **Human-readable**: Looks like a domain name. 7 + - **Mutable**: Users can change their handle at any time (e.g., `alice.bsky.social` -> `alice.com`). 8 + - **Usage**: Used for login input, display names, and mentions. 9 + - **NOT for Storage**: Never use the handle as the primary key in your database user table. 10 + 11 + ## DID (`did:plc:z72...`) 12 + - **Machine-readable**: A unique string starting with `did:`. 13 + - **Immutable**: This creates a permanent identity for the user, regardless of handle changes. 14 + - **Usage**: Database primary keys, internal logic, and resolving data from the PDS (Personal Data Server). 15 + 16 + ## The Resolution Flow 17 + 18 + 1. **User Input**: User types `alice.bsky.social`. 19 + 2. **Resolution**: The OAuth client (or a resolver) queries the network to find the DID associated with that handle. 20 + 3. **Authentication**: The OAuth flow proceeds using the DID. 21 + 4. **Storage**: Your app stores the DID. 22 + 5. **Display**: When showing the user, you resolve the DID back to their *current* handle (or cache it and update periodically). 23 + 24 + ## Code Example 25 + 26 + When a user logs in: 27 + 28 + ```typescript 29 + // src/index.ts logic 30 + const handle = req.body.handle; // "alice.bsky.social" 31 + 32 + // The client.authorize() method handles the resolution internally! 33 + const url = await client.authorize(handle, { ... }); 34 + 35 + // On callback, we get the session which contains the DID 36 + const { session } = await client.callback(params); 37 + const userDid = session.did; // "did:plc:123..." 38 + ```
+39
docs/SCOPES.md
··· 1 + # AT Protocol Scopes 2 + 3 + OAuth scopes define the permissions your application requests from the user. In the AT Protocol, scopes are critical for security and user trust. 4 + 5 + ## Common Scopes 6 + 7 + ### `atproto` 8 + - **Description**: Grants full access to the user's account (except for account deletion or migration in some contexts). 9 + - **Use Case**: Full-featured clients (e.g., a Twitter-like app) that need to read notifications, post content, update profiles, and manage follows. 10 + - **Risk**: High. If your token is leaked, the attacker has nearly full control. 11 + 12 + ### `transition:generic` 13 + - **Description**: A transitional scope often used while the ecosystem moves towards more granular scopes. It provides broad access similar to `atproto` but is intended to be phased out for specific capabilities. 14 + 15 + ### `transition:chat.bsky` 16 + - **Description**: specific to Bluesky chat capabilities. 17 + 18 + ## Granular Scopes (The Future) 19 + 20 + The protocol is moving towards fine-grained scopes like: 21 + - `com.atproto.repo.create` 22 + - `com.atproto.repo.delete` 23 + - `app.bsky.feed.post` 24 + 25 + *Note: As of late 2024/early 2025, `atproto` is still the most commonly used scope for general apps, but you should always check the latest ATProto specs.* 26 + 27 + ## Best Practices 28 + 29 + 1. **Least Privilege**: Only request what you need. If you only need to verify identity, you might only need a hypothetical "signin" scope (or just check the DID returned without requesting API access, although typically some scope is required to get the token). 30 + 2. **Transparency**: Explain to your users why you need specific permissions. 31 + 3. **Offline Access**: If you need to perform actions when the user is not actively using the app (background jobs), ensure you request `offline_access` (often implicit or managed via refresh tokens in this library). 32 + 33 + ## In This Demo 34 + 35 + We use: 36 + ```typescript 37 + scope: 'atproto' 38 + ``` 39 + This is because we demonstrate fetching the user's profile and potentially other account data. For a simple "Log in with Bluesky" (identity only), you might strictly restrict usage to reading the profile and nothing else, even if the token technically allows more.
+47
docs/SETUP.md
··· 1 + # Setup and Configuration 2 + 3 + ## Prerequisites 4 + - Node.js v18+ 5 + - NPM 6 + 7 + ## Local Development 8 + 9 + 1. **Clone & Install**: 10 + ```bash 11 + git clone <repo> 12 + cd atproto-oauth-demo 13 + npm install 14 + ``` 15 + 16 + 2. **Public URL**: 17 + OAuth requires a publicly reachable or explicitly defined callback URL. For `localhost`, strict matching is enforced. 18 + 19 + In `src/client.ts`, the `client_id` is constructed specifically for localhost development to avoid needing a public domain: 20 + ```typescript 21 + client_id: 'http://localhost?redirect_uri=http%3A%2F%2F127.0.0.1%3A3000%2Foauth%2Fcallback&scope=atproto' 22 + ``` 23 + *Note: This is a "Loopback Client" technique. In production, your Client ID will be your website's URL (e.g., `https://myapp.com/client-metadata.json`).* 24 + 25 + 3. **Run**: 26 + ```bash 27 + npm run dev 28 + ``` 29 + 30 + ## Production Deployment 31 + 32 + 1. **Domain**: You need a public domain (e.g., `https://myapp.com`). 33 + 2. **Metadata Endpoint**: You must serve the client metadata at a known URL (usually `https://myapp.com/.well-known/oauth-client-metadata` or similar, or just referenced by the ID). 34 + 3. **Update `src/client.ts`**: 35 + ```typescript 36 + clientMetadata: { 37 + client_name: 'My App', 38 + client_id: 'https://myapp.com/client-metadata.json', // The URL where this JSON is served 39 + client_uri: 'https://myapp.com', 40 + redirect_uris: ['https://myapp.com/oauth/callback'], 41 + // ... 42 + } 43 + ``` 44 + 4. **Serve Metadata**: Ensure your app actually serves this JSON at the `client_id` URL (if using URL-based IDs). The demo app serves it at `/oauth-client-metadata.json`. 45 + 46 + 5. **Environment Variables**: 47 + Move secrets (like cookie passwords) to `.env` files.
+21
docs/WORK_ORDER.md
··· 1 + # Work Order: ANProto Messages on Bluesky PDS 2 + 3 + ## Scope 4 + - Store ANProto-signed messages (and blobs) in a Bluesky PDS using the existing Node OAuth backend. APDS remains frontend-only. 5 + - Use custom collections `com.anproto.message.v1` (messages) and optional `com.anproto.blob` if we need standalone blob records. 6 + 7 + ## First Steps 8 + - Lexicons: `com.anproto.message.v1` has `anmsg` (full ANProto message), optional `anblob`, `anhash` (base64 ANProto hash), and `blobhash` (hash of the attached blob). PDS rkey uses a base64url form of `anhash` (replace `+`→`-`, `/`→`_`, strip `=`) to satisfy record-key rules. IDs must match the collection names exactly. `com.anproto.blob` is optional if blobs stay in `anblob`. 9 + - OAuth/scopes: Keep Node OAuth flow; request repo read/write + blob upload (`atproto` for now). Continue storing DID in cookies and tokens server-side. 10 + - Signing/validation: Frontend APDS signs messages; send backend `{anmsg, blob?, blobMime?}`. Backend recomputes `anhash` via ANProto, converts to base64url rkey, and rejects mismatches. 11 + - Persistence: Upload blobs via `com.atproto.repo.uploadBlob`; create `com.anproto.message.v1` records with rkey = base64url(anhash), storing `anmsg`, `anhash`, optional `anblob`, and optional `blobhash`. 12 + - Retrieval/indexing: Add backend endpoints to fetch by rkey/anhash/DID; optional local index (DB) for search/filter; fallback to repo reads by rkey. 13 + - Safety: Enforce size limits on content/blobs, rate-limit publish, reject tampered/duplicate/invalid signatures, log verification failures. 14 + 15 + ## Client UI (Frontend) 16 + - Auth status: “Connect Bluesky” button; show connected DID/handle; disable publish until connected. 17 + - Key management: Display ANProto pubkey; controls to generate/import/export (avoid leaking private key in production). 18 + - Composer: Text area for content, optional “previous” hash, blob uploader, live display of computed hash/signature. 19 + - Publish flow: “Sign & Save to Bluesky” runs APDS sign then POSTs to backend; show progress/errors. 20 + - Viewer: Fetch by hash, display content, author pubkey, timestamp, previous chain, blob previews; simple search/filter if backend index exists. 21 + - Notifications: Inline status/toasts for auth, signing, upload, and save failures.
+37
lexicons/com.anproto.message.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.anproto.message.v1", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "ANProto message stored on a PDS; rkey = base64url(anhash).", 8 + "key": "anhash-b64url", 9 + "record": { 10 + "type": "object", 11 + "required": ["anmsg", "anhash"], 12 + "properties": { 13 + "anmsg": { 14 + "type": "string", 15 + "description": "Full ANProto message/signature string.", 16 + "maxLength": 20000 17 + }, 18 + "anhash": { 19 + "type": "string", 20 + "description": "ANProto hash (base64). rkey uses base64url (+->-, /->_, trim =).", 21 + "maxLength": 256 22 + }, 23 + "anblob": { 24 + "type": "string", 25 + "description": "Optional attached blob payload, base64-encoded (inline).", 26 + "maxLength": 9000000 27 + }, 28 + "blobhash": { 29 + "type": "string", 30 + "description": "Hash of the attached blob (base64).", 31 + "maxLength": 256 32 + } 33 + } 34 + } 35 + } 36 + } 37 + }
+2343
package-lock.json
··· 1 + { 2 + "name": "atproto-oauth-test", 3 + "version": "1.0.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "atproto-oauth-test", 9 + "version": "1.0.0", 10 + "license": "ISC", 11 + "dependencies": { 12 + "@atproto/api": "^0.18.3", 13 + "@atproto/oauth-client-node": "^0.3.11", 14 + "better-sqlite3": "^12.5.0", 15 + "express": "^5.1.0", 16 + "iron-session": "^8.0.4" 17 + }, 18 + "devDependencies": { 19 + "@types/better-sqlite3": "^7.6.13", 20 + "@types/express": "^5.0.5", 21 + "@types/node": "^24.10.1", 22 + "tsx": "^4.20.6", 23 + "typescript": "^5.9.3" 24 + } 25 + }, 26 + "node_modules/@atproto-labs/did-resolver": { 27 + "version": "0.2.3", 28 + "resolved": "https://registry.npmjs.org/@atproto-labs/did-resolver/-/did-resolver-0.2.3.tgz", 29 + "integrity": "sha512-NAAVMthPD98eEYprpU7+MccDxFzwa3OhecweitL0/4ntjjcmqy1ytTl+ynOcTEC69H/ENjlpo36BB+b2obDcvA==", 30 + "license": "MIT", 31 + "dependencies": { 32 + "@atproto-labs/fetch": "0.2.3", 33 + "@atproto-labs/pipe": "0.1.1", 34 + "@atproto-labs/simple-store": "0.3.0", 35 + "@atproto-labs/simple-store-memory": "0.1.4", 36 + "@atproto/did": "0.2.2", 37 + "zod": "^3.23.8" 38 + } 39 + }, 40 + "node_modules/@atproto-labs/fetch": { 41 + "version": "0.2.3", 42 + "resolved": "https://registry.npmjs.org/@atproto-labs/fetch/-/fetch-0.2.3.tgz", 43 + "integrity": "sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw==", 44 + "license": "MIT", 45 + "dependencies": { 46 + "@atproto-labs/pipe": "0.1.1" 47 + } 48 + }, 49 + "node_modules/@atproto-labs/fetch-node": { 50 + "version": "0.2.0", 51 + "resolved": "https://registry.npmjs.org/@atproto-labs/fetch-node/-/fetch-node-0.2.0.tgz", 52 + "integrity": "sha512-Krq09nH/aeoiU2s9xdHA0FjTEFWG9B5FFenipv1iRixCcPc7V3DhTNDawxG9gI8Ny0k4dBVS9WTRN/IDzBx86Q==", 53 + "license": "MIT", 54 + "dependencies": { 55 + "@atproto-labs/fetch": "0.2.3", 56 + "@atproto-labs/pipe": "0.1.1", 57 + "ipaddr.js": "^2.1.0", 58 + "undici": "^6.14.1" 59 + }, 60 + "engines": { 61 + "node": ">=18.7.0" 62 + } 63 + }, 64 + "node_modules/@atproto-labs/handle-resolver": { 65 + "version": "0.3.3", 66 + "resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver/-/handle-resolver-0.3.3.tgz", 67 + "integrity": "sha512-tBPRiDNWigk39TEQgupnez2st/midvtSuwg+UMEhnEyYItH7imXgZWcHuDkO4HmT9Lxz7br5zLUNaqIfFuTf5w==", 68 + "license": "MIT", 69 + "dependencies": { 70 + "@atproto-labs/simple-store": "0.3.0", 71 + "@atproto-labs/simple-store-memory": "0.1.4", 72 + "@atproto/did": "0.2.2", 73 + "zod": "^3.23.8" 74 + } 75 + }, 76 + "node_modules/@atproto-labs/handle-resolver-node": { 77 + "version": "0.1.22", 78 + "resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver-node/-/handle-resolver-node-0.1.22.tgz", 79 + "integrity": "sha512-Qu2Jc8CqwY8PeToWh1DBeExC6acHHamFXVsrhxB1+7jDRpRDNXhXfQLkblIkdL4y+F7c3KnE0u9VAxLWN9tFBw==", 80 + "license": "MIT", 81 + "dependencies": { 82 + "@atproto-labs/fetch-node": "0.2.0", 83 + "@atproto-labs/handle-resolver": "0.3.3", 84 + "@atproto/did": "0.2.2" 85 + }, 86 + "engines": { 87 + "node": ">=18.7.0" 88 + } 89 + }, 90 + "node_modules/@atproto-labs/identity-resolver": { 91 + "version": "0.3.3", 92 + "resolved": "https://registry.npmjs.org/@atproto-labs/identity-resolver/-/identity-resolver-0.3.3.tgz", 93 + "integrity": "sha512-UfijnA+1JB97vKXRv1zqOacgP6BRGVr6zS7sZCt+i84TxhydPfFxbku1iH+BUUEv86VMys/C/anunFu3Hmg6wg==", 94 + "license": "MIT", 95 + "dependencies": { 96 + "@atproto-labs/did-resolver": "0.2.3", 97 + "@atproto-labs/handle-resolver": "0.3.3" 98 + } 99 + }, 100 + "node_modules/@atproto-labs/pipe": { 101 + "version": "0.1.1", 102 + "resolved": "https://registry.npmjs.org/@atproto-labs/pipe/-/pipe-0.1.1.tgz", 103 + "integrity": "sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg==", 104 + "license": "MIT" 105 + }, 106 + "node_modules/@atproto-labs/simple-store": { 107 + "version": "0.3.0", 108 + "resolved": "https://registry.npmjs.org/@atproto-labs/simple-store/-/simple-store-0.3.0.tgz", 109 + "integrity": "sha512-nOb6ONKBRJHRlukW1sVawUkBqReLlLx6hT35VS3imaNPwiXDxLnTK7lxw3Lrl9k5yugSBDQAkZAq3MPTEFSUBQ==", 110 + "license": "MIT" 111 + }, 112 + "node_modules/@atproto-labs/simple-store-memory": { 113 + "version": "0.1.4", 114 + "resolved": "https://registry.npmjs.org/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.4.tgz", 115 + "integrity": "sha512-3mKY4dP8I7yKPFj9VKpYyCRzGJOi5CEpOLPlRhoJyLmgs3J4RzDrjn323Oakjz2Aj2JzRU/AIvWRAZVhpYNJHw==", 116 + "license": "MIT", 117 + "dependencies": { 118 + "@atproto-labs/simple-store": "0.3.0", 119 + "lru-cache": "^10.2.0" 120 + } 121 + }, 122 + "node_modules/@atproto/api": { 123 + "version": "0.18.3", 124 + "resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.18.3.tgz", 125 + "integrity": "sha512-CBqyZfkcKYsr348KP4CKb9plMlZ5A96HwA/DnYscPBl6fvMZkAezAjniZX+xUILASHQJg5c+NaNw9xP8ZuyyDQ==", 126 + "license": "MIT", 127 + "dependencies": { 128 + "@atproto/common-web": "^0.4.5", 129 + "@atproto/lexicon": "^0.5.2", 130 + "@atproto/syntax": "^0.4.1", 131 + "@atproto/xrpc": "^0.7.6", 132 + "await-lock": "^2.2.2", 133 + "multiformats": "^9.9.0", 134 + "tlds": "^1.234.0", 135 + "zod": "^3.23.8" 136 + } 137 + }, 138 + "node_modules/@atproto/common-web": { 139 + "version": "0.4.5", 140 + "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.5.tgz", 141 + "integrity": "sha512-Tx0xUafLm3vRvOQpbBl5eb9V8xlC7TaRXs6dAulHRkDG3Kb+P9qn3pkDteq+aeMshbVXbVa1rm3Ok4vFyuoyYA==", 142 + "license": "MIT", 143 + "dependencies": { 144 + "@atproto/lex-data": "0.0.1", 145 + "@atproto/lex-json": "0.0.1", 146 + "zod": "^3.23.8" 147 + } 148 + }, 149 + "node_modules/@atproto/did": { 150 + "version": "0.2.2", 151 + "resolved": "https://registry.npmjs.org/@atproto/did/-/did-0.2.2.tgz", 152 + "integrity": "sha512-IfOcEIpGp3owcaWA/e8VSIjdi/ocz5JbT3Ghg9jgFgnxLlwE8ndrihiR4xbRdcdPLza9YQjrPQhfRYdCu7s4Yw==", 153 + "license": "MIT", 154 + "dependencies": { 155 + "zod": "^3.23.8" 156 + } 157 + }, 158 + "node_modules/@atproto/jwk": { 159 + "version": "0.6.0", 160 + "resolved": "https://registry.npmjs.org/@atproto/jwk/-/jwk-0.6.0.tgz", 161 + "integrity": "sha512-bDoJPvt7TrQVi/rBfBrSSpGykhtIriKxeYCYQTiPRKFfyRhbgpElF0wPXADjIswnbzZdOwbY63az4E/CFVT3Tw==", 162 + "license": "MIT", 163 + "dependencies": { 164 + "multiformats": "^9.9.0", 165 + "zod": "^3.23.8" 166 + } 167 + }, 168 + "node_modules/@atproto/jwk-jose": { 169 + "version": "0.1.11", 170 + "resolved": "https://registry.npmjs.org/@atproto/jwk-jose/-/jwk-jose-0.1.11.tgz", 171 + "integrity": "sha512-i4Fnr2sTBYmMmHXl7NJh8GrCH+tDQEVWrcDMDnV5DjJfkgT17wIqvojIw9SNbSL4Uf0OtfEv6AgG0A+mgh8b5Q==", 172 + "license": "MIT", 173 + "dependencies": { 174 + "@atproto/jwk": "0.6.0", 175 + "jose": "^5.2.0" 176 + } 177 + }, 178 + "node_modules/@atproto/jwk-webcrypto": { 179 + "version": "0.2.0", 180 + "resolved": "https://registry.npmjs.org/@atproto/jwk-webcrypto/-/jwk-webcrypto-0.2.0.tgz", 181 + "integrity": "sha512-UmgRrrEAkWvxwhlwe30UmDOdTEFidlIzBC7C3cCbeJMcBN1x8B3KH+crXrsTqfWQBG58mXgt8wgSK3Kxs2LhFg==", 182 + "license": "MIT", 183 + "dependencies": { 184 + "@atproto/jwk": "0.6.0", 185 + "@atproto/jwk-jose": "0.1.11", 186 + "zod": "^3.23.8" 187 + } 188 + }, 189 + "node_modules/@atproto/lex-data": { 190 + "version": "0.0.1", 191 + "resolved": "https://registry.npmjs.org/@atproto/lex-data/-/lex-data-0.0.1.tgz", 192 + "integrity": "sha512-DrS/8cQcQs3s5t9ELAFNtyDZ8/PdiCx47ALtFEP2GnX2uCBHZRkqWG7xmu6ehjc787nsFzZBvlnz3T/gov5fGA==", 193 + "license": "MIT", 194 + "dependencies": { 195 + "@atproto/syntax": "0.4.1", 196 + "multiformats": "^9.9.0", 197 + "tslib": "^2.8.1", 198 + "uint8arrays": "3.0.0", 199 + "unicode-segmenter": "^0.14.0" 200 + } 201 + }, 202 + "node_modules/@atproto/lex-json": { 203 + "version": "0.0.1", 204 + "resolved": "https://registry.npmjs.org/@atproto/lex-json/-/lex-json-0.0.1.tgz", 205 + "integrity": "sha512-ivcF7+pDRuD/P97IEKQ/9TruunXj0w58Khvwk3M6psaI5eZT6LRsRZ4cWcKaXiFX4SHnjy+x43g0f7pPtIsERg==", 206 + "license": "MIT", 207 + "dependencies": { 208 + "@atproto/lex-data": "0.0.1", 209 + "tslib": "^2.8.1" 210 + } 211 + }, 212 + "node_modules/@atproto/lexicon": { 213 + "version": "0.5.2", 214 + "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.5.2.tgz", 215 + "integrity": "sha512-lRmJgMA8f5j7VB5Iu5cp188ald5FuI4FlmZ7nn6EBrk1dgOstWVrI5Ft6K3z2vjyLZRG6nzknlsw+tDP63p7bQ==", 216 + "license": "MIT", 217 + "dependencies": { 218 + "@atproto/common-web": "^0.4.4", 219 + "@atproto/syntax": "^0.4.1", 220 + "iso-datestring-validator": "^2.2.2", 221 + "multiformats": "^9.9.0", 222 + "zod": "^3.23.8" 223 + } 224 + }, 225 + "node_modules/@atproto/oauth-client": { 226 + "version": "0.5.9", 227 + "resolved": "https://registry.npmjs.org/@atproto/oauth-client/-/oauth-client-0.5.9.tgz", 228 + "integrity": "sha512-23Z77A9bQFJYWAn5qJRwfzYeLEzcAx77G6HK8mlbIOYuQbmv6YMmQBm4BntAntJmEukOG69IrdnGIHKvsC4wdg==", 229 + "license": "MIT", 230 + "dependencies": { 231 + "@atproto-labs/did-resolver": "0.2.3", 232 + "@atproto-labs/fetch": "0.2.3", 233 + "@atproto-labs/handle-resolver": "0.3.3", 234 + "@atproto-labs/identity-resolver": "0.3.3", 235 + "@atproto-labs/simple-store": "0.3.0", 236 + "@atproto-labs/simple-store-memory": "0.1.4", 237 + "@atproto/did": "0.2.2", 238 + "@atproto/jwk": "0.6.0", 239 + "@atproto/oauth-types": "0.5.1", 240 + "@atproto/xrpc": "0.7.6", 241 + "core-js": "^3", 242 + "multiformats": "^9.9.0", 243 + "zod": "^3.23.8" 244 + } 245 + }, 246 + "node_modules/@atproto/oauth-client-node": { 247 + "version": "0.3.11", 248 + "resolved": "https://registry.npmjs.org/@atproto/oauth-client-node/-/oauth-client-node-0.3.11.tgz", 249 + "integrity": "sha512-qcA3rr4gO9+THrwDWxAp0N249fiDhHpU1paOjq7eQUvpP0yAx0zaMjBnXCIY+ghU0ahX9dO+fS9/+TGNqZR6DA==", 250 + "license": "MIT", 251 + "dependencies": { 252 + "@atproto-labs/did-resolver": "0.2.3", 253 + "@atproto-labs/handle-resolver-node": "0.1.22", 254 + "@atproto-labs/simple-store": "0.3.0", 255 + "@atproto/did": "0.2.2", 256 + "@atproto/jwk": "0.6.0", 257 + "@atproto/jwk-jose": "0.1.11", 258 + "@atproto/jwk-webcrypto": "0.2.0", 259 + "@atproto/oauth-client": "0.5.9", 260 + "@atproto/oauth-types": "0.5.1" 261 + }, 262 + "engines": { 263 + "node": ">=18.7.0" 264 + } 265 + }, 266 + "node_modules/@atproto/oauth-types": { 267 + "version": "0.5.1", 268 + "resolved": "https://registry.npmjs.org/@atproto/oauth-types/-/oauth-types-0.5.1.tgz", 269 + "integrity": "sha512-x651IN8Ul0LOB7fZXXggcBbc66/7vsTvh0Zqg6vplSbVjozJR6lzKo8i42QKrstXO8+2kLy49qoIHXbTHHOI1Q==", 270 + "license": "MIT", 271 + "dependencies": { 272 + "@atproto/did": "0.2.2", 273 + "@atproto/jwk": "0.6.0", 274 + "zod": "^3.23.8" 275 + } 276 + }, 277 + "node_modules/@atproto/syntax": { 278 + "version": "0.4.1", 279 + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.4.1.tgz", 280 + "integrity": "sha512-CJdImtLAiFO+0z3BWTtxwk6aY5w4t8orHTMVJgkf++QRJWTxPbIFko/0hrkADB7n2EruDxDSeAgfUGehpH6ngw==", 281 + "license": "MIT" 282 + }, 283 + "node_modules/@atproto/xrpc": { 284 + "version": "0.7.6", 285 + "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.7.6.tgz", 286 + "integrity": "sha512-RvCf4j0JnKYWuz3QzsYCntJi3VuiAAybQsMIUw2wLWcHhchO9F7UaBZINLL2z0qc/cYWPv5NSwcVydMseoCZLA==", 287 + "license": "MIT", 288 + "dependencies": { 289 + "@atproto/lexicon": "^0.5.2", 290 + "zod": "^3.23.8" 291 + } 292 + }, 293 + "node_modules/@esbuild/aix-ppc64": { 294 + "version": "0.25.12", 295 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", 296 + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", 297 + "cpu": [ 298 + "ppc64" 299 + ], 300 + "dev": true, 301 + "license": "MIT", 302 + "optional": true, 303 + "os": [ 304 + "aix" 305 + ], 306 + "engines": { 307 + "node": ">=18" 308 + } 309 + }, 310 + "node_modules/@esbuild/android-arm": { 311 + "version": "0.25.12", 312 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", 313 + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", 314 + "cpu": [ 315 + "arm" 316 + ], 317 + "dev": true, 318 + "license": "MIT", 319 + "optional": true, 320 + "os": [ 321 + "android" 322 + ], 323 + "engines": { 324 + "node": ">=18" 325 + } 326 + }, 327 + "node_modules/@esbuild/android-arm64": { 328 + "version": "0.25.12", 329 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", 330 + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", 331 + "cpu": [ 332 + "arm64" 333 + ], 334 + "dev": true, 335 + "license": "MIT", 336 + "optional": true, 337 + "os": [ 338 + "android" 339 + ], 340 + "engines": { 341 + "node": ">=18" 342 + } 343 + }, 344 + "node_modules/@esbuild/android-x64": { 345 + "version": "0.25.12", 346 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", 347 + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", 348 + "cpu": [ 349 + "x64" 350 + ], 351 + "dev": true, 352 + "license": "MIT", 353 + "optional": true, 354 + "os": [ 355 + "android" 356 + ], 357 + "engines": { 358 + "node": ">=18" 359 + } 360 + }, 361 + "node_modules/@esbuild/darwin-arm64": { 362 + "version": "0.25.12", 363 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", 364 + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", 365 + "cpu": [ 366 + "arm64" 367 + ], 368 + "dev": true, 369 + "license": "MIT", 370 + "optional": true, 371 + "os": [ 372 + "darwin" 373 + ], 374 + "engines": { 375 + "node": ">=18" 376 + } 377 + }, 378 + "node_modules/@esbuild/darwin-x64": { 379 + "version": "0.25.12", 380 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", 381 + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", 382 + "cpu": [ 383 + "x64" 384 + ], 385 + "dev": true, 386 + "license": "MIT", 387 + "optional": true, 388 + "os": [ 389 + "darwin" 390 + ], 391 + "engines": { 392 + "node": ">=18" 393 + } 394 + }, 395 + "node_modules/@esbuild/freebsd-arm64": { 396 + "version": "0.25.12", 397 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", 398 + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", 399 + "cpu": [ 400 + "arm64" 401 + ], 402 + "dev": true, 403 + "license": "MIT", 404 + "optional": true, 405 + "os": [ 406 + "freebsd" 407 + ], 408 + "engines": { 409 + "node": ">=18" 410 + } 411 + }, 412 + "node_modules/@esbuild/freebsd-x64": { 413 + "version": "0.25.12", 414 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", 415 + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", 416 + "cpu": [ 417 + "x64" 418 + ], 419 + "dev": true, 420 + "license": "MIT", 421 + "optional": true, 422 + "os": [ 423 + "freebsd" 424 + ], 425 + "engines": { 426 + "node": ">=18" 427 + } 428 + }, 429 + "node_modules/@esbuild/linux-arm": { 430 + "version": "0.25.12", 431 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", 432 + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", 433 + "cpu": [ 434 + "arm" 435 + ], 436 + "dev": true, 437 + "license": "MIT", 438 + "optional": true, 439 + "os": [ 440 + "linux" 441 + ], 442 + "engines": { 443 + "node": ">=18" 444 + } 445 + }, 446 + "node_modules/@esbuild/linux-arm64": { 447 + "version": "0.25.12", 448 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", 449 + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", 450 + "cpu": [ 451 + "arm64" 452 + ], 453 + "dev": true, 454 + "license": "MIT", 455 + "optional": true, 456 + "os": [ 457 + "linux" 458 + ], 459 + "engines": { 460 + "node": ">=18" 461 + } 462 + }, 463 + "node_modules/@esbuild/linux-ia32": { 464 + "version": "0.25.12", 465 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", 466 + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", 467 + "cpu": [ 468 + "ia32" 469 + ], 470 + "dev": true, 471 + "license": "MIT", 472 + "optional": true, 473 + "os": [ 474 + "linux" 475 + ], 476 + "engines": { 477 + "node": ">=18" 478 + } 479 + }, 480 + "node_modules/@esbuild/linux-loong64": { 481 + "version": "0.25.12", 482 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", 483 + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", 484 + "cpu": [ 485 + "loong64" 486 + ], 487 + "dev": true, 488 + "license": "MIT", 489 + "optional": true, 490 + "os": [ 491 + "linux" 492 + ], 493 + "engines": { 494 + "node": ">=18" 495 + } 496 + }, 497 + "node_modules/@esbuild/linux-mips64el": { 498 + "version": "0.25.12", 499 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", 500 + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", 501 + "cpu": [ 502 + "mips64el" 503 + ], 504 + "dev": true, 505 + "license": "MIT", 506 + "optional": true, 507 + "os": [ 508 + "linux" 509 + ], 510 + "engines": { 511 + "node": ">=18" 512 + } 513 + }, 514 + "node_modules/@esbuild/linux-ppc64": { 515 + "version": "0.25.12", 516 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", 517 + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", 518 + "cpu": [ 519 + "ppc64" 520 + ], 521 + "dev": true, 522 + "license": "MIT", 523 + "optional": true, 524 + "os": [ 525 + "linux" 526 + ], 527 + "engines": { 528 + "node": ">=18" 529 + } 530 + }, 531 + "node_modules/@esbuild/linux-riscv64": { 532 + "version": "0.25.12", 533 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", 534 + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", 535 + "cpu": [ 536 + "riscv64" 537 + ], 538 + "dev": true, 539 + "license": "MIT", 540 + "optional": true, 541 + "os": [ 542 + "linux" 543 + ], 544 + "engines": { 545 + "node": ">=18" 546 + } 547 + }, 548 + "node_modules/@esbuild/linux-s390x": { 549 + "version": "0.25.12", 550 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", 551 + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", 552 + "cpu": [ 553 + "s390x" 554 + ], 555 + "dev": true, 556 + "license": "MIT", 557 + "optional": true, 558 + "os": [ 559 + "linux" 560 + ], 561 + "engines": { 562 + "node": ">=18" 563 + } 564 + }, 565 + "node_modules/@esbuild/linux-x64": { 566 + "version": "0.25.12", 567 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", 568 + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", 569 + "cpu": [ 570 + "x64" 571 + ], 572 + "dev": true, 573 + "license": "MIT", 574 + "optional": true, 575 + "os": [ 576 + "linux" 577 + ], 578 + "engines": { 579 + "node": ">=18" 580 + } 581 + }, 582 + "node_modules/@esbuild/netbsd-arm64": { 583 + "version": "0.25.12", 584 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", 585 + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", 586 + "cpu": [ 587 + "arm64" 588 + ], 589 + "dev": true, 590 + "license": "MIT", 591 + "optional": true, 592 + "os": [ 593 + "netbsd" 594 + ], 595 + "engines": { 596 + "node": ">=18" 597 + } 598 + }, 599 + "node_modules/@esbuild/netbsd-x64": { 600 + "version": "0.25.12", 601 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", 602 + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", 603 + "cpu": [ 604 + "x64" 605 + ], 606 + "dev": true, 607 + "license": "MIT", 608 + "optional": true, 609 + "os": [ 610 + "netbsd" 611 + ], 612 + "engines": { 613 + "node": ">=18" 614 + } 615 + }, 616 + "node_modules/@esbuild/openbsd-arm64": { 617 + "version": "0.25.12", 618 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", 619 + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", 620 + "cpu": [ 621 + "arm64" 622 + ], 623 + "dev": true, 624 + "license": "MIT", 625 + "optional": true, 626 + "os": [ 627 + "openbsd" 628 + ], 629 + "engines": { 630 + "node": ">=18" 631 + } 632 + }, 633 + "node_modules/@esbuild/openbsd-x64": { 634 + "version": "0.25.12", 635 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", 636 + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", 637 + "cpu": [ 638 + "x64" 639 + ], 640 + "dev": true, 641 + "license": "MIT", 642 + "optional": true, 643 + "os": [ 644 + "openbsd" 645 + ], 646 + "engines": { 647 + "node": ">=18" 648 + } 649 + }, 650 + "node_modules/@esbuild/openharmony-arm64": { 651 + "version": "0.25.12", 652 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", 653 + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", 654 + "cpu": [ 655 + "arm64" 656 + ], 657 + "dev": true, 658 + "license": "MIT", 659 + "optional": true, 660 + "os": [ 661 + "openharmony" 662 + ], 663 + "engines": { 664 + "node": ">=18" 665 + } 666 + }, 667 + "node_modules/@esbuild/sunos-x64": { 668 + "version": "0.25.12", 669 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", 670 + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", 671 + "cpu": [ 672 + "x64" 673 + ], 674 + "dev": true, 675 + "license": "MIT", 676 + "optional": true, 677 + "os": [ 678 + "sunos" 679 + ], 680 + "engines": { 681 + "node": ">=18" 682 + } 683 + }, 684 + "node_modules/@esbuild/win32-arm64": { 685 + "version": "0.25.12", 686 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", 687 + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", 688 + "cpu": [ 689 + "arm64" 690 + ], 691 + "dev": true, 692 + "license": "MIT", 693 + "optional": true, 694 + "os": [ 695 + "win32" 696 + ], 697 + "engines": { 698 + "node": ">=18" 699 + } 700 + }, 701 + "node_modules/@esbuild/win32-ia32": { 702 + "version": "0.25.12", 703 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", 704 + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", 705 + "cpu": [ 706 + "ia32" 707 + ], 708 + "dev": true, 709 + "license": "MIT", 710 + "optional": true, 711 + "os": [ 712 + "win32" 713 + ], 714 + "engines": { 715 + "node": ">=18" 716 + } 717 + }, 718 + "node_modules/@esbuild/win32-x64": { 719 + "version": "0.25.12", 720 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", 721 + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", 722 + "cpu": [ 723 + "x64" 724 + ], 725 + "dev": true, 726 + "license": "MIT", 727 + "optional": true, 728 + "os": [ 729 + "win32" 730 + ], 731 + "engines": { 732 + "node": ">=18" 733 + } 734 + }, 735 + "node_modules/@types/better-sqlite3": { 736 + "version": "7.6.13", 737 + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", 738 + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", 739 + "dev": true, 740 + "license": "MIT", 741 + "dependencies": { 742 + "@types/node": "*" 743 + } 744 + }, 745 + "node_modules/@types/body-parser": { 746 + "version": "1.19.6", 747 + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", 748 + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", 749 + "dev": true, 750 + "license": "MIT", 751 + "dependencies": { 752 + "@types/connect": "*", 753 + "@types/node": "*" 754 + } 755 + }, 756 + "node_modules/@types/connect": { 757 + "version": "3.4.38", 758 + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", 759 + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", 760 + "dev": true, 761 + "license": "MIT", 762 + "dependencies": { 763 + "@types/node": "*" 764 + } 765 + }, 766 + "node_modules/@types/express": { 767 + "version": "5.0.5", 768 + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", 769 + "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", 770 + "dev": true, 771 + "license": "MIT", 772 + "dependencies": { 773 + "@types/body-parser": "*", 774 + "@types/express-serve-static-core": "^5.0.0", 775 + "@types/serve-static": "^1" 776 + } 777 + }, 778 + "node_modules/@types/express-serve-static-core": { 779 + "version": "5.1.0", 780 + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", 781 + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", 782 + "dev": true, 783 + "license": "MIT", 784 + "dependencies": { 785 + "@types/node": "*", 786 + "@types/qs": "*", 787 + "@types/range-parser": "*", 788 + "@types/send": "*" 789 + } 790 + }, 791 + "node_modules/@types/http-errors": { 792 + "version": "2.0.5", 793 + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", 794 + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", 795 + "dev": true, 796 + "license": "MIT" 797 + }, 798 + "node_modules/@types/mime": { 799 + "version": "1.3.5", 800 + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", 801 + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", 802 + "dev": true, 803 + "license": "MIT" 804 + }, 805 + "node_modules/@types/node": { 806 + "version": "24.10.1", 807 + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", 808 + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", 809 + "dev": true, 810 + "license": "MIT", 811 + "dependencies": { 812 + "undici-types": "~7.16.0" 813 + } 814 + }, 815 + "node_modules/@types/qs": { 816 + "version": "6.14.0", 817 + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", 818 + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", 819 + "dev": true, 820 + "license": "MIT" 821 + }, 822 + "node_modules/@types/range-parser": { 823 + "version": "1.2.7", 824 + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", 825 + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", 826 + "dev": true, 827 + "license": "MIT" 828 + }, 829 + "node_modules/@types/send": { 830 + "version": "1.2.1", 831 + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", 832 + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", 833 + "dev": true, 834 + "license": "MIT", 835 + "dependencies": { 836 + "@types/node": "*" 837 + } 838 + }, 839 + "node_modules/@types/serve-static": { 840 + "version": "1.15.10", 841 + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", 842 + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", 843 + "dev": true, 844 + "license": "MIT", 845 + "dependencies": { 846 + "@types/http-errors": "*", 847 + "@types/node": "*", 848 + "@types/send": "<1" 849 + } 850 + }, 851 + "node_modules/@types/serve-static/node_modules/@types/send": { 852 + "version": "0.17.6", 853 + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", 854 + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", 855 + "dev": true, 856 + "license": "MIT", 857 + "dependencies": { 858 + "@types/mime": "^1", 859 + "@types/node": "*" 860 + } 861 + }, 862 + "node_modules/accepts": { 863 + "version": "2.0.0", 864 + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", 865 + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", 866 + "license": "MIT", 867 + "dependencies": { 868 + "mime-types": "^3.0.0", 869 + "negotiator": "^1.0.0" 870 + }, 871 + "engines": { 872 + "node": ">= 0.6" 873 + } 874 + }, 875 + "node_modules/await-lock": { 876 + "version": "2.2.2", 877 + "resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz", 878 + "integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==", 879 + "license": "MIT" 880 + }, 881 + "node_modules/base64-js": { 882 + "version": "1.5.1", 883 + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 884 + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 885 + "funding": [ 886 + { 887 + "type": "github", 888 + "url": "https://github.com/sponsors/feross" 889 + }, 890 + { 891 + "type": "patreon", 892 + "url": "https://www.patreon.com/feross" 893 + }, 894 + { 895 + "type": "consulting", 896 + "url": "https://feross.org/support" 897 + } 898 + ], 899 + "license": "MIT" 900 + }, 901 + "node_modules/better-sqlite3": { 902 + "version": "12.5.0", 903 + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.5.0.tgz", 904 + "integrity": "sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg==", 905 + "hasInstallScript": true, 906 + "license": "MIT", 907 + "dependencies": { 908 + "bindings": "^1.5.0", 909 + "prebuild-install": "^7.1.1" 910 + }, 911 + "engines": { 912 + "node": "20.x || 22.x || 23.x || 24.x || 25.x" 913 + } 914 + }, 915 + "node_modules/bindings": { 916 + "version": "1.5.0", 917 + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 918 + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 919 + "license": "MIT", 920 + "dependencies": { 921 + "file-uri-to-path": "1.0.0" 922 + } 923 + }, 924 + "node_modules/bl": { 925 + "version": "4.1.0", 926 + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", 927 + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", 928 + "license": "MIT", 929 + "dependencies": { 930 + "buffer": "^5.5.0", 931 + "inherits": "^2.0.4", 932 + "readable-stream": "^3.4.0" 933 + } 934 + }, 935 + "node_modules/body-parser": { 936 + "version": "2.2.1", 937 + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", 938 + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", 939 + "license": "MIT", 940 + "dependencies": { 941 + "bytes": "^3.1.2", 942 + "content-type": "^1.0.5", 943 + "debug": "^4.4.3", 944 + "http-errors": "^2.0.0", 945 + "iconv-lite": "^0.7.0", 946 + "on-finished": "^2.4.1", 947 + "qs": "^6.14.0", 948 + "raw-body": "^3.0.1", 949 + "type-is": "^2.0.1" 950 + }, 951 + "engines": { 952 + "node": ">=18" 953 + }, 954 + "funding": { 955 + "type": "opencollective", 956 + "url": "https://opencollective.com/express" 957 + } 958 + }, 959 + "node_modules/buffer": { 960 + "version": "5.7.1", 961 + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 962 + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 963 + "funding": [ 964 + { 965 + "type": "github", 966 + "url": "https://github.com/sponsors/feross" 967 + }, 968 + { 969 + "type": "patreon", 970 + "url": "https://www.patreon.com/feross" 971 + }, 972 + { 973 + "type": "consulting", 974 + "url": "https://feross.org/support" 975 + } 976 + ], 977 + "license": "MIT", 978 + "dependencies": { 979 + "base64-js": "^1.3.1", 980 + "ieee754": "^1.1.13" 981 + } 982 + }, 983 + "node_modules/bytes": { 984 + "version": "3.1.2", 985 + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 986 + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 987 + "license": "MIT", 988 + "engines": { 989 + "node": ">= 0.8" 990 + } 991 + }, 992 + "node_modules/call-bind-apply-helpers": { 993 + "version": "1.0.2", 994 + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 995 + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 996 + "license": "MIT", 997 + "dependencies": { 998 + "es-errors": "^1.3.0", 999 + "function-bind": "^1.1.2" 1000 + }, 1001 + "engines": { 1002 + "node": ">= 0.4" 1003 + } 1004 + }, 1005 + "node_modules/call-bound": { 1006 + "version": "1.0.4", 1007 + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 1008 + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 1009 + "license": "MIT", 1010 + "dependencies": { 1011 + "call-bind-apply-helpers": "^1.0.2", 1012 + "get-intrinsic": "^1.3.0" 1013 + }, 1014 + "engines": { 1015 + "node": ">= 0.4" 1016 + }, 1017 + "funding": { 1018 + "url": "https://github.com/sponsors/ljharb" 1019 + } 1020 + }, 1021 + "node_modules/chownr": { 1022 + "version": "1.1.4", 1023 + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 1024 + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", 1025 + "license": "ISC" 1026 + }, 1027 + "node_modules/content-disposition": { 1028 + "version": "1.0.1", 1029 + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", 1030 + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", 1031 + "license": "MIT", 1032 + "engines": { 1033 + "node": ">=18" 1034 + }, 1035 + "funding": { 1036 + "type": "opencollective", 1037 + "url": "https://opencollective.com/express" 1038 + } 1039 + }, 1040 + "node_modules/content-type": { 1041 + "version": "1.0.5", 1042 + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 1043 + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 1044 + "license": "MIT", 1045 + "engines": { 1046 + "node": ">= 0.6" 1047 + } 1048 + }, 1049 + "node_modules/cookie": { 1050 + "version": "0.7.2", 1051 + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", 1052 + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", 1053 + "license": "MIT", 1054 + "engines": { 1055 + "node": ">= 0.6" 1056 + } 1057 + }, 1058 + "node_modules/cookie-signature": { 1059 + "version": "1.2.2", 1060 + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", 1061 + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", 1062 + "license": "MIT", 1063 + "engines": { 1064 + "node": ">=6.6.0" 1065 + } 1066 + }, 1067 + "node_modules/core-js": { 1068 + "version": "3.47.0", 1069 + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", 1070 + "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", 1071 + "hasInstallScript": true, 1072 + "license": "MIT", 1073 + "funding": { 1074 + "type": "opencollective", 1075 + "url": "https://opencollective.com/core-js" 1076 + } 1077 + }, 1078 + "node_modules/debug": { 1079 + "version": "4.4.3", 1080 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", 1081 + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", 1082 + "license": "MIT", 1083 + "dependencies": { 1084 + "ms": "^2.1.3" 1085 + }, 1086 + "engines": { 1087 + "node": ">=6.0" 1088 + }, 1089 + "peerDependenciesMeta": { 1090 + "supports-color": { 1091 + "optional": true 1092 + } 1093 + } 1094 + }, 1095 + "node_modules/decompress-response": { 1096 + "version": "6.0.0", 1097 + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", 1098 + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", 1099 + "license": "MIT", 1100 + "dependencies": { 1101 + "mimic-response": "^3.1.0" 1102 + }, 1103 + "engines": { 1104 + "node": ">=10" 1105 + }, 1106 + "funding": { 1107 + "url": "https://github.com/sponsors/sindresorhus" 1108 + } 1109 + }, 1110 + "node_modules/deep-extend": { 1111 + "version": "0.6.0", 1112 + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 1113 + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", 1114 + "license": "MIT", 1115 + "engines": { 1116 + "node": ">=4.0.0" 1117 + } 1118 + }, 1119 + "node_modules/depd": { 1120 + "version": "2.0.0", 1121 + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 1122 + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 1123 + "license": "MIT", 1124 + "engines": { 1125 + "node": ">= 0.8" 1126 + } 1127 + }, 1128 + "node_modules/detect-libc": { 1129 + "version": "2.1.2", 1130 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", 1131 + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", 1132 + "license": "Apache-2.0", 1133 + "engines": { 1134 + "node": ">=8" 1135 + } 1136 + }, 1137 + "node_modules/dunder-proto": { 1138 + "version": "1.0.1", 1139 + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 1140 + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 1141 + "license": "MIT", 1142 + "dependencies": { 1143 + "call-bind-apply-helpers": "^1.0.1", 1144 + "es-errors": "^1.3.0", 1145 + "gopd": "^1.2.0" 1146 + }, 1147 + "engines": { 1148 + "node": ">= 0.4" 1149 + } 1150 + }, 1151 + "node_modules/ee-first": { 1152 + "version": "1.1.1", 1153 + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 1154 + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 1155 + "license": "MIT" 1156 + }, 1157 + "node_modules/encodeurl": { 1158 + "version": "2.0.0", 1159 + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 1160 + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 1161 + "license": "MIT", 1162 + "engines": { 1163 + "node": ">= 0.8" 1164 + } 1165 + }, 1166 + "node_modules/end-of-stream": { 1167 + "version": "1.4.5", 1168 + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", 1169 + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", 1170 + "license": "MIT", 1171 + "dependencies": { 1172 + "once": "^1.4.0" 1173 + } 1174 + }, 1175 + "node_modules/es-define-property": { 1176 + "version": "1.0.1", 1177 + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 1178 + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 1179 + "license": "MIT", 1180 + "engines": { 1181 + "node": ">= 0.4" 1182 + } 1183 + }, 1184 + "node_modules/es-errors": { 1185 + "version": "1.3.0", 1186 + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 1187 + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 1188 + "license": "MIT", 1189 + "engines": { 1190 + "node": ">= 0.4" 1191 + } 1192 + }, 1193 + "node_modules/es-object-atoms": { 1194 + "version": "1.1.1", 1195 + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 1196 + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 1197 + "license": "MIT", 1198 + "dependencies": { 1199 + "es-errors": "^1.3.0" 1200 + }, 1201 + "engines": { 1202 + "node": ">= 0.4" 1203 + } 1204 + }, 1205 + "node_modules/esbuild": { 1206 + "version": "0.25.12", 1207 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", 1208 + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", 1209 + "dev": true, 1210 + "hasInstallScript": true, 1211 + "license": "MIT", 1212 + "bin": { 1213 + "esbuild": "bin/esbuild" 1214 + }, 1215 + "engines": { 1216 + "node": ">=18" 1217 + }, 1218 + "optionalDependencies": { 1219 + "@esbuild/aix-ppc64": "0.25.12", 1220 + "@esbuild/android-arm": "0.25.12", 1221 + "@esbuild/android-arm64": "0.25.12", 1222 + "@esbuild/android-x64": "0.25.12", 1223 + "@esbuild/darwin-arm64": "0.25.12", 1224 + "@esbuild/darwin-x64": "0.25.12", 1225 + "@esbuild/freebsd-arm64": "0.25.12", 1226 + "@esbuild/freebsd-x64": "0.25.12", 1227 + "@esbuild/linux-arm": "0.25.12", 1228 + "@esbuild/linux-arm64": "0.25.12", 1229 + "@esbuild/linux-ia32": "0.25.12", 1230 + "@esbuild/linux-loong64": "0.25.12", 1231 + "@esbuild/linux-mips64el": "0.25.12", 1232 + "@esbuild/linux-ppc64": "0.25.12", 1233 + "@esbuild/linux-riscv64": "0.25.12", 1234 + "@esbuild/linux-s390x": "0.25.12", 1235 + "@esbuild/linux-x64": "0.25.12", 1236 + "@esbuild/netbsd-arm64": "0.25.12", 1237 + "@esbuild/netbsd-x64": "0.25.12", 1238 + "@esbuild/openbsd-arm64": "0.25.12", 1239 + "@esbuild/openbsd-x64": "0.25.12", 1240 + "@esbuild/openharmony-arm64": "0.25.12", 1241 + "@esbuild/sunos-x64": "0.25.12", 1242 + "@esbuild/win32-arm64": "0.25.12", 1243 + "@esbuild/win32-ia32": "0.25.12", 1244 + "@esbuild/win32-x64": "0.25.12" 1245 + } 1246 + }, 1247 + "node_modules/escape-html": { 1248 + "version": "1.0.3", 1249 + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 1250 + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 1251 + "license": "MIT" 1252 + }, 1253 + "node_modules/etag": { 1254 + "version": "1.8.1", 1255 + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 1256 + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 1257 + "license": "MIT", 1258 + "engines": { 1259 + "node": ">= 0.6" 1260 + } 1261 + }, 1262 + "node_modules/expand-template": { 1263 + "version": "2.0.3", 1264 + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", 1265 + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", 1266 + "license": "(MIT OR WTFPL)", 1267 + "engines": { 1268 + "node": ">=6" 1269 + } 1270 + }, 1271 + "node_modules/express": { 1272 + "version": "5.1.0", 1273 + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", 1274 + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", 1275 + "license": "MIT", 1276 + "dependencies": { 1277 + "accepts": "^2.0.0", 1278 + "body-parser": "^2.2.0", 1279 + "content-disposition": "^1.0.0", 1280 + "content-type": "^1.0.5", 1281 + "cookie": "^0.7.1", 1282 + "cookie-signature": "^1.2.1", 1283 + "debug": "^4.4.0", 1284 + "encodeurl": "^2.0.0", 1285 + "escape-html": "^1.0.3", 1286 + "etag": "^1.8.1", 1287 + "finalhandler": "^2.1.0", 1288 + "fresh": "^2.0.0", 1289 + "http-errors": "^2.0.0", 1290 + "merge-descriptors": "^2.0.0", 1291 + "mime-types": "^3.0.0", 1292 + "on-finished": "^2.4.1", 1293 + "once": "^1.4.0", 1294 + "parseurl": "^1.3.3", 1295 + "proxy-addr": "^2.0.7", 1296 + "qs": "^6.14.0", 1297 + "range-parser": "^1.2.1", 1298 + "router": "^2.2.0", 1299 + "send": "^1.1.0", 1300 + "serve-static": "^2.2.0", 1301 + "statuses": "^2.0.1", 1302 + "type-is": "^2.0.1", 1303 + "vary": "^1.1.2" 1304 + }, 1305 + "engines": { 1306 + "node": ">= 18" 1307 + }, 1308 + "funding": { 1309 + "type": "opencollective", 1310 + "url": "https://opencollective.com/express" 1311 + } 1312 + }, 1313 + "node_modules/file-uri-to-path": { 1314 + "version": "1.0.0", 1315 + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 1316 + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", 1317 + "license": "MIT" 1318 + }, 1319 + "node_modules/finalhandler": { 1320 + "version": "2.1.0", 1321 + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", 1322 + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", 1323 + "license": "MIT", 1324 + "dependencies": { 1325 + "debug": "^4.4.0", 1326 + "encodeurl": "^2.0.0", 1327 + "escape-html": "^1.0.3", 1328 + "on-finished": "^2.4.1", 1329 + "parseurl": "^1.3.3", 1330 + "statuses": "^2.0.1" 1331 + }, 1332 + "engines": { 1333 + "node": ">= 0.8" 1334 + } 1335 + }, 1336 + "node_modules/forwarded": { 1337 + "version": "0.2.0", 1338 + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 1339 + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 1340 + "license": "MIT", 1341 + "engines": { 1342 + "node": ">= 0.6" 1343 + } 1344 + }, 1345 + "node_modules/fresh": { 1346 + "version": "2.0.0", 1347 + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", 1348 + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", 1349 + "license": "MIT", 1350 + "engines": { 1351 + "node": ">= 0.8" 1352 + } 1353 + }, 1354 + "node_modules/fs-constants": { 1355 + "version": "1.0.0", 1356 + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", 1357 + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", 1358 + "license": "MIT" 1359 + }, 1360 + "node_modules/fsevents": { 1361 + "version": "2.3.3", 1362 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1363 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1364 + "dev": true, 1365 + "hasInstallScript": true, 1366 + "license": "MIT", 1367 + "optional": true, 1368 + "os": [ 1369 + "darwin" 1370 + ], 1371 + "engines": { 1372 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1373 + } 1374 + }, 1375 + "node_modules/function-bind": { 1376 + "version": "1.1.2", 1377 + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 1378 + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 1379 + "license": "MIT", 1380 + "funding": { 1381 + "url": "https://github.com/sponsors/ljharb" 1382 + } 1383 + }, 1384 + "node_modules/get-intrinsic": { 1385 + "version": "1.3.0", 1386 + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 1387 + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 1388 + "license": "MIT", 1389 + "dependencies": { 1390 + "call-bind-apply-helpers": "^1.0.2", 1391 + "es-define-property": "^1.0.1", 1392 + "es-errors": "^1.3.0", 1393 + "es-object-atoms": "^1.1.1", 1394 + "function-bind": "^1.1.2", 1395 + "get-proto": "^1.0.1", 1396 + "gopd": "^1.2.0", 1397 + "has-symbols": "^1.1.0", 1398 + "hasown": "^2.0.2", 1399 + "math-intrinsics": "^1.1.0" 1400 + }, 1401 + "engines": { 1402 + "node": ">= 0.4" 1403 + }, 1404 + "funding": { 1405 + "url": "https://github.com/sponsors/ljharb" 1406 + } 1407 + }, 1408 + "node_modules/get-proto": { 1409 + "version": "1.0.1", 1410 + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 1411 + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 1412 + "license": "MIT", 1413 + "dependencies": { 1414 + "dunder-proto": "^1.0.1", 1415 + "es-object-atoms": "^1.0.0" 1416 + }, 1417 + "engines": { 1418 + "node": ">= 0.4" 1419 + } 1420 + }, 1421 + "node_modules/get-tsconfig": { 1422 + "version": "4.13.0", 1423 + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", 1424 + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", 1425 + "dev": true, 1426 + "license": "MIT", 1427 + "dependencies": { 1428 + "resolve-pkg-maps": "^1.0.0" 1429 + }, 1430 + "funding": { 1431 + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" 1432 + } 1433 + }, 1434 + "node_modules/github-from-package": { 1435 + "version": "0.0.0", 1436 + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", 1437 + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", 1438 + "license": "MIT" 1439 + }, 1440 + "node_modules/gopd": { 1441 + "version": "1.2.0", 1442 + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 1443 + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 1444 + "license": "MIT", 1445 + "engines": { 1446 + "node": ">= 0.4" 1447 + }, 1448 + "funding": { 1449 + "url": "https://github.com/sponsors/ljharb" 1450 + } 1451 + }, 1452 + "node_modules/has-symbols": { 1453 + "version": "1.1.0", 1454 + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 1455 + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 1456 + "license": "MIT", 1457 + "engines": { 1458 + "node": ">= 0.4" 1459 + }, 1460 + "funding": { 1461 + "url": "https://github.com/sponsors/ljharb" 1462 + } 1463 + }, 1464 + "node_modules/hasown": { 1465 + "version": "2.0.2", 1466 + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 1467 + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 1468 + "license": "MIT", 1469 + "dependencies": { 1470 + "function-bind": "^1.1.2" 1471 + }, 1472 + "engines": { 1473 + "node": ">= 0.4" 1474 + } 1475 + }, 1476 + "node_modules/http-errors": { 1477 + "version": "2.0.1", 1478 + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", 1479 + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", 1480 + "license": "MIT", 1481 + "dependencies": { 1482 + "depd": "~2.0.0", 1483 + "inherits": "~2.0.4", 1484 + "setprototypeof": "~1.2.0", 1485 + "statuses": "~2.0.2", 1486 + "toidentifier": "~1.0.1" 1487 + }, 1488 + "engines": { 1489 + "node": ">= 0.8" 1490 + }, 1491 + "funding": { 1492 + "type": "opencollective", 1493 + "url": "https://opencollective.com/express" 1494 + } 1495 + }, 1496 + "node_modules/iconv-lite": { 1497 + "version": "0.7.0", 1498 + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", 1499 + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", 1500 + "license": "MIT", 1501 + "dependencies": { 1502 + "safer-buffer": ">= 2.1.2 < 3.0.0" 1503 + }, 1504 + "engines": { 1505 + "node": ">=0.10.0" 1506 + }, 1507 + "funding": { 1508 + "type": "opencollective", 1509 + "url": "https://opencollective.com/express" 1510 + } 1511 + }, 1512 + "node_modules/ieee754": { 1513 + "version": "1.2.1", 1514 + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 1515 + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 1516 + "funding": [ 1517 + { 1518 + "type": "github", 1519 + "url": "https://github.com/sponsors/feross" 1520 + }, 1521 + { 1522 + "type": "patreon", 1523 + "url": "https://www.patreon.com/feross" 1524 + }, 1525 + { 1526 + "type": "consulting", 1527 + "url": "https://feross.org/support" 1528 + } 1529 + ], 1530 + "license": "BSD-3-Clause" 1531 + }, 1532 + "node_modules/inherits": { 1533 + "version": "2.0.4", 1534 + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1535 + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1536 + "license": "ISC" 1537 + }, 1538 + "node_modules/ini": { 1539 + "version": "1.3.8", 1540 + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", 1541 + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", 1542 + "license": "ISC" 1543 + }, 1544 + "node_modules/ipaddr.js": { 1545 + "version": "2.3.0", 1546 + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", 1547 + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", 1548 + "license": "MIT", 1549 + "engines": { 1550 + "node": ">= 10" 1551 + } 1552 + }, 1553 + "node_modules/iron-session": { 1554 + "version": "8.0.4", 1555 + "resolved": "https://registry.npmjs.org/iron-session/-/iron-session-8.0.4.tgz", 1556 + "integrity": "sha512-9ivNnaKOd08osD0lJ3i6If23GFS2LsxyMU8Gf/uBUEgm8/8CC1hrrCHFDpMo3IFbpBgwoo/eairRsaD3c5itxA==", 1557 + "funding": [ 1558 + "https://github.com/sponsors/vvo", 1559 + "https://github.com/sponsors/brc-dd" 1560 + ], 1561 + "license": "MIT", 1562 + "dependencies": { 1563 + "cookie": "^0.7.2", 1564 + "iron-webcrypto": "^1.2.1", 1565 + "uncrypto": "^0.1.3" 1566 + } 1567 + }, 1568 + "node_modules/iron-webcrypto": { 1569 + "version": "1.2.1", 1570 + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", 1571 + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", 1572 + "license": "MIT", 1573 + "funding": { 1574 + "url": "https://github.com/sponsors/brc-dd" 1575 + } 1576 + }, 1577 + "node_modules/is-promise": { 1578 + "version": "4.0.0", 1579 + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", 1580 + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", 1581 + "license": "MIT" 1582 + }, 1583 + "node_modules/iso-datestring-validator": { 1584 + "version": "2.2.2", 1585 + "resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz", 1586 + "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==", 1587 + "license": "MIT" 1588 + }, 1589 + "node_modules/jose": { 1590 + "version": "5.10.0", 1591 + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", 1592 + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", 1593 + "license": "MIT", 1594 + "funding": { 1595 + "url": "https://github.com/sponsors/panva" 1596 + } 1597 + }, 1598 + "node_modules/lru-cache": { 1599 + "version": "10.4.3", 1600 + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", 1601 + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", 1602 + "license": "ISC" 1603 + }, 1604 + "node_modules/math-intrinsics": { 1605 + "version": "1.1.0", 1606 + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 1607 + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 1608 + "license": "MIT", 1609 + "engines": { 1610 + "node": ">= 0.4" 1611 + } 1612 + }, 1613 + "node_modules/media-typer": { 1614 + "version": "1.1.0", 1615 + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", 1616 + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", 1617 + "license": "MIT", 1618 + "engines": { 1619 + "node": ">= 0.8" 1620 + } 1621 + }, 1622 + "node_modules/merge-descriptors": { 1623 + "version": "2.0.0", 1624 + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", 1625 + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", 1626 + "license": "MIT", 1627 + "engines": { 1628 + "node": ">=18" 1629 + }, 1630 + "funding": { 1631 + "url": "https://github.com/sponsors/sindresorhus" 1632 + } 1633 + }, 1634 + "node_modules/mime-db": { 1635 + "version": "1.54.0", 1636 + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 1637 + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 1638 + "license": "MIT", 1639 + "engines": { 1640 + "node": ">= 0.6" 1641 + } 1642 + }, 1643 + "node_modules/mime-types": { 1644 + "version": "3.0.2", 1645 + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", 1646 + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", 1647 + "license": "MIT", 1648 + "dependencies": { 1649 + "mime-db": "^1.54.0" 1650 + }, 1651 + "engines": { 1652 + "node": ">=18" 1653 + }, 1654 + "funding": { 1655 + "type": "opencollective", 1656 + "url": "https://opencollective.com/express" 1657 + } 1658 + }, 1659 + "node_modules/mimic-response": { 1660 + "version": "3.1.0", 1661 + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", 1662 + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", 1663 + "license": "MIT", 1664 + "engines": { 1665 + "node": ">=10" 1666 + }, 1667 + "funding": { 1668 + "url": "https://github.com/sponsors/sindresorhus" 1669 + } 1670 + }, 1671 + "node_modules/minimist": { 1672 + "version": "1.2.8", 1673 + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 1674 + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 1675 + "license": "MIT", 1676 + "funding": { 1677 + "url": "https://github.com/sponsors/ljharb" 1678 + } 1679 + }, 1680 + "node_modules/mkdirp-classic": { 1681 + "version": "0.5.3", 1682 + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", 1683 + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", 1684 + "license": "MIT" 1685 + }, 1686 + "node_modules/ms": { 1687 + "version": "2.1.3", 1688 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1689 + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1690 + "license": "MIT" 1691 + }, 1692 + "node_modules/multiformats": { 1693 + "version": "9.9.0", 1694 + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", 1695 + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", 1696 + "license": "(Apache-2.0 AND MIT)" 1697 + }, 1698 + "node_modules/napi-build-utils": { 1699 + "version": "2.0.0", 1700 + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", 1701 + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", 1702 + "license": "MIT" 1703 + }, 1704 + "node_modules/negotiator": { 1705 + "version": "1.0.0", 1706 + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", 1707 + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", 1708 + "license": "MIT", 1709 + "engines": { 1710 + "node": ">= 0.6" 1711 + } 1712 + }, 1713 + "node_modules/node-abi": { 1714 + "version": "3.85.0", 1715 + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", 1716 + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", 1717 + "license": "MIT", 1718 + "dependencies": { 1719 + "semver": "^7.3.5" 1720 + }, 1721 + "engines": { 1722 + "node": ">=10" 1723 + } 1724 + }, 1725 + "node_modules/object-inspect": { 1726 + "version": "1.13.4", 1727 + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 1728 + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 1729 + "license": "MIT", 1730 + "engines": { 1731 + "node": ">= 0.4" 1732 + }, 1733 + "funding": { 1734 + "url": "https://github.com/sponsors/ljharb" 1735 + } 1736 + }, 1737 + "node_modules/on-finished": { 1738 + "version": "2.4.1", 1739 + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1740 + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1741 + "license": "MIT", 1742 + "dependencies": { 1743 + "ee-first": "1.1.1" 1744 + }, 1745 + "engines": { 1746 + "node": ">= 0.8" 1747 + } 1748 + }, 1749 + "node_modules/once": { 1750 + "version": "1.4.0", 1751 + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1752 + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1753 + "license": "ISC", 1754 + "dependencies": { 1755 + "wrappy": "1" 1756 + } 1757 + }, 1758 + "node_modules/parseurl": { 1759 + "version": "1.3.3", 1760 + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1761 + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1762 + "license": "MIT", 1763 + "engines": { 1764 + "node": ">= 0.8" 1765 + } 1766 + }, 1767 + "node_modules/path-to-regexp": { 1768 + "version": "8.3.0", 1769 + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", 1770 + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", 1771 + "license": "MIT", 1772 + "funding": { 1773 + "type": "opencollective", 1774 + "url": "https://opencollective.com/express" 1775 + } 1776 + }, 1777 + "node_modules/prebuild-install": { 1778 + "version": "7.1.3", 1779 + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", 1780 + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", 1781 + "license": "MIT", 1782 + "dependencies": { 1783 + "detect-libc": "^2.0.0", 1784 + "expand-template": "^2.0.3", 1785 + "github-from-package": "0.0.0", 1786 + "minimist": "^1.2.3", 1787 + "mkdirp-classic": "^0.5.3", 1788 + "napi-build-utils": "^2.0.0", 1789 + "node-abi": "^3.3.0", 1790 + "pump": "^3.0.0", 1791 + "rc": "^1.2.7", 1792 + "simple-get": "^4.0.0", 1793 + "tar-fs": "^2.0.0", 1794 + "tunnel-agent": "^0.6.0" 1795 + }, 1796 + "bin": { 1797 + "prebuild-install": "bin.js" 1798 + }, 1799 + "engines": { 1800 + "node": ">=10" 1801 + } 1802 + }, 1803 + "node_modules/proxy-addr": { 1804 + "version": "2.0.7", 1805 + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1806 + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1807 + "license": "MIT", 1808 + "dependencies": { 1809 + "forwarded": "0.2.0", 1810 + "ipaddr.js": "1.9.1" 1811 + }, 1812 + "engines": { 1813 + "node": ">= 0.10" 1814 + } 1815 + }, 1816 + "node_modules/proxy-addr/node_modules/ipaddr.js": { 1817 + "version": "1.9.1", 1818 + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1819 + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 1820 + "license": "MIT", 1821 + "engines": { 1822 + "node": ">= 0.10" 1823 + } 1824 + }, 1825 + "node_modules/pump": { 1826 + "version": "3.0.3", 1827 + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", 1828 + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", 1829 + "license": "MIT", 1830 + "dependencies": { 1831 + "end-of-stream": "^1.1.0", 1832 + "once": "^1.3.1" 1833 + } 1834 + }, 1835 + "node_modules/qs": { 1836 + "version": "6.14.0", 1837 + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", 1838 + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", 1839 + "license": "BSD-3-Clause", 1840 + "dependencies": { 1841 + "side-channel": "^1.1.0" 1842 + }, 1843 + "engines": { 1844 + "node": ">=0.6" 1845 + }, 1846 + "funding": { 1847 + "url": "https://github.com/sponsors/ljharb" 1848 + } 1849 + }, 1850 + "node_modules/range-parser": { 1851 + "version": "1.2.1", 1852 + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1853 + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1854 + "license": "MIT", 1855 + "engines": { 1856 + "node": ">= 0.6" 1857 + } 1858 + }, 1859 + "node_modules/raw-body": { 1860 + "version": "3.0.2", 1861 + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", 1862 + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", 1863 + "license": "MIT", 1864 + "dependencies": { 1865 + "bytes": "~3.1.2", 1866 + "http-errors": "~2.0.1", 1867 + "iconv-lite": "~0.7.0", 1868 + "unpipe": "~1.0.0" 1869 + }, 1870 + "engines": { 1871 + "node": ">= 0.10" 1872 + } 1873 + }, 1874 + "node_modules/rc": { 1875 + "version": "1.2.8", 1876 + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 1877 + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 1878 + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", 1879 + "dependencies": { 1880 + "deep-extend": "^0.6.0", 1881 + "ini": "~1.3.0", 1882 + "minimist": "^1.2.0", 1883 + "strip-json-comments": "~2.0.1" 1884 + }, 1885 + "bin": { 1886 + "rc": "cli.js" 1887 + } 1888 + }, 1889 + "node_modules/readable-stream": { 1890 + "version": "3.6.2", 1891 + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 1892 + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 1893 + "license": "MIT", 1894 + "dependencies": { 1895 + "inherits": "^2.0.3", 1896 + "string_decoder": "^1.1.1", 1897 + "util-deprecate": "^1.0.1" 1898 + }, 1899 + "engines": { 1900 + "node": ">= 6" 1901 + } 1902 + }, 1903 + "node_modules/resolve-pkg-maps": { 1904 + "version": "1.0.0", 1905 + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", 1906 + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", 1907 + "dev": true, 1908 + "license": "MIT", 1909 + "funding": { 1910 + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" 1911 + } 1912 + }, 1913 + "node_modules/router": { 1914 + "version": "2.2.0", 1915 + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", 1916 + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", 1917 + "license": "MIT", 1918 + "dependencies": { 1919 + "debug": "^4.4.0", 1920 + "depd": "^2.0.0", 1921 + "is-promise": "^4.0.0", 1922 + "parseurl": "^1.3.3", 1923 + "path-to-regexp": "^8.0.0" 1924 + }, 1925 + "engines": { 1926 + "node": ">= 18" 1927 + } 1928 + }, 1929 + "node_modules/safe-buffer": { 1930 + "version": "5.2.1", 1931 + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1932 + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1933 + "funding": [ 1934 + { 1935 + "type": "github", 1936 + "url": "https://github.com/sponsors/feross" 1937 + }, 1938 + { 1939 + "type": "patreon", 1940 + "url": "https://www.patreon.com/feross" 1941 + }, 1942 + { 1943 + "type": "consulting", 1944 + "url": "https://feross.org/support" 1945 + } 1946 + ], 1947 + "license": "MIT" 1948 + }, 1949 + "node_modules/safer-buffer": { 1950 + "version": "2.1.2", 1951 + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1952 + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1953 + "license": "MIT" 1954 + }, 1955 + "node_modules/semver": { 1956 + "version": "7.7.3", 1957 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", 1958 + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", 1959 + "license": "ISC", 1960 + "bin": { 1961 + "semver": "bin/semver.js" 1962 + }, 1963 + "engines": { 1964 + "node": ">=10" 1965 + } 1966 + }, 1967 + "node_modules/send": { 1968 + "version": "1.2.0", 1969 + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", 1970 + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", 1971 + "license": "MIT", 1972 + "dependencies": { 1973 + "debug": "^4.3.5", 1974 + "encodeurl": "^2.0.0", 1975 + "escape-html": "^1.0.3", 1976 + "etag": "^1.8.1", 1977 + "fresh": "^2.0.0", 1978 + "http-errors": "^2.0.0", 1979 + "mime-types": "^3.0.1", 1980 + "ms": "^2.1.3", 1981 + "on-finished": "^2.4.1", 1982 + "range-parser": "^1.2.1", 1983 + "statuses": "^2.0.1" 1984 + }, 1985 + "engines": { 1986 + "node": ">= 18" 1987 + } 1988 + }, 1989 + "node_modules/serve-static": { 1990 + "version": "2.2.0", 1991 + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", 1992 + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", 1993 + "license": "MIT", 1994 + "dependencies": { 1995 + "encodeurl": "^2.0.0", 1996 + "escape-html": "^1.0.3", 1997 + "parseurl": "^1.3.3", 1998 + "send": "^1.2.0" 1999 + }, 2000 + "engines": { 2001 + "node": ">= 18" 2002 + } 2003 + }, 2004 + "node_modules/setprototypeof": { 2005 + "version": "1.2.0", 2006 + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 2007 + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 2008 + "license": "ISC" 2009 + }, 2010 + "node_modules/side-channel": { 2011 + "version": "1.1.0", 2012 + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 2013 + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 2014 + "license": "MIT", 2015 + "dependencies": { 2016 + "es-errors": "^1.3.0", 2017 + "object-inspect": "^1.13.3", 2018 + "side-channel-list": "^1.0.0", 2019 + "side-channel-map": "^1.0.1", 2020 + "side-channel-weakmap": "^1.0.2" 2021 + }, 2022 + "engines": { 2023 + "node": ">= 0.4" 2024 + }, 2025 + "funding": { 2026 + "url": "https://github.com/sponsors/ljharb" 2027 + } 2028 + }, 2029 + "node_modules/side-channel-list": { 2030 + "version": "1.0.0", 2031 + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 2032 + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 2033 + "license": "MIT", 2034 + "dependencies": { 2035 + "es-errors": "^1.3.0", 2036 + "object-inspect": "^1.13.3" 2037 + }, 2038 + "engines": { 2039 + "node": ">= 0.4" 2040 + }, 2041 + "funding": { 2042 + "url": "https://github.com/sponsors/ljharb" 2043 + } 2044 + }, 2045 + "node_modules/side-channel-map": { 2046 + "version": "1.0.1", 2047 + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 2048 + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 2049 + "license": "MIT", 2050 + "dependencies": { 2051 + "call-bound": "^1.0.2", 2052 + "es-errors": "^1.3.0", 2053 + "get-intrinsic": "^1.2.5", 2054 + "object-inspect": "^1.13.3" 2055 + }, 2056 + "engines": { 2057 + "node": ">= 0.4" 2058 + }, 2059 + "funding": { 2060 + "url": "https://github.com/sponsors/ljharb" 2061 + } 2062 + }, 2063 + "node_modules/side-channel-weakmap": { 2064 + "version": "1.0.2", 2065 + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 2066 + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 2067 + "license": "MIT", 2068 + "dependencies": { 2069 + "call-bound": "^1.0.2", 2070 + "es-errors": "^1.3.0", 2071 + "get-intrinsic": "^1.2.5", 2072 + "object-inspect": "^1.13.3", 2073 + "side-channel-map": "^1.0.1" 2074 + }, 2075 + "engines": { 2076 + "node": ">= 0.4" 2077 + }, 2078 + "funding": { 2079 + "url": "https://github.com/sponsors/ljharb" 2080 + } 2081 + }, 2082 + "node_modules/simple-concat": { 2083 + "version": "1.0.1", 2084 + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", 2085 + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", 2086 + "funding": [ 2087 + { 2088 + "type": "github", 2089 + "url": "https://github.com/sponsors/feross" 2090 + }, 2091 + { 2092 + "type": "patreon", 2093 + "url": "https://www.patreon.com/feross" 2094 + }, 2095 + { 2096 + "type": "consulting", 2097 + "url": "https://feross.org/support" 2098 + } 2099 + ], 2100 + "license": "MIT" 2101 + }, 2102 + "node_modules/simple-get": { 2103 + "version": "4.0.1", 2104 + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", 2105 + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", 2106 + "funding": [ 2107 + { 2108 + "type": "github", 2109 + "url": "https://github.com/sponsors/feross" 2110 + }, 2111 + { 2112 + "type": "patreon", 2113 + "url": "https://www.patreon.com/feross" 2114 + }, 2115 + { 2116 + "type": "consulting", 2117 + "url": "https://feross.org/support" 2118 + } 2119 + ], 2120 + "license": "MIT", 2121 + "dependencies": { 2122 + "decompress-response": "^6.0.0", 2123 + "once": "^1.3.1", 2124 + "simple-concat": "^1.0.0" 2125 + } 2126 + }, 2127 + "node_modules/statuses": { 2128 + "version": "2.0.2", 2129 + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", 2130 + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", 2131 + "license": "MIT", 2132 + "engines": { 2133 + "node": ">= 0.8" 2134 + } 2135 + }, 2136 + "node_modules/string_decoder": { 2137 + "version": "1.3.0", 2138 + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 2139 + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 2140 + "license": "MIT", 2141 + "dependencies": { 2142 + "safe-buffer": "~5.2.0" 2143 + } 2144 + }, 2145 + "node_modules/strip-json-comments": { 2146 + "version": "2.0.1", 2147 + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 2148 + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", 2149 + "license": "MIT", 2150 + "engines": { 2151 + "node": ">=0.10.0" 2152 + } 2153 + }, 2154 + "node_modules/tar-fs": { 2155 + "version": "2.1.4", 2156 + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", 2157 + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", 2158 + "license": "MIT", 2159 + "dependencies": { 2160 + "chownr": "^1.1.1", 2161 + "mkdirp-classic": "^0.5.2", 2162 + "pump": "^3.0.0", 2163 + "tar-stream": "^2.1.4" 2164 + } 2165 + }, 2166 + "node_modules/tar-stream": { 2167 + "version": "2.2.0", 2168 + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", 2169 + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", 2170 + "license": "MIT", 2171 + "dependencies": { 2172 + "bl": "^4.0.3", 2173 + "end-of-stream": "^1.4.1", 2174 + "fs-constants": "^1.0.0", 2175 + "inherits": "^2.0.3", 2176 + "readable-stream": "^3.1.1" 2177 + }, 2178 + "engines": { 2179 + "node": ">=6" 2180 + } 2181 + }, 2182 + "node_modules/tlds": { 2183 + "version": "1.261.0", 2184 + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz", 2185 + "integrity": "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==", 2186 + "license": "MIT", 2187 + "bin": { 2188 + "tlds": "bin.js" 2189 + } 2190 + }, 2191 + "node_modules/toidentifier": { 2192 + "version": "1.0.1", 2193 + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 2194 + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 2195 + "license": "MIT", 2196 + "engines": { 2197 + "node": ">=0.6" 2198 + } 2199 + }, 2200 + "node_modules/tslib": { 2201 + "version": "2.8.1", 2202 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 2203 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 2204 + "license": "0BSD" 2205 + }, 2206 + "node_modules/tsx": { 2207 + "version": "4.20.6", 2208 + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", 2209 + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", 2210 + "dev": true, 2211 + "license": "MIT", 2212 + "dependencies": { 2213 + "esbuild": "~0.25.0", 2214 + "get-tsconfig": "^4.7.5" 2215 + }, 2216 + "bin": { 2217 + "tsx": "dist/cli.mjs" 2218 + }, 2219 + "engines": { 2220 + "node": ">=18.0.0" 2221 + }, 2222 + "optionalDependencies": { 2223 + "fsevents": "~2.3.3" 2224 + } 2225 + }, 2226 + "node_modules/tunnel-agent": { 2227 + "version": "0.6.0", 2228 + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 2229 + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", 2230 + "license": "Apache-2.0", 2231 + "dependencies": { 2232 + "safe-buffer": "^5.0.1" 2233 + }, 2234 + "engines": { 2235 + "node": "*" 2236 + } 2237 + }, 2238 + "node_modules/type-is": { 2239 + "version": "2.0.1", 2240 + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", 2241 + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", 2242 + "license": "MIT", 2243 + "dependencies": { 2244 + "content-type": "^1.0.5", 2245 + "media-typer": "^1.1.0", 2246 + "mime-types": "^3.0.0" 2247 + }, 2248 + "engines": { 2249 + "node": ">= 0.6" 2250 + } 2251 + }, 2252 + "node_modules/typescript": { 2253 + "version": "5.9.3", 2254 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", 2255 + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 2256 + "dev": true, 2257 + "license": "Apache-2.0", 2258 + "bin": { 2259 + "tsc": "bin/tsc", 2260 + "tsserver": "bin/tsserver" 2261 + }, 2262 + "engines": { 2263 + "node": ">=14.17" 2264 + } 2265 + }, 2266 + "node_modules/uint8arrays": { 2267 + "version": "3.0.0", 2268 + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz", 2269 + "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", 2270 + "license": "MIT", 2271 + "dependencies": { 2272 + "multiformats": "^9.4.2" 2273 + } 2274 + }, 2275 + "node_modules/uncrypto": { 2276 + "version": "0.1.3", 2277 + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", 2278 + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", 2279 + "license": "MIT" 2280 + }, 2281 + "node_modules/undici": { 2282 + "version": "6.22.0", 2283 + "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", 2284 + "integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==", 2285 + "license": "MIT", 2286 + "engines": { 2287 + "node": ">=18.17" 2288 + } 2289 + }, 2290 + "node_modules/undici-types": { 2291 + "version": "7.16.0", 2292 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", 2293 + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", 2294 + "dev": true, 2295 + "license": "MIT" 2296 + }, 2297 + "node_modules/unicode-segmenter": { 2298 + "version": "0.14.0", 2299 + "resolved": "https://registry.npmjs.org/unicode-segmenter/-/unicode-segmenter-0.14.0.tgz", 2300 + "integrity": "sha512-AH4lhPCJANUnSLEKnM4byboctePJzltF4xj8b+NbNiYeAkAXGh7px2K/4NANFp7dnr6+zB3e6HLu8Jj8SKyvYg==", 2301 + "license": "MIT" 2302 + }, 2303 + "node_modules/unpipe": { 2304 + "version": "1.0.0", 2305 + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 2306 + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 2307 + "license": "MIT", 2308 + "engines": { 2309 + "node": ">= 0.8" 2310 + } 2311 + }, 2312 + "node_modules/util-deprecate": { 2313 + "version": "1.0.2", 2314 + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 2315 + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 2316 + "license": "MIT" 2317 + }, 2318 + "node_modules/vary": { 2319 + "version": "1.1.2", 2320 + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 2321 + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 2322 + "license": "MIT", 2323 + "engines": { 2324 + "node": ">= 0.8" 2325 + } 2326 + }, 2327 + "node_modules/wrappy": { 2328 + "version": "1.0.2", 2329 + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2330 + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 2331 + "license": "ISC" 2332 + }, 2333 + "node_modules/zod": { 2334 + "version": "3.25.76", 2335 + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", 2336 + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", 2337 + "license": "MIT", 2338 + "funding": { 2339 + "url": "https://github.com/sponsors/colinhacks" 2340 + } 2341 + } 2342 + } 2343 + }
+28
package.json
··· 1 + { 2 + "name": "anproto-over-atproto", 3 + "version": "1.0.0", 4 + "description": "", 5 + "main": "index.js", 6 + "scripts": { 7 + "dev": "tsx watch src/index.ts", 8 + "test": "echo \"Error: no test specified\" && exit 1" 9 + }, 10 + "keywords": [], 11 + "author": "", 12 + "license": "ISC", 13 + "type": "commonjs", 14 + "dependencies": { 15 + "@atproto/api": "^0.18.3", 16 + "@atproto/oauth-client-node": "^0.3.11", 17 + "better-sqlite3": "^12.5.0", 18 + "express": "^5.1.0", 19 + "iron-session": "^8.0.4" 20 + }, 21 + "devDependencies": { 22 + "@types/better-sqlite3": "^7.6.13", 23 + "@types/express": "^5.0.5", 24 + "@types/node": "^24.10.1", 25 + "tsx": "^4.20.6", 26 + "typescript": "^5.9.3" 27 + } 28 + }
+27
src/client.ts
··· 1 + import { NodeOAuthClient } from '@atproto/oauth-client-node' 2 + import { SessionStore, StateStore } from './storage' 3 + 4 + export const createClient = async () => { 5 + return new NodeOAuthClient({ 6 + // This metadata describes your OAuth client to the PDS. 7 + clientMetadata: { 8 + client_name: 'ATProto OAuth Test', 9 + // For localhost development, we use a "Loopback Client ID". 10 + // This allows us to test without a public domain or https. 11 + // In production, this should be the URL where your metadata is served (e.g., https://myapp.com/client-metadata.json). 12 + client_id: 'http://localhost?redirect_uri=http%3A%2F%2F127.0.0.1%3A3000%2Foauth%2Fcallback&scope=atproto%20repo%3Acom.anproto.message.v1%3Faction%3Dcreate', 13 + client_uri: 'http://localhost:3000', 14 + redirect_uris: ['http://127.0.0.1:3000/oauth/callback'], 15 + scope: 'atproto repo:com.anproto.message.v1?action=create', 16 + grant_types: ['authorization_code', 'refresh_token'], 17 + response_types: ['code'], 18 + application_type: 'web', 19 + token_endpoint_auth_method: 'none', 20 + // DPoP (Demonstrating Proof-of-Possession) binds tokens to a private key, preventing replay attacks if the token is stolen. 21 + // This is highly recommended for security. 22 + dpop_bound_access_tokens: true, 23 + }, 24 + stateStore: new StateStore(), 25 + sessionStore: new SessionStore(), 26 + }) 27 + }
+16
src/db.ts
··· 1 + import Database from 'better-sqlite3' 2 + 3 + // A simple SQLite database for persisting sessions and auth state. 4 + // In a production environment, you might use PostgreSQL, Redis, or another durable store. 5 + export const db = new Database('db.sqlite') 6 + 7 + db.exec(` 8 + CREATE TABLE IF NOT EXISTS auth_state ( 9 + key TEXT PRIMARY KEY, 10 + state TEXT NOT NULL 11 + ); 12 + CREATE TABLE IF NOT EXISTS auth_session ( 13 + key TEXT PRIMARY KEY, 14 + session TEXT NOT NULL 15 + ); 16 + `)
+583
src/index.ts
··· 1 + import express from 'express' 2 + import { createClient } from './client' 3 + import { getIronSession } from 'iron-session' 4 + import { Agent } from '@atproto/api' 5 + import { Lexicons, LexiconDoc, ValidationError } from '@atproto/lexicon' 6 + import path from 'path' 7 + import fs from 'fs' 8 + import { pathToFileURL } from 'url' 9 + 10 + // Types for the session data stored in the browser cookie 11 + declare module 'iron-session' { 12 + interface IronSessionData { 13 + did?: string 14 + } 15 + } 16 + 17 + const app = express() 18 + const port = 3000 19 + const messageLexiconPath = path.join(__dirname, '../lexicons/com.anproto.message.json') 20 + const messageLexicon = JSON.parse(fs.readFileSync(messageLexiconPath, 'utf8')) as LexiconDoc 21 + const lexicons = new Lexicons([messageLexicon]) 22 + const messageLexUri = 'com.anproto.message.v1' 23 + 24 + // Session configuration (cookie settings) 25 + const sessionConfig = { 26 + cookieName: 'atproto-oauth-session', 27 + password: 'complex_password_at_least_32_characters_long', // REPLACE THIS in production! 28 + cookieOptions: { 29 + secure: process.env.NODE_ENV === 'production', 30 + }, 31 + } 32 + 33 + app.use(express.json({ limit: '10mb' })) 34 + app.use(express.urlencoded({ extended: true, limit: '2mb' })) 35 + app.use('/apds', express.static('apds')) 36 + 37 + const run = async () => { 38 + const client = await createClient() 39 + 40 + app.get('/', async (req, res) => { 41 + const session = await getIronSession(req, res, sessionConfig) 42 + let profile: any = {} 43 + let avatarUrl = '' 44 + let handle = session.handle || '' 45 + 46 + if (session.did) { 47 + try { 48 + const oauthSession = await client.restore(session.did) 49 + if (!oauthSession) { 50 + throw new Error('Could not restore OAuth session') 51 + } 52 + const agent = new Agent(oauthSession) 53 + 54 + try { 55 + const prof = await agent.getProfile({ actor: session.did }) 56 + profile = prof.data 57 + handle = profile.handle ?? '' 58 + avatarUrl = profile.avatar ?? '' 59 + } catch (e) { 60 + console.warn('Could not fetch profile via getProfile, falling back to record:', e) 61 + } 62 + 63 + if (!avatarUrl || !handle) { 64 + try { 65 + const { data } = await agent.com.atproto.repo.getRecord({ 66 + repo: session.did, 67 + collection: 'app.bsky.actor.profile', 68 + rkey: 'self', 69 + }) 70 + const rec = data.value 71 + profile = { ...rec, ...(profile || {}) } 72 + if (rec?.avatar && !avatarUrl) { 73 + const serviceUrl = new URL((agent as any).service?.toString() ?? 'https://bsky.social/') 74 + const cid = rec.avatar.ref?.toString() ?? '' 75 + if (cid) { 76 + avatarUrl = new URL(`xrpc/com.atproto.sync.getBlob`, serviceUrl).toString() 77 + avatarUrl += `?did=${encodeURIComponent(session.did!)}&cid=${encodeURIComponent(cid)}` 78 + } 79 + } 80 + } catch (e) { 81 + console.warn('Could not fetch profile record or construct avatar:', e) 82 + } 83 + } 84 + } catch (err) { 85 + console.error('Error processing session:', err) 86 + session.destroy() 87 + res.send(renderError(err)) 88 + return 89 + } 90 + } 91 + 92 + res.send( 93 + renderHome({ 94 + did: session.did, 95 + handle, 96 + avatarUrl, 97 + }), 98 + ) 99 + }) 100 + 101 + app.get('/oauth-client-metadata.json', (req, res) => { 102 + res.json(client.clientMetadata) 103 + }) 104 + 105 + app.post('/login', async (req, res) => { 106 + const handle = req.body.handle 107 + if (typeof handle !== 'string' || !handle) { 108 + return res.status(400).send('Handle is required') 109 + } 110 + 111 + try { 112 + const sess = await getIronSession(req, res, sessionConfig) 113 + sess.pendingHandle = handle 114 + await sess.save() 115 + 116 + const url = await client.authorize(handle, { 117 + // Request full atproto plus explicit write scope for com.anproto.message.v1 118 + scope: 'atproto repo:com.anproto.message.v1?action=create', 119 + }) 120 + res.redirect(url.toString()) 121 + } catch (err) { 122 + console.error(err) 123 + res.status(500).send('Error initiating login: ' + err) 124 + } 125 + }) 126 + 127 + app.get('/oauth/callback', async (req, res) => { 128 + const params = new URLSearchParams(req.url.split('?')[1]) 129 + try { 130 + const { session } = await client.callback(params) 131 + 132 + const reqSession = await getIronSession(req, res, sessionConfig) 133 + reqSession.did = session.did 134 + if (!reqSession.handle && reqSession.pendingHandle) { 135 + reqSession.handle = reqSession.pendingHandle 136 + } 137 + reqSession.pendingHandle = undefined 138 + await reqSession.save() 139 + 140 + res.redirect('/') 141 + } catch (err) { 142 + console.error(err) 143 + res.status(500).send('Callback failed: ' + err) 144 + } 145 + }) 146 + 147 + app.post('/logout', async (req, res) => { 148 + const session = await getIronSession(req, res, sessionConfig) 149 + session.destroy() 150 + res.redirect('/') 151 + }) 152 + 153 + app.post('/publish', async (req, res) => { 154 + const session = await getIronSession(req, res, sessionConfig) 155 + if (!session.did) { 156 + return res.status(401).json({ error: 'Not logged in' }) 157 + } 158 + 159 + const { anmsg, blob, rkey, anhash: providedAnhash, blobhash: providedBlobhash } = req.body ?? {} 160 + if (typeof anmsg !== 'string' || !anmsg.trim()) { 161 + return res.status(400).json({ error: 'anmsg is required' }) 162 + } 163 + const maxAnmsgLen = 20000 164 + if (anmsg.length > maxAnmsgLen) { 165 + return res.status(400).json({ error: 'anmsg too large' }) 166 + } 167 + 168 + try { 169 + const oauthSession = await client.restore(session.did) 170 + if (!oauthSession) { 171 + session.destroy() 172 + return res.status(401).json({ error: 'Session expired' }) 173 + } 174 + 175 + const agent = new Agent(oauthSession) 176 + const an = await loadAn() 177 + let finalBlobhash: string | undefined 178 + let anblob: string | undefined 179 + 180 + if (blob) { 181 + if (typeof blob !== 'string') { 182 + return res.status(400).json({ error: 'blob must be base64 string' }) 183 + } 184 + const buf = Buffer.from(blob, 'base64') 185 + if (!buf.length) { 186 + return res.status(400).json({ error: 'blob decode failed' }) 187 + } 188 + const maxBlobBytes = 6 * 1024 * 1024 189 + if (buf.length > maxBlobBytes) { 190 + return res.status(400).json({ error: 'blob too large (max 6MB)' }) 191 + } 192 + anblob = blob 193 + finalBlobhash = await an.hash(blob) 194 + } else if (typeof providedBlobhash === 'string' && providedBlobhash.trim()) { 195 + finalBlobhash = providedBlobhash.trim() 196 + } 197 + 198 + const anhash = await an.hash(anmsg) 199 + if (providedAnhash && providedAnhash !== anhash) { 200 + return res.status(400).json({ error: 'anhash does not match anmsg hash' }) 201 + } 202 + const computedRkey = toBase64Url(anhash) 203 + if (typeof rkey === 'string' && rkey.trim() && rkey.trim() !== computedRkey) { 204 + return res.status(400).json({ error: 'rkey does not match anmsg hash' }) 205 + } 206 + 207 + const record = { 208 + $type: messageLexUri, 209 + anmsg, 210 + anhash, 211 + ...(anblob ? { anblob } : {}), 212 + ...(finalBlobhash ? { blobhash: finalBlobhash } : {}), 213 + } 214 + 215 + try { 216 + lexicons.assertValidRecord(messageLexUri, record) 217 + } catch (err) { 218 + const message = 219 + err instanceof ValidationError 220 + ? err.message 221 + : err instanceof Error 222 + ? err.message 223 + : 'Record failed lexicon validation' 224 + return res.status(400).json({ error: message }) 225 + } 226 + 227 + await agent.com.atproto.repo.createRecord({ 228 + repo: session.did, 229 + collection: messageLexUri, 230 + rkey: computedRkey, 231 + record, 232 + }) 233 + 234 + res.json({ ok: true, rkey: computedRkey }) 235 + } catch (err) { 236 + console.error('Publish failed:', err) 237 + res.status(500).json({ error: 'Publish failed', detail: String(err) }) 238 + } 239 + }) 240 + 241 + app.listen(port, () => { 242 + console.log(`Server running at http://localhost:${port}`) 243 + }) 244 + } 245 + 246 + function renderHome({ 247 + did, 248 + handle, 249 + avatarUrl, 250 + }: { 251 + did?: string 252 + handle?: string 253 + avatarUrl?: string 254 + }) { 255 + const loggedIn = Boolean(did) 256 + const displayHandle = 257 + handle || 258 + (did ? `${did.slice(0, 16)}…` : '') 259 + 260 + return ` 261 + <!DOCTYPE html> 262 + <html> 263 + <head> 264 + <title>ANProto over ATProto</title> 265 + <style> 266 + :root { --bg:#f7f7f8; --card:#fff; --border:#e3e3e6; --text:#111; --muted:#666; --accent:#111; } 267 + * { box-sizing: border-box; } 268 + body { font-family: "Inter", system-ui, -apple-system, sans-serif; background: var(--bg); color: var(--text); margin: 0; padding: 0; } 269 + header { display: flex; justify-content: space-between; align-items: center; padding: 14px 18px; border-bottom: 1px solid var(--border); background: var(--card); position: sticky; top: 0; } 270 + .brand { font-weight: 700; letter-spacing: -0.01em; } 271 + .auth { display: flex; align-items: center; gap: 12px; } 272 + .login-form { display: flex; align-items: center; gap: 8px; } 273 + .login-form input { padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; min-width: 200px; } 274 + .btn { cursor: pointer; border: 1px solid var(--border); background: var(--text); color: #fff; border-radius: 10px; padding: 8px 12px; font-weight: 600; } 275 + .btn.secondary { background: #fff; color: var(--text); } 276 + .user-chip { display: flex; align-items: center; gap: 10px; background: #fff; border: 1px solid var(--border); border-radius: 999px; padding: 6px 10px; } 277 + .avatar { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; border: 1px solid var(--border); background: #ddd; } 278 + main { max-width: 960px; margin: 0 auto; padding: 24px 18px 40px; display: grid; gap: 20px; } 279 + .card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 16px; } 280 + .composer textarea { width: 100%; min-height: 140px; padding: 12px; border: 1px solid var(--border); border-radius: 10px; font-size: 1rem; } 281 + .composer .actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-top: 10px; } 282 + .status { margin-top: 8px; font-size: 0.92rem; color: var(--muted); } 283 + .muted { color: var(--muted); } 284 + .feed-list { display: flex; flex-direction: column; gap: 12px; } 285 + .feed-item { border: 1px solid var(--border); border-radius: 12px; padding: 12px; background: #fff; } 286 + .feed-head { display: flex; justify-content: space-between; align-items: center; font-size: 0.9rem; color: var(--muted); } 287 + .pill { background: #f0f0f2; border: 1px solid var(--border); border-radius: 999px; padding: 4px 10px; font-size: 0.85rem; } 288 + .logout { margin-left: 8px; } 289 + .row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } 290 + </style> 291 + </head> 292 + <body> 293 + <header> 294 + <div class="brand">ANProto over ATProto</div> 295 + <div class="auth"> 296 + ${ 297 + loggedIn 298 + ? ` 299 + <div class="user-chip"> 300 + ${ 301 + avatarUrl 302 + ? `<img class="avatar" src="${avatarUrl}" alt="avatar">` 303 + : `<div class="avatar"></div>` 304 + } 305 + <div> 306 + <div style="font-weight:600">${displayHandle}</div> 307 + <div style="font-size:0.85rem" class="muted">${did}</div> 308 + </div> 309 + <form class="logout" action="/logout" method="POST"> 310 + <button class="btn secondary" type="submit">Logout</button> 311 + </form> 312 + </div> 313 + ` 314 + : ` 315 + <form class="login-form" action="/login" method="POST"> 316 + <input name="handle" type="text" placeholder="handle.bsky.social" required /> 317 + <button class="btn" type="submit">Login with Bluesky</button> 318 + </form> 319 + ` 320 + } 321 + </div> 322 + </header> 323 + <main> 324 + <section class="card composer"> 325 + <div class="row" style="justify-content: space-between; align-items: flex-start;"> 326 + <div> 327 + <h2 style="margin:0 0 6px 0;">Compose ANProto</h2> 328 + <p class="muted" style="margin:0;">Sign locally with APDS, then save to your PDS.</p> 329 + </div> 330 + <div id="pubkey-display" class="muted" style="font-size:0.9rem; text-align:right;">Loading key...</div> 331 + </div> 332 + <form id="composer-form"> 333 + <textarea id="composer-text" name="anmsg" placeholder="Write or paste your ANProto content..."></textarea> 334 + <div class="actions"> 335 + <label class="muted">Attach blob (optional): <input type="file" id="composer-blob" /></label> 336 + <button class="btn" type="submit">${loggedIn ? 'Sign & Publish' : 'Sign (login to publish)'}</button> 337 + <button class="btn secondary" type="button" id="regen-key">New keypair</button> 338 + </div> 339 + <div class="status" id="composer-status"></div> 340 + </form> 341 + </section> 342 + 343 + <section class="card feed"> 344 + <div class="feed-head"> 345 + <h3 style="margin:0;">Your ANProto feed</h3> 346 + <span class="pill">Local APDS</span> 347 + </div> 348 + <div id="feed-list" class="feed-list"> 349 + <div class="muted">Loading feed...</div> 350 + </div> 351 + </section> 352 + </main> 353 + 354 + <script type="module"> 355 + import { apds } from '/apds/apds.js' 356 + 357 + (async () => { 358 + const form = document.getElementById('composer-form') 359 + const statusEl = document.getElementById('composer-status') 360 + const fileInput = document.getElementById('composer-blob') 361 + const textArea = document.getElementById('composer-text') 362 + const pubkeyEl = document.getElementById('pubkey-display') 363 + const regenBtn = document.getElementById('regen-key') 364 + const feedList = document.getElementById('feed-list') 365 + 366 + const setStatus = (text, isError) => { 367 + if (!statusEl) return 368 + statusEl.textContent = text 369 + statusEl.style.color = isError ? 'crimson' : 'inherit' 370 + } 371 + 372 + const fileToBase64 = async (file) => { 373 + const buf = await file.arrayBuffer() 374 + let binary = '' 375 + const bytes = new Uint8Array(buf) 376 + const chunk = 0x8000 377 + for (let i = 0; i < bytes.length; i += chunk) { 378 + const slice = bytes.subarray(i, i + chunk) 379 + binary += String.fromCharCode.apply(null, Array.from(slice)) 380 + } 381 + return btoa(binary) 382 + } 383 + 384 + const ensureKeypair = async () => { 385 + let pub = await apds.pubkey() 386 + if (!pub) { 387 + const kp = await apds.generate() 388 + await apds.put('keypair', kp) 389 + pub = await apds.pubkey() 390 + } 391 + return pub 392 + } 393 + 394 + const renderPubkey = async () => { 395 + if (!pubkeyEl) return 396 + const pub = await apds.pubkey() 397 + pubkeyEl.textContent = pub ? 'ANProto pubkey: ' + pub : 'No keypair' 398 + } 399 + 400 + await apds.start('anproto-ui') 401 + await ensureKeypair() 402 + await renderPubkey() 403 + 404 + regenBtn?.addEventListener('click', async () => { 405 + const kp = await apds.generate() 406 + await apds.put('keypair', kp) 407 + await renderPubkey() 408 + setStatus('New keypair generated', false) 409 + }) 410 + 411 + form?.addEventListener('submit', async (e) => { 412 + e.preventDefault() 413 + setStatus('Signing and publishing...', false) 414 + const content = (textArea?.value || '').trim() 415 + if (!content) { 416 + setStatus('Message is required', true) 417 + return 418 + } 419 + 420 + let payload = {} 421 + let derivedBlob 422 + let derivedBlobHash 423 + 424 + try { 425 + const protocolHash = await apds.compose(content) 426 + const anmsg = await apds.get(protocolHash) 427 + if (!anmsg) { 428 + setStatus('Could not load signed message', true) 429 + return 430 + } 431 + payload.anmsg = anmsg 432 + try { 433 + const anhash = await apds.hash(anmsg) 434 + payload.anhash = anhash 435 + } catch (err) { 436 + console.warn('Could not compute anhash', err) 437 + } 438 + 439 + try { 440 + const opened = await apds.open(anmsg) 441 + const contentHash = opened?.substring(13) 442 + if (contentHash) { 443 + const body = await apds.get(contentHash) 444 + if (body) { 445 + derivedBlob = body 446 + derivedBlobHash = contentHash 447 + } 448 + } 449 + } catch (err) { 450 + console.warn('Could not derive message body from anmsg', err) 451 + } 452 + } catch (err) { 453 + setStatus('Signing failed', true) 454 + return 455 + } 456 + 457 + const file = fileInput?.files?.[0] 458 + if (file) { 459 + if (file.size > 2 * 1024 * 1024) { 460 + setStatus('Blob too large (max 2MB)', true) 461 + return 462 + } 463 + try { 464 + const b64 = await fileToBase64(file) 465 + let blobhash 466 + try { 467 + blobhash = await apds.hash(b64) 468 + } catch (err) { 469 + console.warn('Could not hash blob', err) 470 + } 471 + payload = { ...payload, blob: b64, ...(blobhash ? { blobhash } : {}) } 472 + } catch (err) { 473 + setStatus('Failed to read blob', true) 474 + return 475 + } 476 + } else if (derivedBlob) { 477 + payload = { ...payload, blob: derivedBlob, blobhash: derivedBlobHash } 478 + } 479 + 480 + try { 481 + const res = await fetch('/publish', { 482 + method: 'POST', 483 + headers: { 'Content-Type': 'application/json' }, 484 + body: JSON.stringify(payload), 485 + }) 486 + const data = await res.json() 487 + if (!res.ok) { 488 + throw new Error(data?.error || 'Publish failed') 489 + } 490 + setStatus('Saved with rkey ' + data.rkey, false) 491 + if (textArea) textArea.value = '' 492 + if (fileInput) fileInput.value = '' 493 + } catch (err) { 494 + setStatus(err?.message || 'Publish failed', true) 495 + } 496 + await refreshFeed() 497 + }) 498 + 499 + const renderFeedItems = (items) => { 500 + if (!feedList) return 501 + if (!items || !items.length) { 502 + feedList.innerHTML = '<div class="muted">No ANProto messages yet.</div>' 503 + return 504 + } 505 + const html = items 506 + .map((item) => { 507 + const author = item.author || 'unknown author' 508 + const time = item.time || '' 509 + const text = item.text || '' 510 + const hash = item.hash || '' 511 + return ( 512 + '<div class="feed-item">' + 513 + '<div class="feed-head">' + 514 + '<span class="pill">' + author + '</span>' + 515 + '<span class="muted">' + time + '</span>' + 516 + '</div>' + 517 + '<div style="margin-top:6px; white-space: pre-wrap;">' + text + '</div>' + 518 + '<div class="muted" style="font-size:0.85rem; margin-top:6px;">' + hash + '</div>' + 519 + '</div>' 520 + ) 521 + }) 522 + .join('') 523 + feedList.innerHTML = html 524 + } 525 + 526 + async function refreshFeed() { 527 + if (!feedList) return 528 + feedList.innerHTML = '<div class="muted">Loading feed...</div>' 529 + try { 530 + const entries = (await apds.query()) || [] 531 + if (!entries.length) { 532 + renderFeedItems([]) 533 + return 534 + } 535 + const mapped = await Promise.all( 536 + [...entries] 537 + .reverse() 538 + .map(async (msg) => ({ 539 + hash: msg.hash, 540 + text: msg.text || '', 541 + author: msg.author || '', 542 + time: msg.ts ? await apds.human(msg.ts) : '', 543 + })), 544 + ) 545 + renderFeedItems(mapped) 546 + } catch (err) { 547 + console.warn('Failed to render feed', err) 548 + feedList.innerHTML = '<div class="muted">Could not load feed.</div>' 549 + } 550 + } 551 + 552 + await refreshFeed() 553 + setInterval(refreshFeed, 20000) 554 + })() 555 + </script> 556 + </body> 557 + </html> 558 + ` 559 + } 560 + 561 + function renderError(err: any) { 562 + return ` 563 + <h1>Error</h1> 564 + <p>Something went wrong.</p> 565 + <pre>${err}</pre> 566 + <a href="/">Go Back</a> 567 + ` 568 + } 569 + 570 + run() 571 + 572 + let anModulePromise: Promise<{ an: { hash: (d: string) => Promise<string> } }> | null = null 573 + async function loadAn() { 574 + if (!anModulePromise) { 575 + const url = pathToFileURL(path.join(__dirname, '../apds/an.js')).href 576 + anModulePromise = import(url) 577 + } 578 + return (await anModulePromise).an 579 + } 580 + 581 + function toBase64Url(b64: string) { 582 + return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') 583 + }
+55
src/storage.ts
··· 1 + import type { 2 + NodeSavedSession, 3 + NodeSavedSessionStore, 4 + NodeSavedState, 5 + NodeSavedStateStore, 6 + } from '@atproto/oauth-client-node' 7 + import { db } from './db' 8 + 9 + /** 10 + * StateStore: 11 + * Stores temporary "state" parameters used during the initial OAuth handshake (authorize -> callback). 12 + * This prevents CSRF attacks by ensuring the callback comes from the same flow we started. 13 + * These are short-lived and can be deleted after the callback is processed. 14 + */ 15 + export class StateStore implements NodeSavedStateStore { 16 + async get(key: string): Promise<NodeSavedState | undefined> { 17 + const result = db.prepare('SELECT state FROM auth_state WHERE key = ?').get(key) as { state: string } | undefined 18 + if (!result) return 19 + return JSON.parse(result.state) as NodeSavedState 20 + } 21 + async set(key: string, val: NodeSavedState) { 22 + const state = JSON.stringify(val) 23 + db.prepare(` 24 + INSERT INTO auth_state (key, state) VALUES (?, ?) 25 + ON CONFLICT(key) DO UPDATE SET state = excluded.state 26 + `).run(key, state) 27 + } 28 + async del(key: string) { 29 + db.prepare('DELETE FROM auth_state WHERE key = ?').run(key) 30 + } 31 + } 32 + 33 + /** 34 + * SessionStore: 35 + * Persists the long-term OAuth session data (Access Token, Refresh Token, DID). 36 + * This allows the user to stay logged in even if the server restarts. 37 + * Keys are usually mapped to the user's DID. 38 + */ 39 + export class SessionStore implements NodeSavedSessionStore { 40 + async get(key: string): Promise<NodeSavedSession | undefined> { 41 + const result = db.prepare('SELECT session FROM auth_session WHERE key = ?').get(key) as { session: string } | undefined 42 + if (!result) return 43 + return JSON.parse(result.session) as NodeSavedSession 44 + } 45 + async set(key: string, val: NodeSavedSession) { 46 + const session = JSON.stringify(val) 47 + db.prepare(` 48 + INSERT INTO auth_session (key, session) VALUES (?, ?) 49 + ON CONFLICT(key) DO UPDATE SET session = excluded.session 50 + `).run(key, session) 51 + } 52 + async del(key: string) { 53 + db.prepare('DELETE FROM auth_session WHERE key = ?').run(key) 54 + } 55 + }
+9
src/types/iron-session.d.ts
··· 1 + import 'iron-session' 2 + 3 + declare module 'iron-session' { 4 + interface IronSessionData { 5 + did?: string 6 + handle?: string 7 + pendingHandle?: string 8 + } 9 + }
+12
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2020", 4 + "module": "commonjs", 5 + "strict": true, 6 + "esModuleInterop": true, 7 + "skipLibCheck": true, 8 + "forceConsistentCasingInFileNames": true, 9 + "outDir": "./dist" 10 + }, 11 + "include": ["src"] 12 + }