@@ -38,19 +38,28 @@ import {
3838 type StoredSession ,
3939 type StoredState ,
4040} from "@atcute/oauth-node-client" ;
41- import type { Kysely } from "kysely" ;
4241
4342import { createDbStore } from "./db-store.js" ;
4443
4544type Did = `did:${string } :${string } `;
4645
46+ interface StorageCollectionLike < T = unknown > {
47+ get ( id : string ) : Promise < T | null > ;
48+ put ( id : string , data : T ) : Promise < void > ;
49+ delete ( id : string ) : Promise < boolean > ;
50+ deleteMany ( ids : string [ ] ) : Promise < number > ;
51+ query ( options ?: { limit ?: number } ) : Promise < { items : Array < { id : string ; data : T } > } > ;
52+ }
53+
54+ type AuthProviderStorageMap = Record < string , StorageCollectionLike > ;
55+
4756// Singleton OAuthClient instance (lazily created).
48- // On Workers, the db binding changes per request, so we store a mutable
57+ // On Workers, the storage binding changes per request, so we store a mutable
4958// reference that DB-backed stores read via a getter.
5059let _client : OAuthClient | null = null ;
5160let _clientBaseUrl : string | null = null ;
52- let _currentDb : Kysely < unknown > | null = null ;
53- let _clientHasDb = false ;
61+ let _currentStorage : AuthProviderStorageMap | null = null ;
62+ let _clientHasStorage = false ;
5463
5564function isLoopback ( url : string ) : boolean {
5665 try {
@@ -74,28 +83,28 @@ function isLoopback(url: string): boolean {
7483 * - Production (HTTPS): PDS fetches the client metadata document to verify
7584 * the client. No JWKS or key management needed.
7685 *
77- * @param db - Database instance for persistent OAuth state/session storage .
78- * Required for multi-instance deployments (e.g., Workers) .
79- * Pass `null` to use in-memory storage (dev only).
86+ * @param baseUrl - The site's public URL .
87+ * @param storage - Auth provider storage collections from `getAuthProviderStorage()` .
88+ * Pass `null` to use in-memory storage (dev only).
8089 */
8190export async function getAtprotoOAuthClient (
8291 baseUrl : string ,
83- db ?: Kysely < unknown > | null ,
92+ storage ?: AuthProviderStorageMap | null ,
8493) : Promise < OAuthClient > {
8594 // Normalize localhost ↔ 127.0.0.1 so the singleton survives the OAuth
8695 // round-trip (authorize uses localhost, callback arrives on 127.0.0.1).
8796 if ( isLoopback ( baseUrl ) ) {
8897 baseUrl = baseUrl . replace ( "://localhost" , "://127.0.0.1" ) ;
8998 }
9099
91- // Update the mutable db reference so cached DB-backed stores use
100+ // Update the mutable storage reference so cached DB-backed stores use
92101 // the current request's binding (critical on Workers).
93- if ( db ) _currentDb = db ;
102+ if ( storage ) _currentStorage = storage ;
94103
95104 // Return cached client if baseUrl matches and store backend hasn't upgraded.
96- // If the cached client uses MemoryStore but a db is now available, recreate
105+ // If the cached client uses MemoryStore but storage is now available, recreate
97106 // with DB-backed stores so state survives across Workers requests.
98- if ( _client && _clientBaseUrl === baseUrl && ( ! db || _clientHasDb ) ) {
107+ if ( _client && _clientBaseUrl === baseUrl && ( ! storage || _clientHasStorage ) ) {
99108 return _client ;
100109 }
101110
@@ -114,15 +123,26 @@ export async function getAtprotoOAuthClient(
114123 } ) ,
115124 } ) ;
116125
117- // Use database-backed stores when a db is provided (required for
118- // multi-instance deployments like Cloudflare Workers where in-memory
119- // state doesn't survive across requests). Fall back to MemoryStore
120- // for local dev where the singleton process persists.
121- const getDb = ( ) => _currentDb ! ;
122- const stores = db
126+ // Use plugin storage when available (required for multi-instance deployments
127+ // like Cloudflare Workers where in-memory state doesn't survive across
128+ // requests). Fall back to MemoryStore for local dev where the singleton
129+ // process persists.
130+ const stores = storage
123131 ? {
124- sessions : createDbStore < Did , StoredSession > ( getDb , "sessions" ) ,
125- states : createDbStore < string , StoredState > ( getDb , "states" ) ,
132+ sessions : createDbStore < Did , StoredSession > (
133+ ( ) =>
134+ _currentStorage ! . sessions as StorageCollectionLike < {
135+ value : StoredSession ;
136+ expiresAt : number | null ;
137+ } > ,
138+ ) ,
139+ states : createDbStore < string , StoredState > (
140+ ( ) =>
141+ _currentStorage ! . states as StorageCollectionLike < {
142+ value : StoredState ;
143+ expiresAt : number | null ;
144+ } > ,
145+ ) ,
126146 }
127147 : {
128148 sessions : new MemoryStore < Did , StoredSession > ( ) ,
@@ -135,7 +155,7 @@ export async function getAtprotoOAuthClient(
135155 // Loopback public client for local development.
136156 // AT Protocol spec allows loopback IPs with public clients.
137157 // No client metadata endpoints needed — the PDS derives
138- // metadata from the client_id URL parameters.
158+ // metadata from the client_id URL parameters per RFC 8252 .
139159 // baseUrl is already normalized to 127.0.0.1 above (RFC 8252).
140160 client = new OAuthClient ( {
141161 metadata : {
@@ -162,7 +182,7 @@ export async function getAtprotoOAuthClient(
162182
163183 _client = client ;
164184 _clientBaseUrl = baseUrl ;
165- _clientHasDb = ! ! db ;
185+ _clientHasStorage = ! ! storage ;
166186
167187 return client ;
168188}
0 commit comments