A lightweight, serverless web client for managing files on an AT Protocol Personal Data Server (PDS).
Live Demo: https://pins-atproto.fusionstrings.workers.dev/
- Frontend: Vanilla Web Components (Shadow DOM), CSS Variables (Linear-style theme).
- Auth:
@atproto/oauth-client-browser(PKCE + DPoP). - Backend: Cloudflare Workers (serves static assets + dynamic OAuth metadata).
- Storage: IndexedDB (session persistence).
-
Install dependencies:
npm install
-
Start dev server:
npx wrangler dev
-
Open App: Navigate to
http://127.0.0.1:8787Note: Must use
127.0.0.1, notlocalhost, for the OAuth loopback client to function.
Deploy to Cloudflare Workers:
npx wrangler deployThe application is origin-agnostic. It automatically detects the deployment domain and serves the correct OAuth metadata.
This project solves the "static site OAuth" problem using a Cloudflare Worker to serve dynamic client metadata.
AT Protocol OAuth requires a client_id URL that returns JSON metadata containing the exact redirect_uris. Hardcoding this prevents deploying to multiple environments (preview, staging, prod) without rebuilding.
- Worker (
worker.js): Intercepts requests to/client-metadata.json. - Dynamic Generation: Constructs the metadata JSON using the request's
origin. - Client (
oauth-service.js):- Localhost: Uses the special "loopback" client mode (no metadata URL needed).
- Production: Sets
client_idto${origin}/client-metadata.json.
├── worker.js # CF Worker: Serves assets & dynamic metadata
├── wrangler.jsonc # CF Configuration
├── public/
│ ├── index.html # Entry point
│ ├── components/ # Web Components (blob-list, upload-zone, etc.)
│ └── js/
│ ├── oauth-service.js # Auth logic (Loopback vs Prod switching)
│ ├── pin-service.js # PDS Blob interaction
│ └── store.js # Reactive state store
oauth-service.js: Handles the dual-mode initialization (Loopback for dev, Metadata URL for prod).pin-service.js: Manages blob operations (upload, delete, list) using the authenticated agent.store.js: A simple event-target based state management system.