A Deno-compatible AT Protocol OAuth client that serves as a drop-in replacement for @atproto/oauth-client-node
0
fork

Configure Feed

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

docs: add comprehensive JSDoc documentation to improve JSR score

- Add detailed JSDoc comments to all error classes with examples
- Enhance resolver class documentation with usage patterns
- Improve client method documentation with comprehensive examples
- Add JSDoc to all exported functions with parameter descriptions
- Follow JSR documentation best practices for symbol documentation
- Improve developer experience with rich IDE support

This should significantly improve the JSR documentation score by providing
comprehensive documentation for all public symbols.

+443 -19
+17
CHANGELOG.md
··· 5 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 7 8 + ## [1.0.1] - 2025-09-05 9 + 10 + ### Added 11 + 12 + - **Comprehensive JSDoc Documentation**: Added detailed JSDoc comments to all public symbols including: 13 + - Complete error class documentation with examples and use cases 14 + - Enhanced resolver class documentation with usage patterns 15 + - Detailed function documentation for all exported utilities 16 + - Improved client method documentation with comprehensive examples 17 + - **JSR Documentation Compliance**: Updated all documentation to follow JSR best practices for symbol documentation 18 + 19 + ### Improved 20 + 21 + - **Developer Experience**: All public APIs now have rich documentation with examples 22 + - **IDE Support**: Enhanced IntelliSense and auto-completion with detailed parameter descriptions 23 + - **Error Handling**: Clear documentation for all error types and when they are thrown 24 + 8 25 ## [1.0.0] - 2025-08-31 9 26 10 27 ### Changed
+1 -1
deno.json
··· 1 1 { 2 2 "name": "@tijs/oauth-client-deno", 3 - "version": "1.0.0", 3 + "version": "1.0.1", 4 4 "description": "AT Protocol OAuth client for Deno - handle-focused alternative to @atproto/oauth-client-node with Web Crypto API compatibility", 5 5 "license": "MIT", 6 6 "repository": {
+44 -9
src/client.ts
··· 272 272 } 273 273 274 274 /** 275 - * Restore session from storage 276 - * @param sessionId Session identifier 277 - * @returns Session if found and valid, null otherwise 275 + * Restore an authenticated session from storage. 276 + * 277 + * Retrieves a previously stored session by its ID and automatically refreshes 278 + * the access token if it has expired. Returns null if the session doesn't exist 279 + * or cannot be restored. 280 + * 281 + * @param sessionId - Unique identifier for the stored session 282 + * @returns Promise resolving to restored session, or null if not found 283 + * @example 284 + * ```ts 285 + * const session = await client.restore("user-session-123"); 286 + * if (session) { 287 + * console.log("Welcome back,", session.handle); 288 + * } else { 289 + * console.log("Please log in again"); 290 + * } 291 + * ``` 278 292 */ 279 293 async restore(sessionId: string): Promise<Session | null> { 280 294 try { ··· 299 313 } 300 314 301 315 /** 302 - * Store session in storage 303 - * @param sessionId Session identifier 304 - * @param session Session to store 316 + * Store an authenticated session in storage for later retrieval. 317 + * 318 + * Persists the session data using the configured storage backend so it can 319 + * be restored later with {@link restore}. The session is serialized before 320 + * storage. 321 + * 322 + * @param sessionId - Unique identifier for the session 323 + * @param session - Authenticated session to store 324 + * @example 325 + * ```ts 326 + * const { session } = await client.callback(params); 327 + * await client.store("user-session-123", session); 328 + * console.log("Session stored successfully"); 329 + * ``` 305 330 */ 306 331 async store(sessionId: string, session: Session): Promise<void> { 307 332 await this.storage.set(`session:${sessionId}`, session.toJSON()); ··· 345 370 } 346 371 347 372 /** 348 - * Revoke session tokens and clean up 349 - * @param sessionId Session identifier 350 - * @param session Session to revoke 373 + * Sign out a user session by revoking tokens and cleaning up storage. 374 + * 375 + * Attempts to revoke the refresh token at the OAuth server (best effort) 376 + * and removes the session from local storage. This ensures proper cleanup 377 + * and prevents token reuse. 378 + * 379 + * @param sessionId - Session identifier to remove from storage 380 + * @param session - Session containing tokens to revoke 381 + * @example 382 + * ```ts 383 + * await client.signOut("user-session-123", session); 384 + * console.log("User signed out successfully"); 385 + * ``` 351 386 */ 352 387 async signOut(sessionId: string, session: Session): Promise<void> { 353 388 try {
+243 -1
src/errors.ts
··· 1 1 /** 2 - * Custom error classes for OAuth client 2 + * @fileoverview Custom error classes for OAuth client operations 3 + * @module 3 4 */ 4 5 6 + /** 7 + * Base OAuth error class for all OAuth-related errors. 8 + * 9 + * Provides a common base class for all OAuth client errors with optional 10 + * error chaining support. All other OAuth errors extend from this class. 11 + * 12 + * @example 13 + * ```ts 14 + * try { 15 + * await client.authorize("invalid-handle"); 16 + * } catch (error) { 17 + * if (error instanceof OAuthError) { 18 + * console.log("OAuth operation failed:", error.message); 19 + * if (error.cause) { 20 + * console.log("Underlying cause:", error.cause.message); 21 + * } 22 + * } 23 + * } 24 + * ``` 25 + */ 5 26 export class OAuthError extends Error { 27 + /** Optional underlying error that caused this OAuth error */ 6 28 public readonly cause?: Error; 7 29 30 + /** 31 + * Create a new OAuth error. 32 + * 33 + * @param message - Error message describing what went wrong 34 + * @param cause - Optional underlying error that caused this OAuth error 35 + */ 8 36 constructor(message: string, cause?: Error) { 9 37 super(message); 10 38 this.name = "OAuthError"; ··· 14 42 } 15 43 } 16 44 45 + /** 46 + * Thrown when an AT Protocol handle has invalid format. 47 + * 48 + * AT Protocol handles must follow specific formatting rules. This error 49 + * is thrown when a handle doesn't match the expected format. 50 + * 51 + * @example 52 + * ```ts 53 + * try { 54 + * await client.authorize("invalid-handle-format!!!"); 55 + * } catch (error) { 56 + * if (error instanceof InvalidHandleError) { 57 + * console.log("Please provide a valid handle like 'alice.bsky.social'"); 58 + * } 59 + * } 60 + * ``` 61 + */ 17 62 export class InvalidHandleError extends OAuthError { 63 + /** 64 + * Create a new invalid handle error. 65 + * 66 + * @param handle - The invalid handle that was provided 67 + */ 18 68 constructor(handle: string) { 19 69 super(`Invalid AT Protocol handle: ${handle}`); 20 70 this.name = "InvalidHandleError"; 21 71 } 22 72 } 23 73 74 + /** 75 + * Thrown when a handle cannot be resolved to a DID and PDS URL. 76 + * 77 + * This error occurs during the handle resolution process when the handle 78 + * cannot be found in the AT Protocol directory or when the resolution 79 + * service is unavailable. 80 + * 81 + * @example 82 + * ```ts 83 + * try { 84 + * await client.authorize("nonexistent.handle.social"); 85 + * } catch (error) { 86 + * if (error instanceof HandleResolutionError) { 87 + * console.log("Handle not found or resolution service unavailable"); 88 + * } 89 + * } 90 + * ``` 91 + */ 24 92 export class HandleResolutionError extends OAuthError { 93 + /** 94 + * Create a new handle resolution error. 95 + * 96 + * @param handle - The handle that failed to resolve 97 + * @param cause - Optional underlying error that caused the resolution failure 98 + */ 25 99 constructor(handle: string, cause?: Error) { 26 100 super(`Failed to resolve handle ${handle} to DID and PDS`, cause); 27 101 this.name = "HandleResolutionError"; 28 102 } 29 103 } 30 104 105 + /** 106 + * Thrown when OAuth endpoints cannot be discovered from a PDS. 107 + * 108 + * This error occurs when the PDS doesn't expose the required OAuth 109 + * configuration endpoints or when the endpoints are malformed. 110 + * 111 + * @example 112 + * ```ts 113 + * try { 114 + * await client.authorize("user.custom-pds.com"); 115 + * } catch (error) { 116 + * if (error instanceof PDSDiscoveryError) { 117 + * console.log("PDS doesn't support OAuth or endpoints are unavailable"); 118 + * } 119 + * } 120 + * ``` 121 + */ 31 122 export class PDSDiscoveryError extends OAuthError { 123 + /** 124 + * Create a new PDS discovery error. 125 + * 126 + * @param pdsUrl - The PDS URL where discovery failed 127 + * @param cause - Optional underlying error that caused the discovery failure 128 + */ 32 129 constructor(pdsUrl: string, cause?: Error) { 33 130 super(`Failed to discover OAuth endpoints for PDS: ${pdsUrl}`, cause); 34 131 this.name = "PDSDiscoveryError"; 35 132 } 36 133 } 37 134 135 + /** 136 + * Thrown when the authentication server cannot be discovered from a PDS. 137 + * 138 + * This error typically occurs with custom domain setups where the OAuth 139 + * authorization server is separate from the PDS. It indicates that the 140 + * authentication server URL couldn't be determined from the PDS configuration. 141 + * 142 + * @example 143 + * ```ts 144 + * try { 145 + * await client.authorize("user.custom-domain.com"); 146 + * } catch (error) { 147 + * if (error instanceof AuthServerDiscoveryError) { 148 + * console.log("Custom domain OAuth setup may be misconfigured"); 149 + * } 150 + * } 151 + * ``` 152 + */ 38 153 export class AuthServerDiscoveryError extends OAuthError { 154 + /** 155 + * Create a new auth server discovery error. 156 + * 157 + * @param pdsUrl - The PDS URL where auth server discovery failed 158 + * @param cause - Optional underlying error that caused the discovery failure 159 + */ 39 160 constructor(pdsUrl: string, cause?: Error) { 40 161 super( 41 162 `Failed to discover authentication server from PDS: ${pdsUrl}. This may be a custom domain setup issue.`, ··· 45 166 } 46 167 } 47 168 169 + /** 170 + * Thrown when OAuth token exchange operations fail. 171 + * 172 + * This error occurs during authorization code exchange or token refresh 173 + * operations when the OAuth server rejects the request or returns an error. 174 + * 175 + * @example 176 + * ```ts 177 + * try { 178 + * const { session } = await client.callback(params); 179 + * } catch (error) { 180 + * if (error instanceof TokenExchangeError) { 181 + * console.log("Token exchange failed:", error.message); 182 + * if (error.errorCode) { 183 + * console.log("OAuth error code:", error.errorCode); 184 + * } 185 + * } 186 + * } 187 + * ``` 188 + */ 48 189 export class TokenExchangeError extends OAuthError { 190 + /** OAuth error code from the server (e.g., "invalid_grant") */ 49 191 public readonly errorCode?: string; 50 192 193 + /** 194 + * Create a new token exchange error. 195 + * 196 + * @param message - Error message describing what went wrong 197 + * @param errorCode - Optional OAuth error code from the server 198 + * @param cause - Optional underlying error that caused the token exchange failure 199 + */ 51 200 constructor(message: string, errorCode?: string, cause?: Error) { 52 201 super(`Token exchange failed: ${message}`, cause); 53 202 this.name = "TokenExchangeError"; ··· 57 206 } 58 207 } 59 208 209 + /** 210 + * Thrown when DPoP (Demonstration of Proof-of-Possession) operations fail. 211 + * 212 + * DPoP is used for secure token binding in OAuth flows. This error occurs 213 + * when DPoP key generation, proof creation, or validation fails. 214 + * 215 + * @example 216 + * ```ts 217 + * try { 218 + * await session.makeRequest("GET", "/xrpc/endpoint"); 219 + * } catch (error) { 220 + * if (error instanceof DPoPError) { 221 + * console.log("DPoP authentication failed:", error.message); 222 + * } 223 + * } 224 + * ``` 225 + */ 60 226 export class DPoPError extends OAuthError { 227 + /** 228 + * Create a new DPoP error. 229 + * 230 + * @param message - Error message describing the DPoP operation failure 231 + * @param cause - Optional underlying error that caused the DPoP failure 232 + */ 61 233 constructor(message: string, cause?: Error) { 62 234 super(`DPoP operation failed: ${message}`, cause); 63 235 this.name = "DPoPError"; 64 236 } 65 237 } 66 238 239 + /** 240 + * Thrown when session operations encounter errors. 241 + * 242 + * This error occurs during session management operations like token refresh, 243 + * request signing, or session restoration when the session state is invalid 244 + * or operations fail. 245 + * 246 + * @example 247 + * ```ts 248 + * try { 249 + * const session = await client.restore("session-id"); 250 + * await session.makeRequest("GET", "/api/endpoint"); 251 + * } catch (error) { 252 + * if (error instanceof SessionError) { 253 + * console.log("Session operation failed:", error.message); 254 + * } 255 + * } 256 + * ``` 257 + */ 67 258 export class SessionError extends OAuthError { 259 + /** 260 + * Create a new session error. 261 + * 262 + * @param message - Error message describing the session operation failure 263 + * @param cause - Optional underlying error that caused the session failure 264 + */ 68 265 constructor(message: string, cause?: Error) { 69 266 super(`Session error: ${message}`, cause); 70 267 this.name = "SessionError"; 71 268 } 72 269 } 73 270 271 + /** 272 + * Thrown when the OAuth state parameter is invalid or expired. 273 + * 274 + * The state parameter is used for CSRF protection in OAuth flows. This error 275 + * occurs when the state parameter in the callback doesn't match the expected 276 + * value or has expired. 277 + * 278 + * @example 279 + * ```ts 280 + * try { 281 + * const { session } = await client.callback(params); 282 + * } catch (error) { 283 + * if (error instanceof InvalidStateError) { 284 + * console.log("OAuth state validation failed - possible CSRF attack"); 285 + * } 286 + * } 287 + * ``` 288 + */ 74 289 export class InvalidStateError extends OAuthError { 290 + /** 291 + * Create a new invalid state error. 292 + */ 75 293 constructor() { 76 294 super("Invalid or expired OAuth state parameter"); 77 295 this.name = "InvalidStateError"; 78 296 } 79 297 } 80 298 299 + /** 300 + * Thrown when OAuth authorization fails at the authorization server. 301 + * 302 + * This error occurs when the authorization server returns an error during 303 + * the OAuth flow, typically due to user denial, invalid client configuration, 304 + * or server-side issues. 305 + * 306 + * @example 307 + * ```ts 308 + * try { 309 + * const { session } = await client.callback(params); 310 + * } catch (error) { 311 + * if (error instanceof AuthorizationError) { 312 + * console.log("Authorization was denied or failed:", error.message); 313 + * } 314 + * } 315 + * ``` 316 + */ 81 317 export class AuthorizationError extends OAuthError { 318 + /** 319 + * Create a new authorization error. 320 + * 321 + * @param error - OAuth error code from the authorization server 322 + * @param description - Optional human-readable error description 323 + */ 82 324 constructor(error: string, description?: string) { 83 325 super(`Authorization failed: ${error}${description ? ` - ${description}` : ""}`); 84 326 this.name = "AuthorizationError";
+138 -8
src/resolvers.ts
··· 1 1 /** 2 - * Default and configurable resolvers for AT Protocol handle resolution and PDS discovery 2 + * @fileoverview Handle resolution and PDS discovery implementations for AT Protocol 3 + * @module 3 4 */ 4 5 5 6 import { AuthServerDiscoveryError, HandleResolutionError, PDSDiscoveryError } from "./errors.ts"; 6 7 import type { HandleResolver } from "./types.ts"; 7 8 8 9 /** 9 - * Default Slingshot-based handle resolver 10 + * Slingshot-based handle resolver for AT Protocol. 11 + * 12 + * Uses the Slingshot service to resolve handles to DID and PDS URLs. Slingshot 13 + * provides fast handle resolution with fallback to standard AT Protocol methods. 14 + * This is the default resolver used by the OAuth client. 15 + * 16 + * @example 17 + * ```ts 18 + * const resolver = new SlingshotResolver("https://custom-slingshot.com"); 19 + * const { did, pdsUrl } = await resolver.resolve("alice.bsky.social"); 20 + * console.log(`DID: ${did}, PDS: ${pdsUrl}`); 21 + * ``` 10 22 */ 11 23 export class SlingshotResolver implements HandleResolver { 24 + /** 25 + * Create a new Slingshot resolver. 26 + * 27 + * @param slingshotUrl - Custom Slingshot service URL (defaults to official instance) 28 + */ 12 29 constructor(private slingshotUrl: string = "https://slingshot.microcosm.blue") {} 13 30 31 + /** 32 + * Resolve an AT Protocol handle to DID and PDS URL. 33 + * 34 + * Uses Slingshot's fast resolution service with fallback to standard AT Protocol 35 + * methods if Slingshot is unavailable. 36 + * 37 + * @param handle - AT Protocol handle to resolve (e.g., "alice.bsky.social") 38 + * @returns Promise resolving to DID and PDS URL 39 + * @throws {HandleResolutionError} When handle cannot be resolved 40 + */ 14 41 async resolve(handle: string): Promise<{ did: string; pdsUrl: string }> { 15 42 try { 16 43 return await this.resolveHandleWithSlingshot(handle); ··· 148 175 } 149 176 150 177 /** 151 - * Bluesky API-first handle resolver (alternative to Slingshot) 178 + * AT Protocol Directory handle resolver using Bluesky's API. 179 + * 180 + * Uses the official Bluesky API to resolve handles to DID and then looks up 181 + * the PDS URL from the DID document. This is an alternative to Slingshot 182 + * that uses standard AT Protocol methods. 183 + * 184 + * @example 185 + * ```ts 186 + * const resolver = new DirectoryResolver(); 187 + * const { did, pdsUrl } = await resolver.resolve("alice.bsky.social"); 188 + * console.log(`Resolved via Directory: ${did} -> ${pdsUrl}`); 189 + * ``` 152 190 */ 153 191 export class DirectoryResolver implements HandleResolver { 192 + /** 193 + * Resolve an AT Protocol handle to DID and PDS URL using Bluesky API. 194 + * 195 + * @param handle - AT Protocol handle to resolve (e.g., "alice.bsky.social") 196 + * @returns Promise resolving to DID and PDS URL 197 + * @throws {HandleResolutionError} When handle cannot be resolved 198 + */ 154 199 async resolve(handle: string): Promise<{ did: string; pdsUrl: string }> { 155 200 const response = await fetch( 156 201 `https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=${ ··· 180 225 } 181 226 182 227 /** 183 - * Custom resolver that allows complete control over handle resolution 228 + * Custom handle resolver with user-provided resolution function. 229 + * 230 + * Allows complete control over handle resolution by providing a custom 231 + * function. Useful for implementing custom resolution logic, testing, 232 + * or integrating with alternative handle resolution services. 233 + * 234 + * @example 235 + * ```ts 236 + * const customResolver = new CustomResolver(async (handle) => { 237 + * // Custom resolution logic 238 + * const did = await myCustomHandleService.resolve(handle); 239 + * const pdsUrl = await myCustomPdsService.getPds(did); 240 + * return { did, pdsUrl }; 241 + * }); 242 + * 243 + * const client = new OAuthClient({ 244 + * // ... other config 245 + * handleResolver: customResolver, 246 + * }); 247 + * ``` 184 248 */ 185 249 export class CustomResolver implements HandleResolver { 250 + /** 251 + * Create a custom resolver with provided resolution function. 252 + * 253 + * @param resolverFunction - Function that resolves handles to DID and PDS URL 254 + */ 186 255 constructor( 187 256 private resolverFunction: (handle: string) => Promise<{ did: string; pdsUrl: string }>, 188 257 ) {} 189 258 259 + /** 260 + * Resolve handle using the provided custom function. 261 + * 262 + * @param handle - AT Protocol handle to resolve 263 + * @returns Promise resolving to DID and PDS URL 264 + * @throws {HandleResolutionError} When custom resolver function fails 265 + */ 190 266 async resolve(handle: string): Promise<{ did: string; pdsUrl: string }> { 191 267 try { 192 268 return await this.resolverFunction(handle); ··· 197 273 } 198 274 199 275 /** 200 - * Create default handle resolver with optional custom Slingshot URL 276 + * Create the default handle resolver with optional custom Slingshot URL. 277 + * 278 + * Returns a {@link SlingshotResolver} configured with the specified Slingshot 279 + * service URL. This is the recommended resolver for most applications. 280 + * 281 + * @param slingshotUrl - Optional custom Slingshot service URL 282 + * @returns Configured Slingshot resolver instance 283 + * @example 284 + * ```ts 285 + * // Use default Slingshot instance 286 + * const resolver = createDefaultResolver(); 287 + * 288 + * // Use custom Slingshot instance 289 + * const customResolver = createDefaultResolver("https://my-slingshot.example.com"); 290 + * ``` 201 291 */ 202 292 export function createDefaultResolver(slingshotUrl?: string): HandleResolver { 203 293 return new SlingshotResolver(slingshotUrl); ··· 243 333 } 244 334 245 335 /** 246 - * Discover authentication server from PDS metadata 336 + * Discover OAuth authentication server URL from PDS metadata. 337 + * 338 + * Fetches the OAuth protected resource metadata from the PDS to determine 339 + * the authentication server URL. This is used for custom domain setups 340 + * where the OAuth server may be separate from the PDS. 341 + * 342 + * @param pdsUrl - The PDS URL to query for OAuth metadata 343 + * @returns Promise resolving to the authentication server URL 344 + * @throws {AuthServerDiscoveryError} When authentication server cannot be discovered 345 + * @example 346 + * ```ts 347 + * const authServer = await discoverAuthenticationServer("https://custom-pds.example.com"); 348 + * console.log("Auth server:", authServer); // "https://oauth.example.com" 349 + * ``` 247 350 */ 248 351 export async function discoverAuthenticationServer( 249 352 pdsUrl: string, ··· 274 377 } 275 378 276 379 /** 277 - * Discover OAuth endpoints from an authentication server 380 + * Discover OAuth endpoints from an authentication server. 381 + * 382 + * Fetches the OAuth authorization server metadata to get the endpoints 383 + * needed for the OAuth flow (authorization, token, and optionally revocation). 384 + * 385 + * @param authServerUrl - The authentication server URL 386 + * @returns Promise resolving to OAuth endpoints 387 + * @throws {PDSDiscoveryError} When OAuth endpoints cannot be discovered 388 + * @example 389 + * ```ts 390 + * const endpoints = await discoverOAuthEndpointsFromAuthServer("https://oauth.example.com"); 391 + * console.log("Auth endpoint:", endpoints.authorizationEndpoint); 392 + * console.log("Token endpoint:", endpoints.tokenEndpoint); 393 + * ``` 278 394 */ 279 395 export async function discoverOAuthEndpointsFromAuthServer( 280 396 authServerUrl: string, ··· 309 425 } 310 426 311 427 /** 312 - * Discover OAuth endpoints from a PDS (complete flow) 428 + * Discover OAuth endpoints from a PDS (complete discovery flow). 429 + * 430 + * Performs the complete OAuth endpoint discovery process by first discovering 431 + * the authentication server from the PDS, then fetching the OAuth endpoints 432 + * from that server. This is the main function used during OAuth authorization. 433 + * 434 + * @param pdsUrl - The PDS URL to discover OAuth endpoints for 435 + * @returns Promise resolving to OAuth endpoints 436 + * @throws {PDSDiscoveryError} When OAuth endpoints cannot be discovered 437 + * @example 438 + * ```ts 439 + * const endpoints = await discoverOAuthEndpointsFromPDS("https://bsky.social"); 440 + * console.log("Authorization URL:", endpoints.authorizationEndpoint); 441 + * console.log("Token endpoint:", endpoints.tokenEndpoint); 442 + * ``` 313 443 */ 314 444 export async function discoverOAuthEndpointsFromPDS( 315 445 pdsUrl: string,