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

Refactor dependencies to use import map in deno.json

- Move all external dependencies to imports section in deno.json
- Update all import statements to use import map aliases
- Regenerate deno.lock with new dependency configuration
- Improves dependency management for JSR package

+28 -6
+2
CHANGELOG.md
··· 40 40 Applications using `restore()` must now handle errors instead of checking for `null`: 41 41 42 42 **Before (v2.x):** 43 + 43 44 ```typescript 44 45 const session = await client.restore("session-id"); 45 46 if (!session) { ··· 49 50 ``` 50 51 51 52 **After (v3.x):** 53 + 52 54 ```typescript 53 55 try { 54 56 const session = await client.restore("session-id");
+8
CLAUDE.md
··· 7 7 This is a Deno-native AT Protocol OAuth client library that provides handle-focused authentication using Web Crypto API. It's an alternative to `@atproto/oauth-client-node` built specifically to solve Node.js crypto compatibility issues in Deno environments. 8 8 9 9 **Key Design Philosophy:** 10 + 10 11 - Handle-focused (accepts `alice.bsky.social` only, not DIDs or URLs) 11 12 - Uses Slingshot resolver by default with multiple fallbacks 12 13 - Web Crypto API for cross-platform compatibility (no Node.js crypto) ··· 78 79 ### Critical Implementation Details 79 80 80 81 **Web Crypto API Usage:** 82 + 81 83 - MUST use `crypto.subtle.generateKey()` with `{ name: "ECDSA", namedCurve: "P-256" }` 82 84 - NEVER use Node.js crypto APIs - they don't work in Deno 83 85 - Import jose from `jsr:@panva/jose` NOT `npm:jose` 84 86 85 87 **DPoP Authentication:** 88 + 86 89 - Every token request requires DPoP proof with ES256 signature 87 90 - Access token hash (`ath` claim) required for authenticated requests 88 91 - Servers may respond with 400/401 + `DPoP-Nonce` header requiring retry 89 92 90 93 **Concurrency Safety:** 94 + 91 95 - `OAuthClient.restore()` uses `refreshLocks` Map to prevent duplicate refresh requests 92 96 - Multiple concurrent restore calls for same session wait on single refresh operation 93 97 94 98 **Token Refresh:** 99 + 95 100 - Sessions considered expired if token expires within 5 minutes 96 101 - Refresh may or may not rotate refresh token (server-dependent) 97 102 - Refresh failures throw typed errors: `RefreshTokenExpiredError`, `NetworkError`, etc. ··· 121 126 ## Common Tasks 122 127 123 128 **Adding a new error type:** 129 + 124 130 1. Create class extending appropriate base in `src/errors.ts` 125 131 2. Add JSDoc with `@example` showing usage 126 132 3. Export from `mod.ts` 127 133 4. Add test case in `tests/errors_test.ts` 128 134 129 135 **Adding a new storage backend:** 136 + 130 137 1. Implement `OAuthStorage` interface in `src/storage.ts` 131 138 2. Handle TTL and expiration logic 132 139 3. Export from `mod.ts` 133 140 4. Add test coverage in `tests/storage_test.ts` 134 141 135 142 **Modifying OAuth flow:** 143 + 136 144 - Changes to `client.ts` likely require testing against real AT Protocol servers 137 145 - Ensure PKCE data cleanup happens even on errors 138 146 - Maintain per-session refresh lock semantics
+5
deno.json
··· 17 17 "web-crypto" 18 18 ], 19 19 "exports": "./mod.ts", 20 + "imports": { 21 + "@atproto/syntax": "npm:@atproto/syntax@0.4.0", 22 + "@panva/jose": "jsr:@panva/jose@^6.1.0", 23 + "@std/assert": "jsr:@std/assert@1" 24 + }, 20 25 "publish": { 21 26 "exclude": [ 22 27 ".github/",
+7
deno.lock
··· 25 25 "@atproto/syntax@0.4.0": { 26 26 "integrity": "sha512-b9y5ceHS8YKOfP3mdKmwAx5yVj9294UN7FG2XzP6V5aKUdFazEYRnR9m5n5ZQFKa3GNvz7de9guZCJ/sUTcOAA==" 27 27 } 28 + }, 29 + "workspace": { 30 + "dependencies": [ 31 + "jsr:@panva/jose@^6.1.0", 32 + "jsr:@std/assert@1", 33 + "npm:@atproto/syntax@0.4.0" 34 + ] 28 35 } 29 36 }
+1 -1
src/client.ts
··· 3 3 * @module 4 4 */ 5 5 6 - import { isValidHandle } from "npm:@atproto/syntax@0.4.0"; 6 + import { isValidHandle } from "@atproto/syntax"; 7 7 import type { 8 8 AuthorizeOptions, 9 9 CallbackOptions,
+1 -1
src/dpop.ts
··· 3 3 * Uses Web Crypto API for Deno compatibility 4 4 */ 5 5 6 - import { exportJWK, SignJWT } from "jsr:@panva/jose@^6.1.0"; 6 + import { exportJWK, SignJWT } from "@panva/jose"; 7 7 import { DPoPError } from "./errors.ts"; 8 8 9 9 export interface DPoPKeyPair {
+1 -1
tests/errors_test.ts
··· 1 - import { assertEquals, assertInstanceOf } from "jsr:@std/assert@1"; 1 + import { assertEquals, assertInstanceOf } from "@std/assert"; 2 2 import { 3 3 AuthorizationError, 4 4 DPoPError,
+1 -1
tests/session_test.ts
··· 2 2 * @fileoverview Tests for Session class 3 3 */ 4 4 5 - import { assertEquals } from "jsr:@std/assert@1"; 5 + import { assertEquals } from "@std/assert"; 6 6 import { Session, type SessionData } from "../src/session.ts"; 7 7 8 8 // Helper to create test session data
+1 -1
tests/storage_test.ts
··· 2 2 * @fileoverview Tests for storage implementations 3 3 */ 4 4 5 - import { assertEquals } from "jsr:@std/assert@1"; 5 + import { assertEquals } from "@std/assert"; 6 6 import { LocalStorage, MemoryStorage } from "../src/storage.ts"; 7 7 8 8 // Mock localStorage for testing
+1 -1
tests/utils_test.ts
··· 1 - import { assertEquals, assertMatch } from "jsr:@std/assert@1"; 1 + import { assertEquals, assertMatch } from "@std/assert"; 2 2 3 3 // Helper functions that replicate the private PKCE methods for testing 4 4 function generateCodeVerifier(): string {