Auto-indexing service and GraphQL API for AT Protocol Records
0
fork

Configure Feed

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

feat: add redirectUri option to QuicksliceClient

Allow developers to pass a custom redirect URI when creating a
QuicksliceClient, instead of always deriving it from the current
page URL. The redirect URI is stored during login initiation and
retrieved during callback to ensure exact match with the authorize
request.

+373 -21
+322
dev-docs/plans/2024-12-09-redirect-uri-option.md
··· 1 + # Redirect URI Configuration Implementation Plan 2 + 3 + > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. 4 + 5 + **Goal:** Allow developers to pass a custom redirect URI when creating a QuicksliceClient, instead of always deriving it from the current page URL. 6 + 7 + **Architecture:** Add optional `redirectUri` field to `QuicksliceClientOptions`. Store it during login initiation alongside PKCE values. Use stored value in callback to ensure exact match with authorize request. 8 + 9 + **Tech Stack:** TypeScript, browser localStorage 10 + 11 + --- 12 + 13 + ### Task 1: Add redirectUri to Storage Keys 14 + 15 + **Files:** 16 + - Modify: `src/storage/keys.ts` 17 + 18 + **Step 1: Add the new key** 19 + 20 + Open `src/storage/keys.ts` and add `redirectUri` to the `STORAGE_KEYS` object: 21 + 22 + ```typescript 23 + export const STORAGE_KEYS = { 24 + accessToken: 'quickslice_access_token', 25 + refreshToken: 'quickslice_refresh_token', 26 + tokenExpiry: 'quickslice_token_expiry', 27 + userDid: 'quickslice_user_did', 28 + codeVerifier: 'quickslice_code_verifier', 29 + oauthState: 'quickslice_oauth_state', 30 + clientId: 'quickslice_client_id', 31 + dpopKey: 'quickslice_dpop_key', 32 + redirectUri: 'quickslice_redirect_uri', // ADD THIS LINE 33 + }; 34 + ``` 35 + 36 + **Step 2: Verify build** 37 + 38 + Run: `cd /Users/chadmiller/code/quickslice/quickslice-client-js && npm run build` 39 + Expected: Build succeeds 40 + 41 + **Step 3: Commit** 42 + 43 + ```bash 44 + git add src/storage/keys.ts 45 + git commit -m "feat: add redirectUri to storage keys" 46 + ``` 47 + 48 + --- 49 + 50 + ### Task 2: Update OAuth Functions to Accept and Use redirectUri 51 + 52 + **Files:** 53 + - Modify: `src/auth/oauth.ts` 54 + 55 + **Step 1: Update LoginOptions interface** 56 + 57 + Add `redirectUri` to the `LoginOptions` interface at the top of `oauth.ts`: 58 + 59 + ```typescript 60 + export interface LoginOptions { 61 + handle?: string; 62 + redirectUri?: string; 63 + } 64 + ``` 65 + 66 + **Step 2: Update initiateLogin to use provided redirectUri** 67 + 68 + Modify the `initiateLogin` function to accept and store the redirect URI: 69 + 70 + ```typescript 71 + export async function initiateLogin( 72 + authorizeUrl: string, 73 + clientId: string, 74 + options: LoginOptions = {} 75 + ): Promise<void> { 76 + const codeVerifier = generateCodeVerifier(); 77 + const codeChallenge = await generateCodeChallenge(codeVerifier); 78 + const state = generateState(); 79 + 80 + // Build redirect URI (use provided or derive from current page) 81 + const redirectUri = options.redirectUri || (window.location.origin + window.location.pathname); 82 + 83 + // Store for callback 84 + storage.set(STORAGE_KEYS.codeVerifier, codeVerifier); 85 + storage.set(STORAGE_KEYS.oauthState, state); 86 + storage.set(STORAGE_KEYS.clientId, clientId); 87 + storage.set(STORAGE_KEYS.redirectUri, redirectUri); 88 + 89 + // Build authorization URL 90 + const params = new URLSearchParams({ 91 + client_id: clientId, 92 + redirect_uri: redirectUri, 93 + response_type: 'code', 94 + code_challenge: codeChallenge, 95 + code_challenge_method: 'S256', 96 + state: state, 97 + }); 98 + 99 + if (options.handle) { 100 + params.set('login_hint', options.handle); 101 + } 102 + 103 + window.location.href = `${authorizeUrl}?${params.toString()}`; 104 + } 105 + ``` 106 + 107 + **Step 3: Update handleOAuthCallback to use stored redirectUri** 108 + 109 + Modify the `handleOAuthCallback` function to retrieve the stored redirect URI: 110 + 111 + ```typescript 112 + export async function handleOAuthCallback(tokenUrl: string): Promise<boolean> { 113 + const params = new URLSearchParams(window.location.search); 114 + const code = params.get('code'); 115 + const state = params.get('state'); 116 + const error = params.get('error'); 117 + 118 + if (error) { 119 + throw new Error( 120 + `OAuth error: ${error} - ${params.get('error_description') || ''}` 121 + ); 122 + } 123 + 124 + if (!code || !state) { 125 + return false; // Not a callback 126 + } 127 + 128 + // Verify state 129 + const storedState = storage.get(STORAGE_KEYS.oauthState); 130 + if (state !== storedState) { 131 + throw new Error('OAuth state mismatch - possible CSRF attack'); 132 + } 133 + 134 + // Get stored values 135 + const codeVerifier = storage.get(STORAGE_KEYS.codeVerifier); 136 + const clientId = storage.get(STORAGE_KEYS.clientId); 137 + const redirectUri = storage.get(STORAGE_KEYS.redirectUri); 138 + 139 + if (!codeVerifier || !clientId || !redirectUri) { 140 + throw new Error('Missing OAuth session data'); 141 + } 142 + 143 + // Exchange code for tokens with DPoP 144 + const dpopProof = await createDPoPProof('POST', tokenUrl); 145 + 146 + const tokenResponse = await fetch(tokenUrl, { 147 + method: 'POST', 148 + headers: { 149 + 'Content-Type': 'application/x-www-form-urlencoded', 150 + DPoP: dpopProof, 151 + }, 152 + body: new URLSearchParams({ 153 + grant_type: 'authorization_code', 154 + code: code, 155 + redirect_uri: redirectUri, 156 + client_id: clientId, 157 + code_verifier: codeVerifier, 158 + }), 159 + }); 160 + 161 + if (!tokenResponse.ok) { 162 + const errorData = await tokenResponse.json().catch(() => ({})); 163 + throw new Error( 164 + `Token exchange failed: ${errorData.error_description || tokenResponse.statusText}` 165 + ); 166 + } 167 + 168 + const tokens = await tokenResponse.json(); 169 + 170 + // Store tokens 171 + storeTokens(tokens); 172 + 173 + // Clean up OAuth state 174 + storage.remove(STORAGE_KEYS.codeVerifier); 175 + storage.remove(STORAGE_KEYS.oauthState); 176 + storage.remove(STORAGE_KEYS.redirectUri); 177 + 178 + // Clear URL params 179 + window.history.replaceState({}, document.title, window.location.pathname); 180 + 181 + return true; 182 + } 183 + ``` 184 + 185 + **Step 4: Verify build** 186 + 187 + Run: `cd /Users/chadmiller/code/quickslice/quickslice-client-js && npm run build` 188 + Expected: Build succeeds 189 + 190 + **Step 5: Commit** 191 + 192 + ```bash 193 + git add src/auth/oauth.ts 194 + git commit -m "feat: support custom redirectUri in OAuth flow" 195 + ``` 196 + 197 + --- 198 + 199 + ### Task 3: Update QuicksliceClient to Accept redirectUri Option 200 + 201 + **Files:** 202 + - Modify: `src/client.ts` 203 + 204 + **Step 1: Update QuicksliceClientOptions interface** 205 + 206 + Add `redirectUri` to the options interface: 207 + 208 + ```typescript 209 + export interface QuicksliceClientOptions { 210 + server: string; 211 + clientId: string; 212 + redirectUri?: string; 213 + } 214 + ``` 215 + 216 + **Step 2: Store redirectUri in client class** 217 + 218 + Add a private field and store the value in the constructor: 219 + 220 + ```typescript 221 + export class QuicksliceClient { 222 + private server: string; 223 + private clientId: string; 224 + private redirectUri?: string; 225 + private graphqlUrl: string; 226 + private authorizeUrl: string; 227 + private tokenUrl: string; 228 + private initialized = false; 229 + 230 + constructor(options: QuicksliceClientOptions) { 231 + this.server = options.server.replace(/\/$/, ''); // Remove trailing slash 232 + this.clientId = options.clientId; 233 + this.redirectUri = options.redirectUri; 234 + 235 + this.graphqlUrl = `${this.server}/graphql`; 236 + this.authorizeUrl = `${this.server}/oauth/authorize`; 237 + this.tokenUrl = `${this.server}/oauth/token`; 238 + } 239 + ``` 240 + 241 + **Step 3: Pass redirectUri to loginWithRedirect** 242 + 243 + Update the `loginWithRedirect` method to pass the redirect URI: 244 + 245 + ```typescript 246 + /** 247 + * Start OAuth login flow 248 + */ 249 + async loginWithRedirect(options: LoginOptions = {}): Promise<void> { 250 + await this.init(); 251 + await initiateLogin(this.authorizeUrl, this.clientId, { 252 + ...options, 253 + redirectUri: options.redirectUri || this.redirectUri, 254 + }); 255 + } 256 + ``` 257 + 258 + **Step 4: Verify build** 259 + 260 + Run: `cd /Users/chadmiller/code/quickslice/quickslice-client-js && npm run build` 261 + Expected: Build succeeds 262 + 263 + **Step 5: Commit** 264 + 265 + ```bash 266 + git add src/client.ts 267 + git commit -m "feat: add redirectUri option to QuicksliceClient" 268 + ``` 269 + 270 + --- 271 + 272 + ### Task 4: Update README Documentation 273 + 274 + **Files:** 275 + - Modify: `README.md` 276 + 277 + **Step 1: Update the usage example** 278 + 279 + Find the client initialization example and add the `redirectUri` option: 280 + 281 + ```typescript 282 + const client = new QuicksliceClient({ 283 + server: 'https://your-quickslice-server.com', 284 + clientId: 'your-client-id', 285 + redirectUri: 'https://yourapp.com/oauth/callback' // optional 286 + }); 287 + ``` 288 + 289 + **Step 2: Add a note about the option** 290 + 291 + Add documentation explaining: 292 + - `redirectUri` is optional 293 + - If omitted, defaults to current page URL 294 + - Useful when you have a dedicated callback route 295 + 296 + **Step 3: Commit** 297 + 298 + ```bash 299 + git add README.md 300 + git commit -m "docs: add redirectUri option to README" 301 + ``` 302 + 303 + --- 304 + 305 + ### Task 5: Final Build and Verify 306 + 307 + **Step 1: Clean build** 308 + 309 + Run: `cd /Users/chadmiller/code/quickslice/quickslice-client-js && rm -rf dist && npm run build` 310 + Expected: Build succeeds, `dist/` contains updated files 311 + 312 + **Step 2: Check TypeScript declarations** 313 + 314 + Run: `cat dist/client.d.ts | grep -A3 "QuicksliceClientOptions"` 315 + Expected: Shows `redirectUri?: string` in the interface 316 + 317 + **Step 3: Final commit (if any uncommitted changes)** 318 + 319 + ```bash 320 + git status 321 + # If clean, done. Otherwise commit remaining changes. 322 + ```
+2
quickslice-client-js/README.md
··· 23 23 const client = await QuicksliceClient.createQuicksliceClient({ 24 24 server: 'https://api.example.com', 25 25 clientId: 'client_abc123', 26 + redirectUri: 'https://yourapp.com/oauth/callback', // optional 26 27 }); 27 28 28 29 // Handle OAuth callback (on page load) ··· 79 80 Options: 80 81 - `server` (required): Quickslice server URL 81 82 - `clientId` (required): Pre-registered client ID 83 + - `redirectUri` (optional): OAuth callback URL. Defaults to current page URL if omitted. Useful when you have a dedicated callback route. 82 84 83 85 ### `QuicksliceClient` 84 86
+1
quickslice-client-js/dist/auth/oauth.d.ts
··· 1 1 export interface LoginOptions { 2 2 handle?: string; 3 + redirectUri?: string; 3 4 } 4 5 /** 5 6 * Initiate OAuth login flow with PKCE
+2
quickslice-client-js/dist/client.d.ts
··· 2 2 export interface QuicksliceClientOptions { 3 3 server: string; 4 4 clientId: string; 5 + redirectUri?: string; 5 6 } 6 7 export interface User { 7 8 did: string; ··· 9 10 export declare class QuicksliceClient { 10 11 private server; 11 12 private clientId; 13 + private redirectUri?; 12 14 private graphqlUrl; 13 15 private authorizeUrl; 14 16 private tokenUrl;
+12 -5
quickslice-client-js/dist/quickslice-client.esm.js
··· 6 6 clientId: "quickslice_client_id", 7 7 userDid: "quickslice_user_did", 8 8 codeVerifier: "quickslice_code_verifier", 9 - oauthState: "quickslice_oauth_state" 9 + oauthState: "quickslice_oauth_state", 10 + redirectUri: "quickslice_redirect_uri" 10 11 }; 11 12 12 13 // src/storage/storage.ts ··· 312 313 const codeVerifier = generateCodeVerifier(); 313 314 const codeChallenge = await generateCodeChallenge(codeVerifier); 314 315 const state = generateState(); 316 + const redirectUri = options.redirectUri || window.location.origin + window.location.pathname; 315 317 storage.set(STORAGE_KEYS.codeVerifier, codeVerifier); 316 318 storage.set(STORAGE_KEYS.oauthState, state); 317 319 storage.set(STORAGE_KEYS.clientId, clientId); 318 - const redirectUri = window.location.origin + window.location.pathname; 320 + storage.set(STORAGE_KEYS.redirectUri, redirectUri); 319 321 const params = new URLSearchParams({ 320 322 client_id: clientId, 321 323 redirect_uri: redirectUri, ··· 348 350 } 349 351 const codeVerifier = storage.get(STORAGE_KEYS.codeVerifier); 350 352 const clientId = storage.get(STORAGE_KEYS.clientId); 351 - const redirectUri = window.location.origin + window.location.pathname; 352 - if (!codeVerifier || !clientId) { 353 + const redirectUri = storage.get(STORAGE_KEYS.redirectUri); 354 + if (!codeVerifier || !clientId || !redirectUri) { 353 355 throw new Error("Missing OAuth session data"); 354 356 } 355 357 const dpopProof = await createDPoPProof("POST", tokenUrl); ··· 377 379 storeTokens(tokens); 378 380 storage.remove(STORAGE_KEYS.codeVerifier); 379 381 storage.remove(STORAGE_KEYS.oauthState); 382 + storage.remove(STORAGE_KEYS.redirectUri); 380 383 window.history.replaceState({}, document.title, window.location.pathname); 381 384 return true; 382 385 } ··· 423 426 this.initialized = false; 424 427 this.server = options.server.replace(/\/$/, ""); 425 428 this.clientId = options.clientId; 429 + this.redirectUri = options.redirectUri; 426 430 this.graphqlUrl = `${this.server}/graphql`; 427 431 this.authorizeUrl = `${this.server}/oauth/authorize`; 428 432 this.tokenUrl = `${this.server}/oauth/token`; ··· 440 444 */ 441 445 async loginWithRedirect(options = {}) { 442 446 await this.init(); 443 - await initiateLogin(this.authorizeUrl, this.clientId, options); 447 + await initiateLogin(this.authorizeUrl, this.clientId, { 448 + ...options, 449 + redirectUri: options.redirectUri || this.redirectUri 450 + }); 444 451 } 445 452 /** 446 453 * Handle OAuth callback after redirect
+2 -2
quickslice-client-js/dist/quickslice-client.esm.js.map
··· 1 1 { 2 2 "version": 3, 3 3 "sources": ["../src/storage/keys.ts", "../src/storage/storage.ts", "../src/utils/base64url.ts", "../src/utils/crypto.ts", "../src/auth/dpop.ts", "../src/auth/pkce.ts", "../src/storage/lock.ts", "../src/auth/tokens.ts", "../src/auth/oauth.ts", "../src/graphql.ts", "../src/client.ts", "../src/errors.ts", "../src/index.ts"], 4 - "sourcesContent": ["/**\n * Storage key constants\n */\nexport const STORAGE_KEYS = {\n accessToken: 'quickslice_access_token',\n refreshToken: 'quickslice_refresh_token',\n tokenExpiresAt: 'quickslice_token_expires_at',\n clientId: 'quickslice_client_id',\n userDid: 'quickslice_user_did',\n codeVerifier: 'quickslice_code_verifier',\n oauthState: 'quickslice_oauth_state',\n} as const;\n\nexport type StorageKey = (typeof STORAGE_KEYS)[keyof typeof STORAGE_KEYS];\n", "import { STORAGE_KEYS, StorageKey } from './keys';\n\n/**\n * Hybrid storage utility - sessionStorage for OAuth flow state,\n * localStorage for tokens (shared across tabs)\n */\nexport const storage = {\n get(key: StorageKey): string | null {\n // OAuth flow state stays in sessionStorage (per-tab)\n if (key === STORAGE_KEYS.codeVerifier || key === STORAGE_KEYS.oauthState) {\n return sessionStorage.getItem(key);\n }\n // Tokens go in localStorage (shared across tabs)\n return localStorage.getItem(key);\n },\n\n set(key: StorageKey, value: string): void {\n if (key === STORAGE_KEYS.codeVerifier || key === STORAGE_KEYS.oauthState) {\n sessionStorage.setItem(key, value);\n } else {\n localStorage.setItem(key, value);\n }\n },\n\n remove(key: StorageKey): void {\n sessionStorage.removeItem(key);\n localStorage.removeItem(key);\n },\n\n clear(): void {\n Object.values(STORAGE_KEYS).forEach((key) => {\n sessionStorage.removeItem(key);\n localStorage.removeItem(key);\n });\n },\n};\n", "/**\n * Base64 URL encode a buffer (Uint8Array or ArrayBuffer)\n */\nexport function base64UrlEncode(buffer: ArrayBuffer | Uint8Array): string {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\n/**\n * Generate a random base64url string\n */\nexport function generateRandomString(byteLength: number): string {\n const bytes = new Uint8Array(byteLength);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n", "import { base64UrlEncode } from './base64url';\n\n/**\n * SHA-256 hash, returned as base64url string\n */\nexport async function sha256Base64Url(data: string): Promise<string> {\n const encoder = new TextEncoder();\n const hash = await crypto.subtle.digest('SHA-256', encoder.encode(data));\n return base64UrlEncode(hash);\n}\n\n/**\n * Sign a JWT with an ECDSA P-256 private key\n */\nexport async function signJwt(\n header: Record<string, unknown>,\n payload: Record<string, unknown>,\n privateKey: CryptoKey\n): Promise<string> {\n const encoder = new TextEncoder();\n\n const headerB64 = base64UrlEncode(encoder.encode(JSON.stringify(header)));\n const payloadB64 = base64UrlEncode(encoder.encode(JSON.stringify(payload)));\n\n const signingInput = `${headerB64}.${payloadB64}`;\n\n const signature = await crypto.subtle.sign(\n { name: 'ECDSA', hash: 'SHA-256' },\n privateKey,\n encoder.encode(signingInput)\n );\n\n const signatureB64 = base64UrlEncode(signature);\n\n return `${signingInput}.${signatureB64}`;\n}\n", "import { generateRandomString } from '../utils/base64url';\nimport { sha256Base64Url, signJwt } from '../utils/crypto';\n\nconst DB_NAME = 'quickslice-oauth';\nconst DB_VERSION = 1;\nconst KEY_STORE = 'dpop-keys';\nconst KEY_ID = 'dpop-key';\n\ninterface DPoPKeyData {\n id: string;\n privateKey: CryptoKey;\n publicJwk: JsonWebKey;\n createdAt: number;\n}\n\nlet dbPromise: Promise<IDBDatabase> | null = null;\n\nfunction openDatabase(): Promise<IDBDatabase> {\n if (dbPromise) return dbPromise;\n\n dbPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result);\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n if (!db.objectStoreNames.contains(KEY_STORE)) {\n db.createObjectStore(KEY_STORE, { keyPath: 'id' });\n }\n };\n });\n\n return dbPromise;\n}\n\nasync function getDPoPKey(): Promise<DPoPKeyData | null> {\n const db = await openDatabase();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(KEY_STORE, 'readonly');\n const store = tx.objectStore(KEY_STORE);\n const request = store.get(KEY_ID);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result || null);\n });\n}\n\nasync function storeDPoPKey(\n privateKey: CryptoKey,\n publicJwk: JsonWebKey\n): Promise<void> {\n const db = await openDatabase();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(KEY_STORE, 'readwrite');\n const store = tx.objectStore(KEY_STORE);\n const request = store.put({\n id: KEY_ID,\n privateKey,\n publicJwk,\n createdAt: Date.now(),\n });\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve();\n });\n}\n\nexport async function getOrCreateDPoPKey(): Promise<DPoPKeyData> {\n const keyData = await getDPoPKey();\n\n if (keyData) {\n return keyData;\n }\n\n // Generate new P-256 key pair\n const keyPair = await crypto.subtle.generateKey(\n { name: 'ECDSA', namedCurve: 'P-256' },\n false, // NOT extractable - critical for security\n ['sign']\n );\n\n // Export public key as JWK\n const publicJwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);\n\n // Store in IndexedDB\n await storeDPoPKey(keyPair.privateKey, publicJwk);\n\n return {\n id: KEY_ID,\n privateKey: keyPair.privateKey,\n publicJwk,\n createdAt: Date.now(),\n };\n}\n\n/**\n * Create a DPoP proof JWT\n */\nexport async function createDPoPProof(\n method: string,\n url: string,\n accessToken: string | null = null\n): Promise<string> {\n const keyData = await getOrCreateDPoPKey();\n\n // Strip WebCrypto-specific fields from JWK for interoperability\n const { kty, crv, x, y } = keyData.publicJwk;\n const minimalJwk = { kty, crv, x, y };\n\n const header = {\n alg: 'ES256',\n typ: 'dpop+jwt',\n jwk: minimalJwk,\n };\n\n const payload: Record<string, unknown> = {\n jti: generateRandomString(16),\n htm: method,\n htu: url,\n iat: Math.floor(Date.now() / 1000),\n };\n\n // Add access token hash if provided (for resource requests)\n if (accessToken) {\n payload.ath = await sha256Base64Url(accessToken);\n }\n\n return await signJwt(header, payload, keyData.privateKey);\n}\n\n/**\n * Clear DPoP keys from IndexedDB\n */\nexport async function clearDPoPKeys(): Promise<void> {\n const db = await openDatabase();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(KEY_STORE, 'readwrite');\n const store = tx.objectStore(KEY_STORE);\n const request = store.clear();\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve();\n });\n}\n", "import { base64UrlEncode, generateRandomString } from '../utils/base64url';\n\n/**\n * Generate a PKCE code verifier (32 random bytes, base64url encoded)\n */\nexport function generateCodeVerifier(): string {\n return generateRandomString(32);\n}\n\n/**\n * Generate a PKCE code challenge from a verifier (SHA-256, base64url encoded)\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const hash = await crypto.subtle.digest('SHA-256', data);\n return base64UrlEncode(hash);\n}\n\n/**\n * Generate a random state parameter for CSRF protection\n */\nexport function generateState(): string {\n return generateRandomString(16);\n}\n", "const LOCK_TIMEOUT = 5000; // 5 seconds\nconst LOCK_PREFIX = 'quickslice_lock_';\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Acquire a lock using localStorage for multi-tab coordination\n */\nexport async function acquireLock(\n key: string,\n timeout = LOCK_TIMEOUT\n): Promise<string | null> {\n const lockKey = LOCK_PREFIX + key;\n const lockValue = `${Date.now()}_${Math.random()}`;\n const deadline = Date.now() + timeout;\n\n while (Date.now() < deadline) {\n const existing = localStorage.getItem(lockKey);\n\n if (existing) {\n // Check if lock is stale (older than timeout)\n const [timestamp] = existing.split('_');\n if (Date.now() - parseInt(timestamp) > LOCK_TIMEOUT) {\n // Lock is stale, remove it\n localStorage.removeItem(lockKey);\n } else {\n // Lock is held, wait and retry\n await sleep(50);\n continue;\n }\n }\n\n // Try to acquire\n localStorage.setItem(lockKey, lockValue);\n\n // Verify we got it (handle race condition)\n await sleep(10);\n if (localStorage.getItem(lockKey) === lockValue) {\n return lockValue; // Lock acquired\n }\n }\n\n return null; // Failed to acquire\n}\n\n/**\n * Release a lock\n */\nexport function releaseLock(key: string, lockValue: string): void {\n const lockKey = LOCK_PREFIX + key;\n // Only release if we still hold it\n if (localStorage.getItem(lockKey) === lockValue) {\n localStorage.removeItem(lockKey);\n }\n}\n", "import { storage } from '../storage/storage';\nimport { STORAGE_KEYS } from '../storage/keys';\nimport { acquireLock, releaseLock } from '../storage/lock';\nimport { createDPoPProof } from './dpop';\n\nconst TOKEN_REFRESH_BUFFER_MS = 60000; // 60 seconds before expiry\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Refresh tokens using the refresh token\n */\nasync function refreshTokens(tokenUrl: string): Promise<string> {\n const refreshToken = storage.get(STORAGE_KEYS.refreshToken);\n const clientId = storage.get(STORAGE_KEYS.clientId);\n\n if (!refreshToken || !clientId) {\n throw new Error('No refresh token available');\n }\n\n const dpopProof = await createDPoPProof('POST', tokenUrl);\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n DPoP: dpopProof,\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n }),\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(\n `Token refresh failed: ${errorData.error_description || response.statusText}`\n );\n }\n\n const tokens = await response.json();\n\n // Store new tokens (rotation - new refresh token each time)\n storage.set(STORAGE_KEYS.accessToken, tokens.access_token);\n if (tokens.refresh_token) {\n storage.set(STORAGE_KEYS.refreshToken, tokens.refresh_token);\n }\n\n const expiresAt = Date.now() + tokens.expires_in * 1000;\n storage.set(STORAGE_KEYS.tokenExpiresAt, expiresAt.toString());\n\n return tokens.access_token;\n}\n\n/**\n * Get a valid access token, refreshing if necessary.\n * Uses multi-tab locking to prevent duplicate refresh requests.\n */\nexport async function getValidAccessToken(tokenUrl: string): Promise<string> {\n const accessToken = storage.get(STORAGE_KEYS.accessToken);\n const expiresAt = parseInt(storage.get(STORAGE_KEYS.tokenExpiresAt) || '0');\n\n // Check if token is still valid (with buffer)\n if (accessToken && Date.now() < expiresAt - TOKEN_REFRESH_BUFFER_MS) {\n return accessToken;\n }\n\n // Need to refresh - acquire lock first\n const clientId = storage.get(STORAGE_KEYS.clientId);\n const lockKey = `token_refresh_${clientId}`;\n const lockValue = await acquireLock(lockKey);\n\n if (!lockValue) {\n // Failed to acquire lock, another tab is refreshing\n // Wait a bit and check cache again\n await sleep(100);\n const freshToken = storage.get(STORAGE_KEYS.accessToken);\n const freshExpiry = parseInt(\n storage.get(STORAGE_KEYS.tokenExpiresAt) || '0'\n );\n if (freshToken && Date.now() < freshExpiry - TOKEN_REFRESH_BUFFER_MS) {\n return freshToken;\n }\n throw new Error('Failed to refresh token');\n }\n\n try {\n // Double-check after acquiring lock\n const freshToken = storage.get(STORAGE_KEYS.accessToken);\n const freshExpiry = parseInt(\n storage.get(STORAGE_KEYS.tokenExpiresAt) || '0'\n );\n if (freshToken && Date.now() < freshExpiry - TOKEN_REFRESH_BUFFER_MS) {\n return freshToken;\n }\n\n // Actually refresh\n return await refreshTokens(tokenUrl);\n } finally {\n releaseLock(lockKey, lockValue);\n }\n}\n\n/**\n * Store tokens from OAuth response\n */\nexport function storeTokens(tokens: {\n access_token: string;\n refresh_token?: string;\n expires_in: number;\n sub?: string;\n}): void {\n storage.set(STORAGE_KEYS.accessToken, tokens.access_token);\n if (tokens.refresh_token) {\n storage.set(STORAGE_KEYS.refreshToken, tokens.refresh_token);\n }\n\n const expiresAt = Date.now() + tokens.expires_in * 1000;\n storage.set(STORAGE_KEYS.tokenExpiresAt, expiresAt.toString());\n\n if (tokens.sub) {\n storage.set(STORAGE_KEYS.userDid, tokens.sub);\n }\n}\n\n/**\n * Check if we have a valid session\n */\nexport function hasValidSession(): boolean {\n const accessToken = storage.get(STORAGE_KEYS.accessToken);\n const refreshToken = storage.get(STORAGE_KEYS.refreshToken);\n return !!(accessToken || refreshToken);\n}\n", "import { storage } from '../storage/storage';\nimport { STORAGE_KEYS } from '../storage/keys';\nimport { createDPoPProof, clearDPoPKeys } from './dpop';\nimport { generateCodeVerifier, generateCodeChallenge, generateState } from './pkce';\nimport { storeTokens } from './tokens';\n\nexport interface LoginOptions {\n handle?: string;\n}\n\n/**\n * Initiate OAuth login flow with PKCE\n */\nexport async function initiateLogin(\n authorizeUrl: string,\n clientId: string,\n options: LoginOptions = {}\n): Promise<void> {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n // Store for callback\n storage.set(STORAGE_KEYS.codeVerifier, codeVerifier);\n storage.set(STORAGE_KEYS.oauthState, state);\n storage.set(STORAGE_KEYS.clientId, clientId);\n\n // Build redirect URI (current page without query params)\n const redirectUri = window.location.origin + window.location.pathname;\n\n // Build authorization URL\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n state: state,\n });\n\n if (options.handle) {\n params.set('login_hint', options.handle);\n }\n\n window.location.href = `${authorizeUrl}?${params.toString()}`;\n}\n\n/**\n * Handle OAuth callback - exchange code for tokens\n * Returns true if callback was handled, false if not a callback\n */\nexport async function handleOAuthCallback(tokenUrl: string): Promise<boolean> {\n const params = new URLSearchParams(window.location.search);\n const code = params.get('code');\n const state = params.get('state');\n const error = params.get('error');\n\n if (error) {\n throw new Error(\n `OAuth error: ${error} - ${params.get('error_description') || ''}`\n );\n }\n\n if (!code || !state) {\n return false; // Not a callback\n }\n\n // Verify state\n const storedState = storage.get(STORAGE_KEYS.oauthState);\n if (state !== storedState) {\n throw new Error('OAuth state mismatch - possible CSRF attack');\n }\n\n // Get stored values\n const codeVerifier = storage.get(STORAGE_KEYS.codeVerifier);\n const clientId = storage.get(STORAGE_KEYS.clientId);\n const redirectUri = window.location.origin + window.location.pathname;\n\n if (!codeVerifier || !clientId) {\n throw new Error('Missing OAuth session data');\n }\n\n // Exchange code for tokens with DPoP\n const dpopProof = await createDPoPProof('POST', tokenUrl);\n\n const tokenResponse = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n DPoP: dpopProof,\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: code,\n redirect_uri: redirectUri,\n client_id: clientId,\n code_verifier: codeVerifier,\n }),\n });\n\n if (!tokenResponse.ok) {\n const errorData = await tokenResponse.json().catch(() => ({}));\n throw new Error(\n `Token exchange failed: ${errorData.error_description || tokenResponse.statusText}`\n );\n }\n\n const tokens = await tokenResponse.json();\n\n // Store tokens\n storeTokens(tokens);\n\n // Clean up OAuth state\n storage.remove(STORAGE_KEYS.codeVerifier);\n storage.remove(STORAGE_KEYS.oauthState);\n\n // Clear URL params\n window.history.replaceState({}, document.title, window.location.pathname);\n\n return true;\n}\n\n/**\n * Logout - clear all stored data\n */\nexport async function logout(options: { reload?: boolean } = {}): Promise<void> {\n storage.clear();\n await clearDPoPKeys();\n\n if (options.reload !== false) {\n window.location.reload();\n }\n}\n", "import { createDPoPProof } from './auth/dpop';\nimport { getValidAccessToken } from './auth/tokens';\n\nexport interface GraphQLResponse<T = unknown> {\n data?: T;\n errors?: Array<{ message: string; path?: string[] }>;\n}\n\n/**\n * Execute a GraphQL query or mutation\n */\nexport async function graphqlRequest<T = unknown>(\n graphqlUrl: string,\n tokenUrl: string,\n query: string,\n variables: Record<string, unknown> = {},\n requireAuth = false\n): Promise<T> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (requireAuth) {\n const token = await getValidAccessToken(tokenUrl);\n if (!token) {\n throw new Error('Not authenticated');\n }\n\n // Create DPoP proof bound to this request\n const dpopProof = await createDPoPProof('POST', graphqlUrl, token);\n\n headers['Authorization'] = `DPoP ${token}`;\n headers['DPoP'] = dpopProof;\n }\n\n const response = await fetch(graphqlUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify({ query, variables }),\n });\n\n if (!response.ok) {\n throw new Error(`GraphQL request failed: ${response.statusText}`);\n }\n\n const result: GraphQLResponse<T> = await response.json();\n\n if (result.errors && result.errors.length > 0) {\n throw new Error(`GraphQL error: ${result.errors[0].message}`);\n }\n\n return result.data as T;\n}\n", "import { storage } from './storage/storage';\nimport { STORAGE_KEYS } from './storage/keys';\nimport { getOrCreateDPoPKey } from './auth/dpop';\nimport { initiateLogin, handleOAuthCallback, logout as doLogout, LoginOptions } from './auth/oauth';\nimport { getValidAccessToken, hasValidSession } from './auth/tokens';\nimport { graphqlRequest } from './graphql';\n\nexport interface QuicksliceClientOptions {\n server: string;\n clientId: string;\n}\n\nexport interface User {\n did: string;\n}\n\nexport class QuicksliceClient {\n private server: string;\n private clientId: string;\n private graphqlUrl: string;\n private authorizeUrl: string;\n private tokenUrl: string;\n private initialized = false;\n\n constructor(options: QuicksliceClientOptions) {\n this.server = options.server.replace(/\\/$/, ''); // Remove trailing slash\n this.clientId = options.clientId;\n\n this.graphqlUrl = `${this.server}/graphql`;\n this.authorizeUrl = `${this.server}/oauth/authorize`;\n this.tokenUrl = `${this.server}/oauth/token`;\n }\n\n /**\n * Initialize the client - must be called before other methods\n */\n async init(): Promise<void> {\n if (this.initialized) return;\n\n // Ensure DPoP key exists\n await getOrCreateDPoPKey();\n\n this.initialized = true;\n }\n\n /**\n * Start OAuth login flow\n */\n async loginWithRedirect(options: LoginOptions = {}): Promise<void> {\n await this.init();\n await initiateLogin(this.authorizeUrl, this.clientId, options);\n }\n\n /**\n * Handle OAuth callback after redirect\n * Returns true if callback was handled\n */\n async handleRedirectCallback(): Promise<boolean> {\n await this.init();\n return await handleOAuthCallback(this.tokenUrl);\n }\n\n /**\n * Logout and clear all stored data\n */\n async logout(options: { reload?: boolean } = {}): Promise<void> {\n await doLogout(options);\n }\n\n /**\n * Check if user is authenticated\n */\n async isAuthenticated(): Promise<boolean> {\n return hasValidSession();\n }\n\n /**\n * Get current user's DID (from stored token data)\n * For richer profile info, use client.query() with your own schema\n */\n getUser(): User | null {\n if (!hasValidSession()) {\n return null;\n }\n\n const did = storage.get(STORAGE_KEYS.userDid);\n if (!did) {\n return null;\n }\n\n return { did };\n }\n\n /**\n * Get access token (auto-refreshes if needed)\n */\n async getAccessToken(): Promise<string> {\n await this.init();\n return await getValidAccessToken(this.tokenUrl);\n }\n\n /**\n * Execute a GraphQL query (authenticated)\n */\n async query<T = unknown>(\n query: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n await this.init();\n return await graphqlRequest<T>(\n this.graphqlUrl,\n this.tokenUrl,\n query,\n variables,\n true\n );\n }\n\n /**\n * Execute a GraphQL mutation (authenticated)\n */\n async mutate<T = unknown>(\n mutation: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n return this.query<T>(mutation, variables);\n }\n\n /**\n * Execute a public GraphQL query (no auth)\n */\n async publicQuery<T = unknown>(\n query: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n await this.init();\n return await graphqlRequest<T>(\n this.graphqlUrl,\n this.tokenUrl,\n query,\n variables,\n false\n );\n }\n}\n", "/**\n * Base error class for Quickslice client errors\n */\nexport class QuicksliceError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'QuicksliceError';\n }\n}\n\n/**\n * Thrown when authentication is required but user is not logged in\n */\nexport class LoginRequiredError extends QuicksliceError {\n constructor(message = 'Login required') {\n super(message);\n this.name = 'LoginRequiredError';\n }\n}\n\n/**\n * Thrown when network request fails\n */\nexport class NetworkError extends QuicksliceError {\n constructor(message: string) {\n super(message);\n this.name = 'NetworkError';\n }\n}\n\n/**\n * Thrown when OAuth flow fails\n */\nexport class OAuthError extends QuicksliceError {\n public code: string;\n public description?: string;\n\n constructor(code: string, description?: string) {\n super(`OAuth error: ${code}${description ? ` - ${description}` : ''}`);\n this.name = 'OAuthError';\n this.code = code;\n this.description = description;\n }\n}\n", "export { QuicksliceClient, QuicksliceClientOptions, User } from './client';\nexport {\n QuicksliceError,\n LoginRequiredError,\n NetworkError,\n OAuthError,\n} from './errors';\n\nimport { QuicksliceClient, QuicksliceClientOptions } from './client';\n\n/**\n * Create and initialize a Quickslice client\n */\nexport async function createQuicksliceClient(\n options: QuicksliceClientOptions\n): Promise<QuicksliceClient> {\n const client = new QuicksliceClient(options);\n await client.init();\n return client;\n}\n"], 5 - "mappings": ";AAGO,IAAM,eAAe;AAAA,EAC1B,aAAa;AAAA,EACb,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,SAAS;AAAA,EACT,cAAc;AAAA,EACd,YAAY;AACd;;;ACLO,IAAM,UAAU;AAAA,EACrB,IAAI,KAAgC;AAElC,QAAI,QAAQ,aAAa,gBAAgB,QAAQ,aAAa,YAAY;AACxE,aAAO,eAAe,QAAQ,GAAG;AAAA,IACnC;AAEA,WAAO,aAAa,QAAQ,GAAG;AAAA,EACjC;AAAA,EAEA,IAAI,KAAiB,OAAqB;AACxC,QAAI,QAAQ,aAAa,gBAAgB,QAAQ,aAAa,YAAY;AACxE,qBAAe,QAAQ,KAAK,KAAK;AAAA,IACnC,OAAO;AACL,mBAAa,QAAQ,KAAK,KAAK;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,OAAO,KAAuB;AAC5B,mBAAe,WAAW,GAAG;AAC7B,iBAAa,WAAW,GAAG;AAAA,EAC7B;AAAA,EAEA,QAAc;AACZ,WAAO,OAAO,YAAY,EAAE,QAAQ,CAAC,QAAQ;AAC3C,qBAAe,WAAW,GAAG;AAC7B,mBAAa,WAAW,GAAG;AAAA,IAC7B,CAAC;AAAA,EACH;AACF;;;AChCO,SAAS,gBAAgB,QAA0C;AACxE,QAAM,QAAQ,kBAAkB,aAAa,SAAS,IAAI,WAAW,MAAM;AAC3E,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAC;AAAA,EACxC;AACA,SAAO,KAAK,MAAM,EACf,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AACtB;AAKO,SAAS,qBAAqB,YAA4B;AAC/D,QAAM,QAAQ,IAAI,WAAW,UAAU;AACvC,SAAO,gBAAgB,KAAK;AAC5B,SAAO,gBAAgB,KAAK;AAC9B;;;ACjBA,eAAsB,gBAAgB,MAA+B;AACnE,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,OAAO,MAAM,OAAO,OAAO,OAAO,WAAW,QAAQ,OAAO,IAAI,CAAC;AACvE,SAAO,gBAAgB,IAAI;AAC7B;AAKA,eAAsB,QACpB,QACA,SACA,YACiB;AACjB,QAAM,UAAU,IAAI,YAAY;AAEhC,QAAM,YAAY,gBAAgB,QAAQ,OAAO,KAAK,UAAU,MAAM,CAAC,CAAC;AACxE,QAAM,aAAa,gBAAgB,QAAQ,OAAO,KAAK,UAAU,OAAO,CAAC,CAAC;AAE1E,QAAM,eAAe,GAAG,SAAS,IAAI,UAAU;AAE/C,QAAM,YAAY,MAAM,OAAO,OAAO;AAAA,IACpC,EAAE,MAAM,SAAS,MAAM,UAAU;AAAA,IACjC;AAAA,IACA,QAAQ,OAAO,YAAY;AAAA,EAC7B;AAEA,QAAM,eAAe,gBAAgB,SAAS;AAE9C,SAAO,GAAG,YAAY,IAAI,YAAY;AACxC;;;AChCA,IAAM,UAAU;AAChB,IAAM,aAAa;AACnB,IAAM,YAAY;AAClB,IAAM,SAAS;AASf,IAAI,YAAyC;AAE7C,SAAS,eAAqC;AAC5C,MAAI,UAAW,QAAO;AAEtB,cAAY,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC3C,UAAM,UAAU,UAAU,KAAK,SAAS,UAAU;AAElD,YAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAC5C,YAAQ,YAAY,MAAM,QAAQ,QAAQ,MAAM;AAEhD,YAAQ,kBAAkB,CAAC,UAAU;AACnC,YAAM,KAAM,MAAM,OAA4B;AAC9C,UAAI,CAAC,GAAG,iBAAiB,SAAS,SAAS,GAAG;AAC5C,WAAG,kBAAkB,WAAW,EAAE,SAAS,KAAK,CAAC;AAAA,MACnD;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAEA,eAAe,aAA0C;AACvD,QAAM,KAAK,MAAM,aAAa;AAC9B,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,KAAK,GAAG,YAAY,WAAW,UAAU;AAC/C,UAAM,QAAQ,GAAG,YAAY,SAAS;AACtC,UAAM,UAAU,MAAM,IAAI,MAAM;AAEhC,YAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAC5C,YAAQ,YAAY,MAAM,QAAQ,QAAQ,UAAU,IAAI;AAAA,EAC1D,CAAC;AACH;AAEA,eAAe,aACb,YACA,WACe;AACf,QAAM,KAAK,MAAM,aAAa;AAC9B,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,KAAK,GAAG,YAAY,WAAW,WAAW;AAChD,UAAM,QAAQ,GAAG,YAAY,SAAS;AACtC,UAAM,UAAU,MAAM,IAAI;AAAA,MACxB,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,MACA,WAAW,KAAK,IAAI;AAAA,IACtB,CAAC;AAED,YAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAC5C,YAAQ,YAAY,MAAM,QAAQ;AAAA,EACpC,CAAC;AACH;AAEA,eAAsB,qBAA2C;AAC/D,QAAM,UAAU,MAAM,WAAW;AAEjC,MAAI,SAAS;AACX,WAAO;AAAA,EACT;AAGA,QAAM,UAAU,MAAM,OAAO,OAAO;AAAA,IAClC,EAAE,MAAM,SAAS,YAAY,QAAQ;AAAA,IACrC;AAAA;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAGA,QAAM,YAAY,MAAM,OAAO,OAAO,UAAU,OAAO,QAAQ,SAAS;AAGxE,QAAM,aAAa,QAAQ,YAAY,SAAS;AAEhD,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,YAAY,QAAQ;AAAA,IACpB;AAAA,IACA,WAAW,KAAK,IAAI;AAAA,EACtB;AACF;AAKA,eAAsB,gBACpB,QACA,KACA,cAA6B,MACZ;AACjB,QAAM,UAAU,MAAM,mBAAmB;AAGzC,QAAM,EAAE,KAAK,KAAK,GAAG,EAAE,IAAI,QAAQ;AACnC,QAAM,aAAa,EAAE,KAAK,KAAK,GAAG,EAAE;AAEpC,QAAM,SAAS;AAAA,IACb,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAEA,QAAM,UAAmC;AAAA,IACvC,KAAK,qBAAqB,EAAE;AAAA,IAC5B,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,EACnC;AAGA,MAAI,aAAa;AACf,YAAQ,MAAM,MAAM,gBAAgB,WAAW;AAAA,EACjD;AAEA,SAAO,MAAM,QAAQ,QAAQ,SAAS,QAAQ,UAAU;AAC1D;AAKA,eAAsB,gBAA+B;AACnD,QAAM,KAAK,MAAM,aAAa;AAC9B,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,KAAK,GAAG,YAAY,WAAW,WAAW;AAChD,UAAM,QAAQ,GAAG,YAAY,SAAS;AACtC,UAAM,UAAU,MAAM,MAAM;AAE5B,YAAQ,UAAU,MAAM,OAAO,QAAQ,KAAK;AAC5C,YAAQ,YAAY,MAAM,QAAQ;AAAA,EACpC,CAAC;AACH;;;AC5IO,SAAS,uBAA+B;AAC7C,SAAO,qBAAqB,EAAE;AAChC;AAKA,eAAsB,sBAAsB,UAAmC;AAC7E,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,OAAO,QAAQ,OAAO,QAAQ;AACpC,QAAM,OAAO,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI;AACvD,SAAO,gBAAgB,IAAI;AAC7B;AAKO,SAAS,gBAAwB;AACtC,SAAO,qBAAqB,EAAE;AAChC;;;ACxBA,IAAM,eAAe;AACrB,IAAM,cAAc;AAEpB,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAKA,eAAsB,YACpB,KACA,UAAU,cACc;AACxB,QAAM,UAAU,cAAc;AAC9B,QAAM,YAAY,GAAG,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC;AAChD,QAAM,WAAW,KAAK,IAAI,IAAI;AAE9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAM,WAAW,aAAa,QAAQ,OAAO;AAE7C,QAAI,UAAU;AAEZ,YAAM,CAAC,SAAS,IAAI,SAAS,MAAM,GAAG;AACtC,UAAI,KAAK,IAAI,IAAI,SAAS,SAAS,IAAI,cAAc;AAEnD,qBAAa,WAAW,OAAO;AAAA,MACjC,OAAO;AAEL,cAAM,MAAM,EAAE;AACd;AAAA,MACF;AAAA,IACF;AAGA,iBAAa,QAAQ,SAAS,SAAS;AAGvC,UAAM,MAAM,EAAE;AACd,QAAI,aAAa,QAAQ,OAAO,MAAM,WAAW;AAC/C,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,YAAY,KAAa,WAAyB;AAChE,QAAM,UAAU,cAAc;AAE9B,MAAI,aAAa,QAAQ,OAAO,MAAM,WAAW;AAC/C,iBAAa,WAAW,OAAO;AAAA,EACjC;AACF;;;ACnDA,IAAM,0BAA0B;AAEhC,SAASA,OAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAKA,eAAe,cAAc,UAAmC;AAC9D,QAAM,eAAe,QAAQ,IAAI,aAAa,YAAY;AAC1D,QAAM,WAAW,QAAQ,IAAI,aAAa,QAAQ;AAElD,MAAI,CAAC,gBAAgB,CAAC,UAAU;AAC9B,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AAEA,QAAM,YAAY,MAAM,gBAAgB,QAAQ,QAAQ;AAExD,QAAM,WAAW,MAAM,MAAM,UAAU;AAAA,IACrC,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,MAAM;AAAA,IACR;AAAA,IACA,MAAM,IAAI,gBAAgB;AAAA,MACxB,YAAY;AAAA,MACZ,eAAe;AAAA,MACf,WAAW;AAAA,IACb,CAAC;AAAA,EACH,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACxD,UAAM,IAAI;AAAA,MACR,yBAAyB,UAAU,qBAAqB,SAAS,UAAU;AAAA,IAC7E;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,SAAS,KAAK;AAGnC,UAAQ,IAAI,aAAa,aAAa,OAAO,YAAY;AACzD,MAAI,OAAO,eAAe;AACxB,YAAQ,IAAI,aAAa,cAAc,OAAO,aAAa;AAAA,EAC7D;AAEA,QAAM,YAAY,KAAK,IAAI,IAAI,OAAO,aAAa;AACnD,UAAQ,IAAI,aAAa,gBAAgB,UAAU,SAAS,CAAC;AAE7D,SAAO,OAAO;AAChB;AAMA,eAAsB,oBAAoB,UAAmC;AAC3E,QAAM,cAAc,QAAQ,IAAI,aAAa,WAAW;AACxD,QAAM,YAAY,SAAS,QAAQ,IAAI,aAAa,cAAc,KAAK,GAAG;AAG1E,MAAI,eAAe,KAAK,IAAI,IAAI,YAAY,yBAAyB;AACnE,WAAO;AAAA,EACT;AAGA,QAAM,WAAW,QAAQ,IAAI,aAAa,QAAQ;AAClD,QAAM,UAAU,iBAAiB,QAAQ;AACzC,QAAM,YAAY,MAAM,YAAY,OAAO;AAE3C,MAAI,CAAC,WAAW;AAGd,UAAMA,OAAM,GAAG;AACf,UAAM,aAAa,QAAQ,IAAI,aAAa,WAAW;AACvD,UAAM,cAAc;AAAA,MAClB,QAAQ,IAAI,aAAa,cAAc,KAAK;AAAA,IAC9C;AACA,QAAI,cAAc,KAAK,IAAI,IAAI,cAAc,yBAAyB;AACpE,aAAO;AAAA,IACT;AACA,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AAEA,MAAI;AAEF,UAAM,aAAa,QAAQ,IAAI,aAAa,WAAW;AACvD,UAAM,cAAc;AAAA,MAClB,QAAQ,IAAI,aAAa,cAAc,KAAK;AAAA,IAC9C;AACA,QAAI,cAAc,KAAK,IAAI,IAAI,cAAc,yBAAyB;AACpE,aAAO;AAAA,IACT;AAGA,WAAO,MAAM,cAAc,QAAQ;AAAA,EACrC,UAAE;AACA,gBAAY,SAAS,SAAS;AAAA,EAChC;AACF;AAKO,SAAS,YAAY,QAKnB;AACP,UAAQ,IAAI,aAAa,aAAa,OAAO,YAAY;AACzD,MAAI,OAAO,eAAe;AACxB,YAAQ,IAAI,aAAa,cAAc,OAAO,aAAa;AAAA,EAC7D;AAEA,QAAM,YAAY,KAAK,IAAI,IAAI,OAAO,aAAa;AACnD,UAAQ,IAAI,aAAa,gBAAgB,UAAU,SAAS,CAAC;AAE7D,MAAI,OAAO,KAAK;AACd,YAAQ,IAAI,aAAa,SAAS,OAAO,GAAG;AAAA,EAC9C;AACF;AAKO,SAAS,kBAA2B;AACzC,QAAM,cAAc,QAAQ,IAAI,aAAa,WAAW;AACxD,QAAM,eAAe,QAAQ,IAAI,aAAa,YAAY;AAC1D,SAAO,CAAC,EAAE,eAAe;AAC3B;;;AC3HA,eAAsB,cACpB,cACA,UACA,UAAwB,CAAC,GACV;AACf,QAAM,eAAe,qBAAqB;AAC1C,QAAM,gBAAgB,MAAM,sBAAsB,YAAY;AAC9D,QAAM,QAAQ,cAAc;AAG5B,UAAQ,IAAI,aAAa,cAAc,YAAY;AACnD,UAAQ,IAAI,aAAa,YAAY,KAAK;AAC1C,UAAQ,IAAI,aAAa,UAAU,QAAQ;AAG3C,QAAM,cAAc,OAAO,SAAS,SAAS,OAAO,SAAS;AAG7D,QAAM,SAAS,IAAI,gBAAgB;AAAA,IACjC,WAAW;AAAA,IACX,cAAc;AAAA,IACd,eAAe;AAAA,IACf,gBAAgB;AAAA,IAChB,uBAAuB;AAAA,IACvB;AAAA,EACF,CAAC;AAED,MAAI,QAAQ,QAAQ;AAClB,WAAO,IAAI,cAAc,QAAQ,MAAM;AAAA,EACzC;AAEA,SAAO,SAAS,OAAO,GAAG,YAAY,IAAI,OAAO,SAAS,CAAC;AAC7D;AAMA,eAAsB,oBAAoB,UAAoC;AAC5E,QAAM,SAAS,IAAI,gBAAgB,OAAO,SAAS,MAAM;AACzD,QAAM,OAAO,OAAO,IAAI,MAAM;AAC9B,QAAM,QAAQ,OAAO,IAAI,OAAO;AAChC,QAAM,QAAQ,OAAO,IAAI,OAAO;AAEhC,MAAI,OAAO;AACT,UAAM,IAAI;AAAA,MACR,gBAAgB,KAAK,MAAM,OAAO,IAAI,mBAAmB,KAAK,EAAE;AAAA,IAClE;AAAA,EACF;AAEA,MAAI,CAAC,QAAQ,CAAC,OAAO;AACnB,WAAO;AAAA,EACT;AAGA,QAAM,cAAc,QAAQ,IAAI,aAAa,UAAU;AACvD,MAAI,UAAU,aAAa;AACzB,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AAGA,QAAM,eAAe,QAAQ,IAAI,aAAa,YAAY;AAC1D,QAAM,WAAW,QAAQ,IAAI,aAAa,QAAQ;AAClD,QAAM,cAAc,OAAO,SAAS,SAAS,OAAO,SAAS;AAE7D,MAAI,CAAC,gBAAgB,CAAC,UAAU;AAC9B,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AAGA,QAAM,YAAY,MAAM,gBAAgB,QAAQ,QAAQ;AAExD,QAAM,gBAAgB,MAAM,MAAM,UAAU;AAAA,IAC1C,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,MAAM;AAAA,IACR;AAAA,IACA,MAAM,IAAI,gBAAgB;AAAA,MACxB,YAAY;AAAA,MACZ;AAAA,MACA,cAAc;AAAA,MACd,WAAW;AAAA,MACX,eAAe;AAAA,IACjB,CAAC;AAAA,EACH,CAAC;AAED,MAAI,CAAC,cAAc,IAAI;AACrB,UAAM,YAAY,MAAM,cAAc,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC7D,UAAM,IAAI;AAAA,MACR,0BAA0B,UAAU,qBAAqB,cAAc,UAAU;AAAA,IACnF;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,cAAc,KAAK;AAGxC,cAAY,MAAM;AAGlB,UAAQ,OAAO,aAAa,YAAY;AACxC,UAAQ,OAAO,aAAa,UAAU;AAGtC,SAAO,QAAQ,aAAa,CAAC,GAAG,SAAS,OAAO,OAAO,SAAS,QAAQ;AAExE,SAAO;AACT;AAKA,eAAsB,OAAO,UAAgC,CAAC,GAAkB;AAC9E,UAAQ,MAAM;AACd,QAAM,cAAc;AAEpB,MAAI,QAAQ,WAAW,OAAO;AAC5B,WAAO,SAAS,OAAO;AAAA,EACzB;AACF;;;ACzHA,eAAsB,eACpB,YACA,UACA,OACA,YAAqC,CAAC,GACtC,cAAc,OACF;AACZ,QAAM,UAAkC;AAAA,IACtC,gBAAgB;AAAA,EAClB;AAEA,MAAI,aAAa;AACf,UAAM,QAAQ,MAAM,oBAAoB,QAAQ;AAChD,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,mBAAmB;AAAA,IACrC;AAGA,UAAM,YAAY,MAAM,gBAAgB,QAAQ,YAAY,KAAK;AAEjE,YAAQ,eAAe,IAAI,QAAQ,KAAK;AACxC,YAAQ,MAAM,IAAI;AAAA,EACpB;AAEA,QAAM,WAAW,MAAM,MAAM,YAAY;AAAA,IACvC,QAAQ;AAAA,IACR;AAAA,IACA,MAAM,KAAK,UAAU,EAAE,OAAO,UAAU,CAAC;AAAA,EAC3C,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,2BAA2B,SAAS,UAAU,EAAE;AAAA,EAClE;AAEA,QAAM,SAA6B,MAAM,SAAS,KAAK;AAEvD,MAAI,OAAO,UAAU,OAAO,OAAO,SAAS,GAAG;AAC7C,UAAM,IAAI,MAAM,kBAAkB,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE;AAAA,EAC9D;AAEA,SAAO,OAAO;AAChB;;;ACpCO,IAAM,mBAAN,MAAuB;AAAA,EAQ5B,YAAY,SAAkC;AAF9C,SAAQ,cAAc;AAGpB,SAAK,SAAS,QAAQ,OAAO,QAAQ,OAAO,EAAE;AAC9C,SAAK,WAAW,QAAQ;AAExB,SAAK,aAAa,GAAG,KAAK,MAAM;AAChC,SAAK,eAAe,GAAG,KAAK,MAAM;AAClC,SAAK,WAAW,GAAG,KAAK,MAAM;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,KAAK,YAAa;AAGtB,UAAM,mBAAmB;AAEzB,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAkB,UAAwB,CAAC,GAAkB;AACjE,UAAM,KAAK,KAAK;AAChB,UAAM,cAAc,KAAK,cAAc,KAAK,UAAU,OAAO;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,yBAA2C;AAC/C,UAAM,KAAK,KAAK;AAChB,WAAO,MAAM,oBAAoB,KAAK,QAAQ;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,UAAgC,CAAC,GAAkB;AAC9D,UAAM,OAAS,OAAO;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAoC;AACxC,WAAO,gBAAgB;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAuB;AACrB,QAAI,CAAC,gBAAgB,GAAG;AACtB,aAAO;AAAA,IACT;AAEA,UAAM,MAAM,QAAQ,IAAI,aAAa,OAAO;AAC5C,QAAI,CAAC,KAAK;AACR,aAAO;AAAA,IACT;AAEA,WAAO,EAAE,IAAI;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAkC;AACtC,UAAM,KAAK,KAAK;AAChB,WAAO,MAAM,oBAAoB,KAAK,QAAQ;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MACJ,OACA,YAAqC,CAAC,GAC1B;AACZ,UAAM,KAAK,KAAK;AAChB,WAAO,MAAM;AAAA,MACX,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OACJ,UACA,YAAqC,CAAC,GAC1B;AACZ,WAAO,KAAK,MAAS,UAAU,SAAS;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YACJ,OACA,YAAqC,CAAC,GAC1B;AACZ,UAAM,KAAK,KAAK;AAChB,WAAO,MAAM;AAAA,MACX,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;;;AC7IO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,qBAAN,cAAiC,gBAAgB;AAAA,EACtD,YAAY,UAAU,kBAAkB;AACtC,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,eAAN,cAA2B,gBAAgB;AAAA,EAChD,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,aAAN,cAAyB,gBAAgB;AAAA,EAI9C,YAAY,MAAc,aAAsB;AAC9C,UAAM,gBAAgB,IAAI,GAAG,cAAc,MAAM,WAAW,KAAK,EAAE,EAAE;AACrE,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,cAAc;AAAA,EACrB;AACF;;;AC9BA,eAAsB,uBACpB,SAC2B;AAC3B,QAAM,SAAS,IAAI,iBAAiB,OAAO;AAC3C,QAAM,OAAO,KAAK;AAClB,SAAO;AACT;", 4 + "sourcesContent": ["/**\n * Storage key constants\n */\nexport const STORAGE_KEYS = {\n accessToken: 'quickslice_access_token',\n refreshToken: 'quickslice_refresh_token',\n tokenExpiresAt: 'quickslice_token_expires_at',\n clientId: 'quickslice_client_id',\n userDid: 'quickslice_user_did',\n codeVerifier: 'quickslice_code_verifier',\n oauthState: 'quickslice_oauth_state',\n redirectUri: 'quickslice_redirect_uri',\n} as const;\n\nexport type StorageKey = (typeof STORAGE_KEYS)[keyof typeof STORAGE_KEYS];\n", "import { STORAGE_KEYS, StorageKey } from './keys';\n\n/**\n * Hybrid storage utility - sessionStorage for OAuth flow state,\n * localStorage for tokens (shared across tabs)\n */\nexport const storage = {\n get(key: StorageKey): string | null {\n // OAuth flow state stays in sessionStorage (per-tab)\n if (key === STORAGE_KEYS.codeVerifier || key === STORAGE_KEYS.oauthState) {\n return sessionStorage.getItem(key);\n }\n // Tokens go in localStorage (shared across tabs)\n return localStorage.getItem(key);\n },\n\n set(key: StorageKey, value: string): void {\n if (key === STORAGE_KEYS.codeVerifier || key === STORAGE_KEYS.oauthState) {\n sessionStorage.setItem(key, value);\n } else {\n localStorage.setItem(key, value);\n }\n },\n\n remove(key: StorageKey): void {\n sessionStorage.removeItem(key);\n localStorage.removeItem(key);\n },\n\n clear(): void {\n Object.values(STORAGE_KEYS).forEach((key) => {\n sessionStorage.removeItem(key);\n localStorage.removeItem(key);\n });\n },\n};\n", "/**\n * Base64 URL encode a buffer (Uint8Array or ArrayBuffer)\n */\nexport function base64UrlEncode(buffer: ArrayBuffer | Uint8Array): string {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\n/**\n * Generate a random base64url string\n */\nexport function generateRandomString(byteLength: number): string {\n const bytes = new Uint8Array(byteLength);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n", "import { base64UrlEncode } from './base64url';\n\n/**\n * SHA-256 hash, returned as base64url string\n */\nexport async function sha256Base64Url(data: string): Promise<string> {\n const encoder = new TextEncoder();\n const hash = await crypto.subtle.digest('SHA-256', encoder.encode(data));\n return base64UrlEncode(hash);\n}\n\n/**\n * Sign a JWT with an ECDSA P-256 private key\n */\nexport async function signJwt(\n header: Record<string, unknown>,\n payload: Record<string, unknown>,\n privateKey: CryptoKey\n): Promise<string> {\n const encoder = new TextEncoder();\n\n const headerB64 = base64UrlEncode(encoder.encode(JSON.stringify(header)));\n const payloadB64 = base64UrlEncode(encoder.encode(JSON.stringify(payload)));\n\n const signingInput = `${headerB64}.${payloadB64}`;\n\n const signature = await crypto.subtle.sign(\n { name: 'ECDSA', hash: 'SHA-256' },\n privateKey,\n encoder.encode(signingInput)\n );\n\n const signatureB64 = base64UrlEncode(signature);\n\n return `${signingInput}.${signatureB64}`;\n}\n", "import { generateRandomString } from '../utils/base64url';\nimport { sha256Base64Url, signJwt } from '../utils/crypto';\n\nconst DB_NAME = 'quickslice-oauth';\nconst DB_VERSION = 1;\nconst KEY_STORE = 'dpop-keys';\nconst KEY_ID = 'dpop-key';\n\ninterface DPoPKeyData {\n id: string;\n privateKey: CryptoKey;\n publicJwk: JsonWebKey;\n createdAt: number;\n}\n\nlet dbPromise: Promise<IDBDatabase> | null = null;\n\nfunction openDatabase(): Promise<IDBDatabase> {\n if (dbPromise) return dbPromise;\n\n dbPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result);\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n if (!db.objectStoreNames.contains(KEY_STORE)) {\n db.createObjectStore(KEY_STORE, { keyPath: 'id' });\n }\n };\n });\n\n return dbPromise;\n}\n\nasync function getDPoPKey(): Promise<DPoPKeyData | null> {\n const db = await openDatabase();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(KEY_STORE, 'readonly');\n const store = tx.objectStore(KEY_STORE);\n const request = store.get(KEY_ID);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result || null);\n });\n}\n\nasync function storeDPoPKey(\n privateKey: CryptoKey,\n publicJwk: JsonWebKey\n): Promise<void> {\n const db = await openDatabase();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(KEY_STORE, 'readwrite');\n const store = tx.objectStore(KEY_STORE);\n const request = store.put({\n id: KEY_ID,\n privateKey,\n publicJwk,\n createdAt: Date.now(),\n });\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve();\n });\n}\n\nexport async function getOrCreateDPoPKey(): Promise<DPoPKeyData> {\n const keyData = await getDPoPKey();\n\n if (keyData) {\n return keyData;\n }\n\n // Generate new P-256 key pair\n const keyPair = await crypto.subtle.generateKey(\n { name: 'ECDSA', namedCurve: 'P-256' },\n false, // NOT extractable - critical for security\n ['sign']\n );\n\n // Export public key as JWK\n const publicJwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);\n\n // Store in IndexedDB\n await storeDPoPKey(keyPair.privateKey, publicJwk);\n\n return {\n id: KEY_ID,\n privateKey: keyPair.privateKey,\n publicJwk,\n createdAt: Date.now(),\n };\n}\n\n/**\n * Create a DPoP proof JWT\n */\nexport async function createDPoPProof(\n method: string,\n url: string,\n accessToken: string | null = null\n): Promise<string> {\n const keyData = await getOrCreateDPoPKey();\n\n // Strip WebCrypto-specific fields from JWK for interoperability\n const { kty, crv, x, y } = keyData.publicJwk;\n const minimalJwk = { kty, crv, x, y };\n\n const header = {\n alg: 'ES256',\n typ: 'dpop+jwt',\n jwk: minimalJwk,\n };\n\n const payload: Record<string, unknown> = {\n jti: generateRandomString(16),\n htm: method,\n htu: url,\n iat: Math.floor(Date.now() / 1000),\n };\n\n // Add access token hash if provided (for resource requests)\n if (accessToken) {\n payload.ath = await sha256Base64Url(accessToken);\n }\n\n return await signJwt(header, payload, keyData.privateKey);\n}\n\n/**\n * Clear DPoP keys from IndexedDB\n */\nexport async function clearDPoPKeys(): Promise<void> {\n const db = await openDatabase();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(KEY_STORE, 'readwrite');\n const store = tx.objectStore(KEY_STORE);\n const request = store.clear();\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve();\n });\n}\n", "import { base64UrlEncode, generateRandomString } from '../utils/base64url';\n\n/**\n * Generate a PKCE code verifier (32 random bytes, base64url encoded)\n */\nexport function generateCodeVerifier(): string {\n return generateRandomString(32);\n}\n\n/**\n * Generate a PKCE code challenge from a verifier (SHA-256, base64url encoded)\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const hash = await crypto.subtle.digest('SHA-256', data);\n return base64UrlEncode(hash);\n}\n\n/**\n * Generate a random state parameter for CSRF protection\n */\nexport function generateState(): string {\n return generateRandomString(16);\n}\n", "const LOCK_TIMEOUT = 5000; // 5 seconds\nconst LOCK_PREFIX = 'quickslice_lock_';\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Acquire a lock using localStorage for multi-tab coordination\n */\nexport async function acquireLock(\n key: string,\n timeout = LOCK_TIMEOUT\n): Promise<string | null> {\n const lockKey = LOCK_PREFIX + key;\n const lockValue = `${Date.now()}_${Math.random()}`;\n const deadline = Date.now() + timeout;\n\n while (Date.now() < deadline) {\n const existing = localStorage.getItem(lockKey);\n\n if (existing) {\n // Check if lock is stale (older than timeout)\n const [timestamp] = existing.split('_');\n if (Date.now() - parseInt(timestamp) > LOCK_TIMEOUT) {\n // Lock is stale, remove it\n localStorage.removeItem(lockKey);\n } else {\n // Lock is held, wait and retry\n await sleep(50);\n continue;\n }\n }\n\n // Try to acquire\n localStorage.setItem(lockKey, lockValue);\n\n // Verify we got it (handle race condition)\n await sleep(10);\n if (localStorage.getItem(lockKey) === lockValue) {\n return lockValue; // Lock acquired\n }\n }\n\n return null; // Failed to acquire\n}\n\n/**\n * Release a lock\n */\nexport function releaseLock(key: string, lockValue: string): void {\n const lockKey = LOCK_PREFIX + key;\n // Only release if we still hold it\n if (localStorage.getItem(lockKey) === lockValue) {\n localStorage.removeItem(lockKey);\n }\n}\n", "import { storage } from '../storage/storage';\nimport { STORAGE_KEYS } from '../storage/keys';\nimport { acquireLock, releaseLock } from '../storage/lock';\nimport { createDPoPProof } from './dpop';\n\nconst TOKEN_REFRESH_BUFFER_MS = 60000; // 60 seconds before expiry\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Refresh tokens using the refresh token\n */\nasync function refreshTokens(tokenUrl: string): Promise<string> {\n const refreshToken = storage.get(STORAGE_KEYS.refreshToken);\n const clientId = storage.get(STORAGE_KEYS.clientId);\n\n if (!refreshToken || !clientId) {\n throw new Error('No refresh token available');\n }\n\n const dpopProof = await createDPoPProof('POST', tokenUrl);\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n DPoP: dpopProof,\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n }),\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(\n `Token refresh failed: ${errorData.error_description || response.statusText}`\n );\n }\n\n const tokens = await response.json();\n\n // Store new tokens (rotation - new refresh token each time)\n storage.set(STORAGE_KEYS.accessToken, tokens.access_token);\n if (tokens.refresh_token) {\n storage.set(STORAGE_KEYS.refreshToken, tokens.refresh_token);\n }\n\n const expiresAt = Date.now() + tokens.expires_in * 1000;\n storage.set(STORAGE_KEYS.tokenExpiresAt, expiresAt.toString());\n\n return tokens.access_token;\n}\n\n/**\n * Get a valid access token, refreshing if necessary.\n * Uses multi-tab locking to prevent duplicate refresh requests.\n */\nexport async function getValidAccessToken(tokenUrl: string): Promise<string> {\n const accessToken = storage.get(STORAGE_KEYS.accessToken);\n const expiresAt = parseInt(storage.get(STORAGE_KEYS.tokenExpiresAt) || '0');\n\n // Check if token is still valid (with buffer)\n if (accessToken && Date.now() < expiresAt - TOKEN_REFRESH_BUFFER_MS) {\n return accessToken;\n }\n\n // Need to refresh - acquire lock first\n const clientId = storage.get(STORAGE_KEYS.clientId);\n const lockKey = `token_refresh_${clientId}`;\n const lockValue = await acquireLock(lockKey);\n\n if (!lockValue) {\n // Failed to acquire lock, another tab is refreshing\n // Wait a bit and check cache again\n await sleep(100);\n const freshToken = storage.get(STORAGE_KEYS.accessToken);\n const freshExpiry = parseInt(\n storage.get(STORAGE_KEYS.tokenExpiresAt) || '0'\n );\n if (freshToken && Date.now() < freshExpiry - TOKEN_REFRESH_BUFFER_MS) {\n return freshToken;\n }\n throw new Error('Failed to refresh token');\n }\n\n try {\n // Double-check after acquiring lock\n const freshToken = storage.get(STORAGE_KEYS.accessToken);\n const freshExpiry = parseInt(\n storage.get(STORAGE_KEYS.tokenExpiresAt) || '0'\n );\n if (freshToken && Date.now() < freshExpiry - TOKEN_REFRESH_BUFFER_MS) {\n return freshToken;\n }\n\n // Actually refresh\n return await refreshTokens(tokenUrl);\n } finally {\n releaseLock(lockKey, lockValue);\n }\n}\n\n/**\n * Store tokens from OAuth response\n */\nexport function storeTokens(tokens: {\n access_token: string;\n refresh_token?: string;\n expires_in: number;\n sub?: string;\n}): void {\n storage.set(STORAGE_KEYS.accessToken, tokens.access_token);\n if (tokens.refresh_token) {\n storage.set(STORAGE_KEYS.refreshToken, tokens.refresh_token);\n }\n\n const expiresAt = Date.now() + tokens.expires_in * 1000;\n storage.set(STORAGE_KEYS.tokenExpiresAt, expiresAt.toString());\n\n if (tokens.sub) {\n storage.set(STORAGE_KEYS.userDid, tokens.sub);\n }\n}\n\n/**\n * Check if we have a valid session\n */\nexport function hasValidSession(): boolean {\n const accessToken = storage.get(STORAGE_KEYS.accessToken);\n const refreshToken = storage.get(STORAGE_KEYS.refreshToken);\n return !!(accessToken || refreshToken);\n}\n", "import { storage } from '../storage/storage';\nimport { STORAGE_KEYS } from '../storage/keys';\nimport { createDPoPProof, clearDPoPKeys } from './dpop';\nimport { generateCodeVerifier, generateCodeChallenge, generateState } from './pkce';\nimport { storeTokens } from './tokens';\n\nexport interface LoginOptions {\n handle?: string;\n redirectUri?: string;\n}\n\n/**\n * Initiate OAuth login flow with PKCE\n */\nexport async function initiateLogin(\n authorizeUrl: string,\n clientId: string,\n options: LoginOptions = {}\n): Promise<void> {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n // Build redirect URI (use provided or derive from current page)\n const redirectUri = options.redirectUri || (window.location.origin + window.location.pathname);\n\n // Store for callback\n storage.set(STORAGE_KEYS.codeVerifier, codeVerifier);\n storage.set(STORAGE_KEYS.oauthState, state);\n storage.set(STORAGE_KEYS.clientId, clientId);\n storage.set(STORAGE_KEYS.redirectUri, redirectUri);\n\n // Build authorization URL\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n state: state,\n });\n\n if (options.handle) {\n params.set('login_hint', options.handle);\n }\n\n window.location.href = `${authorizeUrl}?${params.toString()}`;\n}\n\n/**\n * Handle OAuth callback - exchange code for tokens\n * Returns true if callback was handled, false if not a callback\n */\nexport async function handleOAuthCallback(tokenUrl: string): Promise<boolean> {\n const params = new URLSearchParams(window.location.search);\n const code = params.get('code');\n const state = params.get('state');\n const error = params.get('error');\n\n if (error) {\n throw new Error(\n `OAuth error: ${error} - ${params.get('error_description') || ''}`\n );\n }\n\n if (!code || !state) {\n return false; // Not a callback\n }\n\n // Verify state\n const storedState = storage.get(STORAGE_KEYS.oauthState);\n if (state !== storedState) {\n throw new Error('OAuth state mismatch - possible CSRF attack');\n }\n\n // Get stored values\n const codeVerifier = storage.get(STORAGE_KEYS.codeVerifier);\n const clientId = storage.get(STORAGE_KEYS.clientId);\n const redirectUri = storage.get(STORAGE_KEYS.redirectUri);\n\n if (!codeVerifier || !clientId || !redirectUri) {\n throw new Error('Missing OAuth session data');\n }\n\n // Exchange code for tokens with DPoP\n const dpopProof = await createDPoPProof('POST', tokenUrl);\n\n const tokenResponse = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n DPoP: dpopProof,\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: code,\n redirect_uri: redirectUri,\n client_id: clientId,\n code_verifier: codeVerifier,\n }),\n });\n\n if (!tokenResponse.ok) {\n const errorData = await tokenResponse.json().catch(() => ({}));\n throw new Error(\n `Token exchange failed: ${errorData.error_description || tokenResponse.statusText}`\n );\n }\n\n const tokens = await tokenResponse.json();\n\n // Store tokens\n storeTokens(tokens);\n\n // Clean up OAuth state\n storage.remove(STORAGE_KEYS.codeVerifier);\n storage.remove(STORAGE_KEYS.oauthState);\n storage.remove(STORAGE_KEYS.redirectUri);\n\n // Clear URL params\n window.history.replaceState({}, document.title, window.location.pathname);\n\n return true;\n}\n\n/**\n * Logout - clear all stored data\n */\nexport async function logout(options: { reload?: boolean } = {}): Promise<void> {\n storage.clear();\n await clearDPoPKeys();\n\n if (options.reload !== false) {\n window.location.reload();\n }\n}\n", "import { createDPoPProof } from './auth/dpop';\nimport { getValidAccessToken } from './auth/tokens';\n\nexport interface GraphQLResponse<T = unknown> {\n data?: T;\n errors?: Array<{ message: string; path?: string[] }>;\n}\n\n/**\n * Execute a GraphQL query or mutation\n */\nexport async function graphqlRequest<T = unknown>(\n graphqlUrl: string,\n tokenUrl: string,\n query: string,\n variables: Record<string, unknown> = {},\n requireAuth = false\n): Promise<T> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (requireAuth) {\n const token = await getValidAccessToken(tokenUrl);\n if (!token) {\n throw new Error('Not authenticated');\n }\n\n // Create DPoP proof bound to this request\n const dpopProof = await createDPoPProof('POST', graphqlUrl, token);\n\n headers['Authorization'] = `DPoP ${token}`;\n headers['DPoP'] = dpopProof;\n }\n\n const response = await fetch(graphqlUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify({ query, variables }),\n });\n\n if (!response.ok) {\n throw new Error(`GraphQL request failed: ${response.statusText}`);\n }\n\n const result: GraphQLResponse<T> = await response.json();\n\n if (result.errors && result.errors.length > 0) {\n throw new Error(`GraphQL error: ${result.errors[0].message}`);\n }\n\n return result.data as T;\n}\n", "import { storage } from './storage/storage';\nimport { STORAGE_KEYS } from './storage/keys';\nimport { getOrCreateDPoPKey } from './auth/dpop';\nimport { initiateLogin, handleOAuthCallback, logout as doLogout, LoginOptions } from './auth/oauth';\nimport { getValidAccessToken, hasValidSession } from './auth/tokens';\nimport { graphqlRequest } from './graphql';\n\nexport interface QuicksliceClientOptions {\n server: string;\n clientId: string;\n redirectUri?: string;\n}\n\nexport interface User {\n did: string;\n}\n\nexport class QuicksliceClient {\n private server: string;\n private clientId: string;\n private redirectUri?: string;\n private graphqlUrl: string;\n private authorizeUrl: string;\n private tokenUrl: string;\n private initialized = false;\n\n constructor(options: QuicksliceClientOptions) {\n this.server = options.server.replace(/\\/$/, ''); // Remove trailing slash\n this.clientId = options.clientId;\n this.redirectUri = options.redirectUri;\n\n this.graphqlUrl = `${this.server}/graphql`;\n this.authorizeUrl = `${this.server}/oauth/authorize`;\n this.tokenUrl = `${this.server}/oauth/token`;\n }\n\n /**\n * Initialize the client - must be called before other methods\n */\n async init(): Promise<void> {\n if (this.initialized) return;\n\n // Ensure DPoP key exists\n await getOrCreateDPoPKey();\n\n this.initialized = true;\n }\n\n /**\n * Start OAuth login flow\n */\n async loginWithRedirect(options: LoginOptions = {}): Promise<void> {\n await this.init();\n await initiateLogin(this.authorizeUrl, this.clientId, {\n ...options,\n redirectUri: options.redirectUri || this.redirectUri,\n });\n }\n\n /**\n * Handle OAuth callback after redirect\n * Returns true if callback was handled\n */\n async handleRedirectCallback(): Promise<boolean> {\n await this.init();\n return await handleOAuthCallback(this.tokenUrl);\n }\n\n /**\n * Logout and clear all stored data\n */\n async logout(options: { reload?: boolean } = {}): Promise<void> {\n await doLogout(options);\n }\n\n /**\n * Check if user is authenticated\n */\n async isAuthenticated(): Promise<boolean> {\n return hasValidSession();\n }\n\n /**\n * Get current user's DID (from stored token data)\n * For richer profile info, use client.query() with your own schema\n */\n getUser(): User | null {\n if (!hasValidSession()) {\n return null;\n }\n\n const did = storage.get(STORAGE_KEYS.userDid);\n if (!did) {\n return null;\n }\n\n return { did };\n }\n\n /**\n * Get access token (auto-refreshes if needed)\n */\n async getAccessToken(): Promise<string> {\n await this.init();\n return await getValidAccessToken(this.tokenUrl);\n }\n\n /**\n * Execute a GraphQL query (authenticated)\n */\n async query<T = unknown>(\n query: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n await this.init();\n return await graphqlRequest<T>(\n this.graphqlUrl,\n this.tokenUrl,\n query,\n variables,\n true\n );\n }\n\n /**\n * Execute a GraphQL mutation (authenticated)\n */\n async mutate<T = unknown>(\n mutation: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n return this.query<T>(mutation, variables);\n }\n\n /**\n * Execute a public GraphQL query (no auth)\n */\n async publicQuery<T = unknown>(\n query: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n await this.init();\n return await graphqlRequest<T>(\n this.graphqlUrl,\n this.tokenUrl,\n query,\n variables,\n false\n );\n }\n}\n", "/**\n * Base error class for Quickslice client errors\n */\nexport class QuicksliceError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'QuicksliceError';\n }\n}\n\n/**\n * Thrown when authentication is required but user is not logged in\n */\nexport class LoginRequiredError extends QuicksliceError {\n constructor(message = 'Login required') {\n super(message);\n this.name = 'LoginRequiredError';\n }\n}\n\n/**\n * Thrown when network request fails\n */\nexport class NetworkError extends QuicksliceError {\n constructor(message: string) {\n super(message);\n this.name = 'NetworkError';\n }\n}\n\n/**\n * Thrown when OAuth flow fails\n */\nexport class OAuthError extends QuicksliceError {\n public code: string;\n public description?: string;\n\n constructor(code: string, description?: string) {\n super(`OAuth error: ${code}${description ? ` - ${description}` : ''}`);\n this.name = 'OAuthError';\n this.code = code;\n this.description = description;\n }\n}\n", "export { QuicksliceClient, QuicksliceClientOptions, User } from './client';\nexport {\n QuicksliceError,\n LoginRequiredError,\n NetworkError,\n OAuthError,\n} from './errors';\n\nimport { QuicksliceClient, QuicksliceClientOptions } from './client';\n\n/**\n * Create and initialize a Quickslice client\n */\nexport async function createQuicksliceClient(\n options: QuicksliceClientOptions\n): Promise<QuicksliceClient> {\n const client = new QuicksliceClient(options);\n await client.init();\n return client;\n}\n"], 5 + "mappings": "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", 6 6 "names": ["sleep"] 7 7 }
+12 -5
quickslice-client-js/dist/quickslice-client.js
··· 37 37 clientId: "quickslice_client_id", 38 38 userDid: "quickslice_user_did", 39 39 codeVerifier: "quickslice_code_verifier", 40 - oauthState: "quickslice_oauth_state" 40 + oauthState: "quickslice_oauth_state", 41 + redirectUri: "quickslice_redirect_uri" 41 42 }; 42 43 43 44 // src/storage/storage.ts ··· 343 344 const codeVerifier = generateCodeVerifier(); 344 345 const codeChallenge = await generateCodeChallenge(codeVerifier); 345 346 const state = generateState(); 347 + const redirectUri = options.redirectUri || window.location.origin + window.location.pathname; 346 348 storage.set(STORAGE_KEYS.codeVerifier, codeVerifier); 347 349 storage.set(STORAGE_KEYS.oauthState, state); 348 350 storage.set(STORAGE_KEYS.clientId, clientId); 349 - const redirectUri = window.location.origin + window.location.pathname; 351 + storage.set(STORAGE_KEYS.redirectUri, redirectUri); 350 352 const params = new URLSearchParams({ 351 353 client_id: clientId, 352 354 redirect_uri: redirectUri, ··· 379 381 } 380 382 const codeVerifier = storage.get(STORAGE_KEYS.codeVerifier); 381 383 const clientId = storage.get(STORAGE_KEYS.clientId); 382 - const redirectUri = window.location.origin + window.location.pathname; 383 - if (!codeVerifier || !clientId) { 384 + const redirectUri = storage.get(STORAGE_KEYS.redirectUri); 385 + if (!codeVerifier || !clientId || !redirectUri) { 384 386 throw new Error("Missing OAuth session data"); 385 387 } 386 388 const dpopProof = await createDPoPProof("POST", tokenUrl); ··· 408 410 storeTokens(tokens); 409 411 storage.remove(STORAGE_KEYS.codeVerifier); 410 412 storage.remove(STORAGE_KEYS.oauthState); 413 + storage.remove(STORAGE_KEYS.redirectUri); 411 414 window.history.replaceState({}, document.title, window.location.pathname); 412 415 return true; 413 416 } ··· 454 457 this.initialized = false; 455 458 this.server = options.server.replace(/\/$/, ""); 456 459 this.clientId = options.clientId; 460 + this.redirectUri = options.redirectUri; 457 461 this.graphqlUrl = `${this.server}/graphql`; 458 462 this.authorizeUrl = `${this.server}/oauth/authorize`; 459 463 this.tokenUrl = `${this.server}/oauth/token`; ··· 471 475 */ 472 476 async loginWithRedirect(options = {}) { 473 477 await this.init(); 474 - await initiateLogin(this.authorizeUrl, this.clientId, options); 478 + await initiateLogin(this.authorizeUrl, this.clientId, { 479 + ...options, 480 + redirectUri: options.redirectUri || this.redirectUri 481 + }); 475 482 } 476 483 /** 477 484 * Handle OAuth callback after redirect
+2 -2
quickslice-client-js/dist/quickslice-client.js.map
··· 1 1 { 2 2 "version": 3, 3 3 "sources": ["../src/index.ts", "../src/storage/keys.ts", "../src/storage/storage.ts", "../src/utils/base64url.ts", "../src/utils/crypto.ts", "../src/auth/dpop.ts", "../src/auth/pkce.ts", "../src/storage/lock.ts", "../src/auth/tokens.ts", "../src/auth/oauth.ts", "../src/graphql.ts", "../src/client.ts", "../src/errors.ts"], 4 - "sourcesContent": ["export { QuicksliceClient, QuicksliceClientOptions, User } from './client';\nexport {\n QuicksliceError,\n LoginRequiredError,\n NetworkError,\n OAuthError,\n} from './errors';\n\nimport { QuicksliceClient, QuicksliceClientOptions } from './client';\n\n/**\n * Create and initialize a Quickslice client\n */\nexport async function createQuicksliceClient(\n options: QuicksliceClientOptions\n): Promise<QuicksliceClient> {\n const client = new QuicksliceClient(options);\n await client.init();\n return client;\n}\n", "/**\n * Storage key constants\n */\nexport const STORAGE_KEYS = {\n accessToken: 'quickslice_access_token',\n refreshToken: 'quickslice_refresh_token',\n tokenExpiresAt: 'quickslice_token_expires_at',\n clientId: 'quickslice_client_id',\n userDid: 'quickslice_user_did',\n codeVerifier: 'quickslice_code_verifier',\n oauthState: 'quickslice_oauth_state',\n} as const;\n\nexport type StorageKey = (typeof STORAGE_KEYS)[keyof typeof STORAGE_KEYS];\n", "import { STORAGE_KEYS, StorageKey } from './keys';\n\n/**\n * Hybrid storage utility - sessionStorage for OAuth flow state,\n * localStorage for tokens (shared across tabs)\n */\nexport const storage = {\n get(key: StorageKey): string | null {\n // OAuth flow state stays in sessionStorage (per-tab)\n if (key === STORAGE_KEYS.codeVerifier || key === STORAGE_KEYS.oauthState) {\n return sessionStorage.getItem(key);\n }\n // Tokens go in localStorage (shared across tabs)\n return localStorage.getItem(key);\n },\n\n set(key: StorageKey, value: string): void {\n if (key === STORAGE_KEYS.codeVerifier || key === STORAGE_KEYS.oauthState) {\n sessionStorage.setItem(key, value);\n } else {\n localStorage.setItem(key, value);\n }\n },\n\n remove(key: StorageKey): void {\n sessionStorage.removeItem(key);\n localStorage.removeItem(key);\n },\n\n clear(): void {\n Object.values(STORAGE_KEYS).forEach((key) => {\n sessionStorage.removeItem(key);\n localStorage.removeItem(key);\n });\n },\n};\n", "/**\n * Base64 URL encode a buffer (Uint8Array or ArrayBuffer)\n */\nexport function base64UrlEncode(buffer: ArrayBuffer | Uint8Array): string {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\n/**\n * Generate a random base64url string\n */\nexport function generateRandomString(byteLength: number): string {\n const bytes = new Uint8Array(byteLength);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n", "import { base64UrlEncode } from './base64url';\n\n/**\n * SHA-256 hash, returned as base64url string\n */\nexport async function sha256Base64Url(data: string): Promise<string> {\n const encoder = new TextEncoder();\n const hash = await crypto.subtle.digest('SHA-256', encoder.encode(data));\n return base64UrlEncode(hash);\n}\n\n/**\n * Sign a JWT with an ECDSA P-256 private key\n */\nexport async function signJwt(\n header: Record<string, unknown>,\n payload: Record<string, unknown>,\n privateKey: CryptoKey\n): Promise<string> {\n const encoder = new TextEncoder();\n\n const headerB64 = base64UrlEncode(encoder.encode(JSON.stringify(header)));\n const payloadB64 = base64UrlEncode(encoder.encode(JSON.stringify(payload)));\n\n const signingInput = `${headerB64}.${payloadB64}`;\n\n const signature = await crypto.subtle.sign(\n { name: 'ECDSA', hash: 'SHA-256' },\n privateKey,\n encoder.encode(signingInput)\n );\n\n const signatureB64 = base64UrlEncode(signature);\n\n return `${signingInput}.${signatureB64}`;\n}\n", "import { generateRandomString } from '../utils/base64url';\nimport { sha256Base64Url, signJwt } from '../utils/crypto';\n\nconst DB_NAME = 'quickslice-oauth';\nconst DB_VERSION = 1;\nconst KEY_STORE = 'dpop-keys';\nconst KEY_ID = 'dpop-key';\n\ninterface DPoPKeyData {\n id: string;\n privateKey: CryptoKey;\n publicJwk: JsonWebKey;\n createdAt: number;\n}\n\nlet dbPromise: Promise<IDBDatabase> | null = null;\n\nfunction openDatabase(): Promise<IDBDatabase> {\n if (dbPromise) return dbPromise;\n\n dbPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result);\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n if (!db.objectStoreNames.contains(KEY_STORE)) {\n db.createObjectStore(KEY_STORE, { keyPath: 'id' });\n }\n };\n });\n\n return dbPromise;\n}\n\nasync function getDPoPKey(): Promise<DPoPKeyData | null> {\n const db = await openDatabase();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(KEY_STORE, 'readonly');\n const store = tx.objectStore(KEY_STORE);\n const request = store.get(KEY_ID);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result || null);\n });\n}\n\nasync function storeDPoPKey(\n privateKey: CryptoKey,\n publicJwk: JsonWebKey\n): Promise<void> {\n const db = await openDatabase();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(KEY_STORE, 'readwrite');\n const store = tx.objectStore(KEY_STORE);\n const request = store.put({\n id: KEY_ID,\n privateKey,\n publicJwk,\n createdAt: Date.now(),\n });\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve();\n });\n}\n\nexport async function getOrCreateDPoPKey(): Promise<DPoPKeyData> {\n const keyData = await getDPoPKey();\n\n if (keyData) {\n return keyData;\n }\n\n // Generate new P-256 key pair\n const keyPair = await crypto.subtle.generateKey(\n { name: 'ECDSA', namedCurve: 'P-256' },\n false, // NOT extractable - critical for security\n ['sign']\n );\n\n // Export public key as JWK\n const publicJwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);\n\n // Store in IndexedDB\n await storeDPoPKey(keyPair.privateKey, publicJwk);\n\n return {\n id: KEY_ID,\n privateKey: keyPair.privateKey,\n publicJwk,\n createdAt: Date.now(),\n };\n}\n\n/**\n * Create a DPoP proof JWT\n */\nexport async function createDPoPProof(\n method: string,\n url: string,\n accessToken: string | null = null\n): Promise<string> {\n const keyData = await getOrCreateDPoPKey();\n\n // Strip WebCrypto-specific fields from JWK for interoperability\n const { kty, crv, x, y } = keyData.publicJwk;\n const minimalJwk = { kty, crv, x, y };\n\n const header = {\n alg: 'ES256',\n typ: 'dpop+jwt',\n jwk: minimalJwk,\n };\n\n const payload: Record<string, unknown> = {\n jti: generateRandomString(16),\n htm: method,\n htu: url,\n iat: Math.floor(Date.now() / 1000),\n };\n\n // Add access token hash if provided (for resource requests)\n if (accessToken) {\n payload.ath = await sha256Base64Url(accessToken);\n }\n\n return await signJwt(header, payload, keyData.privateKey);\n}\n\n/**\n * Clear DPoP keys from IndexedDB\n */\nexport async function clearDPoPKeys(): Promise<void> {\n const db = await openDatabase();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(KEY_STORE, 'readwrite');\n const store = tx.objectStore(KEY_STORE);\n const request = store.clear();\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve();\n });\n}\n", "import { base64UrlEncode, generateRandomString } from '../utils/base64url';\n\n/**\n * Generate a PKCE code verifier (32 random bytes, base64url encoded)\n */\nexport function generateCodeVerifier(): string {\n return generateRandomString(32);\n}\n\n/**\n * Generate a PKCE code challenge from a verifier (SHA-256, base64url encoded)\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const hash = await crypto.subtle.digest('SHA-256', data);\n return base64UrlEncode(hash);\n}\n\n/**\n * Generate a random state parameter for CSRF protection\n */\nexport function generateState(): string {\n return generateRandomString(16);\n}\n", "const LOCK_TIMEOUT = 5000; // 5 seconds\nconst LOCK_PREFIX = 'quickslice_lock_';\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Acquire a lock using localStorage for multi-tab coordination\n */\nexport async function acquireLock(\n key: string,\n timeout = LOCK_TIMEOUT\n): Promise<string | null> {\n const lockKey = LOCK_PREFIX + key;\n const lockValue = `${Date.now()}_${Math.random()}`;\n const deadline = Date.now() + timeout;\n\n while (Date.now() < deadline) {\n const existing = localStorage.getItem(lockKey);\n\n if (existing) {\n // Check if lock is stale (older than timeout)\n const [timestamp] = existing.split('_');\n if (Date.now() - parseInt(timestamp) > LOCK_TIMEOUT) {\n // Lock is stale, remove it\n localStorage.removeItem(lockKey);\n } else {\n // Lock is held, wait and retry\n await sleep(50);\n continue;\n }\n }\n\n // Try to acquire\n localStorage.setItem(lockKey, lockValue);\n\n // Verify we got it (handle race condition)\n await sleep(10);\n if (localStorage.getItem(lockKey) === lockValue) {\n return lockValue; // Lock acquired\n }\n }\n\n return null; // Failed to acquire\n}\n\n/**\n * Release a lock\n */\nexport function releaseLock(key: string, lockValue: string): void {\n const lockKey = LOCK_PREFIX + key;\n // Only release if we still hold it\n if (localStorage.getItem(lockKey) === lockValue) {\n localStorage.removeItem(lockKey);\n }\n}\n", "import { storage } from '../storage/storage';\nimport { STORAGE_KEYS } from '../storage/keys';\nimport { acquireLock, releaseLock } from '../storage/lock';\nimport { createDPoPProof } from './dpop';\n\nconst TOKEN_REFRESH_BUFFER_MS = 60000; // 60 seconds before expiry\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Refresh tokens using the refresh token\n */\nasync function refreshTokens(tokenUrl: string): Promise<string> {\n const refreshToken = storage.get(STORAGE_KEYS.refreshToken);\n const clientId = storage.get(STORAGE_KEYS.clientId);\n\n if (!refreshToken || !clientId) {\n throw new Error('No refresh token available');\n }\n\n const dpopProof = await createDPoPProof('POST', tokenUrl);\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n DPoP: dpopProof,\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n }),\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(\n `Token refresh failed: ${errorData.error_description || response.statusText}`\n );\n }\n\n const tokens = await response.json();\n\n // Store new tokens (rotation - new refresh token each time)\n storage.set(STORAGE_KEYS.accessToken, tokens.access_token);\n if (tokens.refresh_token) {\n storage.set(STORAGE_KEYS.refreshToken, tokens.refresh_token);\n }\n\n const expiresAt = Date.now() + tokens.expires_in * 1000;\n storage.set(STORAGE_KEYS.tokenExpiresAt, expiresAt.toString());\n\n return tokens.access_token;\n}\n\n/**\n * Get a valid access token, refreshing if necessary.\n * Uses multi-tab locking to prevent duplicate refresh requests.\n */\nexport async function getValidAccessToken(tokenUrl: string): Promise<string> {\n const accessToken = storage.get(STORAGE_KEYS.accessToken);\n const expiresAt = parseInt(storage.get(STORAGE_KEYS.tokenExpiresAt) || '0');\n\n // Check if token is still valid (with buffer)\n if (accessToken && Date.now() < expiresAt - TOKEN_REFRESH_BUFFER_MS) {\n return accessToken;\n }\n\n // Need to refresh - acquire lock first\n const clientId = storage.get(STORAGE_KEYS.clientId);\n const lockKey = `token_refresh_${clientId}`;\n const lockValue = await acquireLock(lockKey);\n\n if (!lockValue) {\n // Failed to acquire lock, another tab is refreshing\n // Wait a bit and check cache again\n await sleep(100);\n const freshToken = storage.get(STORAGE_KEYS.accessToken);\n const freshExpiry = parseInt(\n storage.get(STORAGE_KEYS.tokenExpiresAt) || '0'\n );\n if (freshToken && Date.now() < freshExpiry - TOKEN_REFRESH_BUFFER_MS) {\n return freshToken;\n }\n throw new Error('Failed to refresh token');\n }\n\n try {\n // Double-check after acquiring lock\n const freshToken = storage.get(STORAGE_KEYS.accessToken);\n const freshExpiry = parseInt(\n storage.get(STORAGE_KEYS.tokenExpiresAt) || '0'\n );\n if (freshToken && Date.now() < freshExpiry - TOKEN_REFRESH_BUFFER_MS) {\n return freshToken;\n }\n\n // Actually refresh\n return await refreshTokens(tokenUrl);\n } finally {\n releaseLock(lockKey, lockValue);\n }\n}\n\n/**\n * Store tokens from OAuth response\n */\nexport function storeTokens(tokens: {\n access_token: string;\n refresh_token?: string;\n expires_in: number;\n sub?: string;\n}): void {\n storage.set(STORAGE_KEYS.accessToken, tokens.access_token);\n if (tokens.refresh_token) {\n storage.set(STORAGE_KEYS.refreshToken, tokens.refresh_token);\n }\n\n const expiresAt = Date.now() + tokens.expires_in * 1000;\n storage.set(STORAGE_KEYS.tokenExpiresAt, expiresAt.toString());\n\n if (tokens.sub) {\n storage.set(STORAGE_KEYS.userDid, tokens.sub);\n }\n}\n\n/**\n * Check if we have a valid session\n */\nexport function hasValidSession(): boolean {\n const accessToken = storage.get(STORAGE_KEYS.accessToken);\n const refreshToken = storage.get(STORAGE_KEYS.refreshToken);\n return !!(accessToken || refreshToken);\n}\n", "import { storage } from '../storage/storage';\nimport { STORAGE_KEYS } from '../storage/keys';\nimport { createDPoPProof, clearDPoPKeys } from './dpop';\nimport { generateCodeVerifier, generateCodeChallenge, generateState } from './pkce';\nimport { storeTokens } from './tokens';\n\nexport interface LoginOptions {\n handle?: string;\n}\n\n/**\n * Initiate OAuth login flow with PKCE\n */\nexport async function initiateLogin(\n authorizeUrl: string,\n clientId: string,\n options: LoginOptions = {}\n): Promise<void> {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n // Store for callback\n storage.set(STORAGE_KEYS.codeVerifier, codeVerifier);\n storage.set(STORAGE_KEYS.oauthState, state);\n storage.set(STORAGE_KEYS.clientId, clientId);\n\n // Build redirect URI (current page without query params)\n const redirectUri = window.location.origin + window.location.pathname;\n\n // Build authorization URL\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n state: state,\n });\n\n if (options.handle) {\n params.set('login_hint', options.handle);\n }\n\n window.location.href = `${authorizeUrl}?${params.toString()}`;\n}\n\n/**\n * Handle OAuth callback - exchange code for tokens\n * Returns true if callback was handled, false if not a callback\n */\nexport async function handleOAuthCallback(tokenUrl: string): Promise<boolean> {\n const params = new URLSearchParams(window.location.search);\n const code = params.get('code');\n const state = params.get('state');\n const error = params.get('error');\n\n if (error) {\n throw new Error(\n `OAuth error: ${error} - ${params.get('error_description') || ''}`\n );\n }\n\n if (!code || !state) {\n return false; // Not a callback\n }\n\n // Verify state\n const storedState = storage.get(STORAGE_KEYS.oauthState);\n if (state !== storedState) {\n throw new Error('OAuth state mismatch - possible CSRF attack');\n }\n\n // Get stored values\n const codeVerifier = storage.get(STORAGE_KEYS.codeVerifier);\n const clientId = storage.get(STORAGE_KEYS.clientId);\n const redirectUri = window.location.origin + window.location.pathname;\n\n if (!codeVerifier || !clientId) {\n throw new Error('Missing OAuth session data');\n }\n\n // Exchange code for tokens with DPoP\n const dpopProof = await createDPoPProof('POST', tokenUrl);\n\n const tokenResponse = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n DPoP: dpopProof,\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: code,\n redirect_uri: redirectUri,\n client_id: clientId,\n code_verifier: codeVerifier,\n }),\n });\n\n if (!tokenResponse.ok) {\n const errorData = await tokenResponse.json().catch(() => ({}));\n throw new Error(\n `Token exchange failed: ${errorData.error_description || tokenResponse.statusText}`\n );\n }\n\n const tokens = await tokenResponse.json();\n\n // Store tokens\n storeTokens(tokens);\n\n // Clean up OAuth state\n storage.remove(STORAGE_KEYS.codeVerifier);\n storage.remove(STORAGE_KEYS.oauthState);\n\n // Clear URL params\n window.history.replaceState({}, document.title, window.location.pathname);\n\n return true;\n}\n\n/**\n * Logout - clear all stored data\n */\nexport async function logout(options: { reload?: boolean } = {}): Promise<void> {\n storage.clear();\n await clearDPoPKeys();\n\n if (options.reload !== false) {\n window.location.reload();\n }\n}\n", "import { createDPoPProof } from './auth/dpop';\nimport { getValidAccessToken } from './auth/tokens';\n\nexport interface GraphQLResponse<T = unknown> {\n data?: T;\n errors?: Array<{ message: string; path?: string[] }>;\n}\n\n/**\n * Execute a GraphQL query or mutation\n */\nexport async function graphqlRequest<T = unknown>(\n graphqlUrl: string,\n tokenUrl: string,\n query: string,\n variables: Record<string, unknown> = {},\n requireAuth = false\n): Promise<T> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (requireAuth) {\n const token = await getValidAccessToken(tokenUrl);\n if (!token) {\n throw new Error('Not authenticated');\n }\n\n // Create DPoP proof bound to this request\n const dpopProof = await createDPoPProof('POST', graphqlUrl, token);\n\n headers['Authorization'] = `DPoP ${token}`;\n headers['DPoP'] = dpopProof;\n }\n\n const response = await fetch(graphqlUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify({ query, variables }),\n });\n\n if (!response.ok) {\n throw new Error(`GraphQL request failed: ${response.statusText}`);\n }\n\n const result: GraphQLResponse<T> = await response.json();\n\n if (result.errors && result.errors.length > 0) {\n throw new Error(`GraphQL error: ${result.errors[0].message}`);\n }\n\n return result.data as T;\n}\n", "import { storage } from './storage/storage';\nimport { STORAGE_KEYS } from './storage/keys';\nimport { getOrCreateDPoPKey } from './auth/dpop';\nimport { initiateLogin, handleOAuthCallback, logout as doLogout, LoginOptions } from './auth/oauth';\nimport { getValidAccessToken, hasValidSession } from './auth/tokens';\nimport { graphqlRequest } from './graphql';\n\nexport interface QuicksliceClientOptions {\n server: string;\n clientId: string;\n}\n\nexport interface User {\n did: string;\n}\n\nexport class QuicksliceClient {\n private server: string;\n private clientId: string;\n private graphqlUrl: string;\n private authorizeUrl: string;\n private tokenUrl: string;\n private initialized = false;\n\n constructor(options: QuicksliceClientOptions) {\n this.server = options.server.replace(/\\/$/, ''); // Remove trailing slash\n this.clientId = options.clientId;\n\n this.graphqlUrl = `${this.server}/graphql`;\n this.authorizeUrl = `${this.server}/oauth/authorize`;\n this.tokenUrl = `${this.server}/oauth/token`;\n }\n\n /**\n * Initialize the client - must be called before other methods\n */\n async init(): Promise<void> {\n if (this.initialized) return;\n\n // Ensure DPoP key exists\n await getOrCreateDPoPKey();\n\n this.initialized = true;\n }\n\n /**\n * Start OAuth login flow\n */\n async loginWithRedirect(options: LoginOptions = {}): Promise<void> {\n await this.init();\n await initiateLogin(this.authorizeUrl, this.clientId, options);\n }\n\n /**\n * Handle OAuth callback after redirect\n * Returns true if callback was handled\n */\n async handleRedirectCallback(): Promise<boolean> {\n await this.init();\n return await handleOAuthCallback(this.tokenUrl);\n }\n\n /**\n * Logout and clear all stored data\n */\n async logout(options: { reload?: boolean } = {}): Promise<void> {\n await doLogout(options);\n }\n\n /**\n * Check if user is authenticated\n */\n async isAuthenticated(): Promise<boolean> {\n return hasValidSession();\n }\n\n /**\n * Get current user's DID (from stored token data)\n * For richer profile info, use client.query() with your own schema\n */\n getUser(): User | null {\n if (!hasValidSession()) {\n return null;\n }\n\n const did = storage.get(STORAGE_KEYS.userDid);\n if (!did) {\n return null;\n }\n\n return { did };\n }\n\n /**\n * Get access token (auto-refreshes if needed)\n */\n async getAccessToken(): Promise<string> {\n await this.init();\n return await getValidAccessToken(this.tokenUrl);\n }\n\n /**\n * Execute a GraphQL query (authenticated)\n */\n async query<T = unknown>(\n query: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n await this.init();\n return await graphqlRequest<T>(\n this.graphqlUrl,\n this.tokenUrl,\n query,\n variables,\n true\n );\n }\n\n /**\n * Execute a GraphQL mutation (authenticated)\n */\n async mutate<T = unknown>(\n mutation: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n return this.query<T>(mutation, variables);\n }\n\n /**\n * Execute a public GraphQL query (no auth)\n */\n async publicQuery<T = unknown>(\n query: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n await this.init();\n return await graphqlRequest<T>(\n this.graphqlUrl,\n this.tokenUrl,\n query,\n variables,\n false\n );\n }\n}\n", "/**\n * Base error class for Quickslice client errors\n */\nexport class QuicksliceError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'QuicksliceError';\n }\n}\n\n/**\n * Thrown when authentication is required but user is not logged in\n */\nexport class LoginRequiredError extends QuicksliceError {\n constructor(message = 'Login required') {\n super(message);\n this.name = 'LoginRequiredError';\n }\n}\n\n/**\n * Thrown when network request fails\n */\nexport class NetworkError extends QuicksliceError {\n constructor(message: string) {\n super(message);\n this.name = 'NetworkError';\n }\n}\n\n/**\n * Thrown when OAuth flow fails\n */\nexport class OAuthError extends QuicksliceError {\n public code: string;\n public description?: string;\n\n constructor(code: string, description?: string) {\n super(`OAuth error: ${code}${description ? ` - ${description}` : ''}`);\n this.name = 'OAuthError';\n this.code = code;\n this.description = description;\n }\n}\n"], 5 - "mappings": "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", 4 + "sourcesContent": ["export { QuicksliceClient, QuicksliceClientOptions, User } from './client';\nexport {\n QuicksliceError,\n LoginRequiredError,\n NetworkError,\n OAuthError,\n} from './errors';\n\nimport { QuicksliceClient, QuicksliceClientOptions } from './client';\n\n/**\n * Create and initialize a Quickslice client\n */\nexport async function createQuicksliceClient(\n options: QuicksliceClientOptions\n): Promise<QuicksliceClient> {\n const client = new QuicksliceClient(options);\n await client.init();\n return client;\n}\n", "/**\n * Storage key constants\n */\nexport const STORAGE_KEYS = {\n accessToken: 'quickslice_access_token',\n refreshToken: 'quickslice_refresh_token',\n tokenExpiresAt: 'quickslice_token_expires_at',\n clientId: 'quickslice_client_id',\n userDid: 'quickslice_user_did',\n codeVerifier: 'quickslice_code_verifier',\n oauthState: 'quickslice_oauth_state',\n redirectUri: 'quickslice_redirect_uri',\n} as const;\n\nexport type StorageKey = (typeof STORAGE_KEYS)[keyof typeof STORAGE_KEYS];\n", "import { STORAGE_KEYS, StorageKey } from './keys';\n\n/**\n * Hybrid storage utility - sessionStorage for OAuth flow state,\n * localStorage for tokens (shared across tabs)\n */\nexport const storage = {\n get(key: StorageKey): string | null {\n // OAuth flow state stays in sessionStorage (per-tab)\n if (key === STORAGE_KEYS.codeVerifier || key === STORAGE_KEYS.oauthState) {\n return sessionStorage.getItem(key);\n }\n // Tokens go in localStorage (shared across tabs)\n return localStorage.getItem(key);\n },\n\n set(key: StorageKey, value: string): void {\n if (key === STORAGE_KEYS.codeVerifier || key === STORAGE_KEYS.oauthState) {\n sessionStorage.setItem(key, value);\n } else {\n localStorage.setItem(key, value);\n }\n },\n\n remove(key: StorageKey): void {\n sessionStorage.removeItem(key);\n localStorage.removeItem(key);\n },\n\n clear(): void {\n Object.values(STORAGE_KEYS).forEach((key) => {\n sessionStorage.removeItem(key);\n localStorage.removeItem(key);\n });\n },\n};\n", "/**\n * Base64 URL encode a buffer (Uint8Array or ArrayBuffer)\n */\nexport function base64UrlEncode(buffer: ArrayBuffer | Uint8Array): string {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\n/**\n * Generate a random base64url string\n */\nexport function generateRandomString(byteLength: number): string {\n const bytes = new Uint8Array(byteLength);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n", "import { base64UrlEncode } from './base64url';\n\n/**\n * SHA-256 hash, returned as base64url string\n */\nexport async function sha256Base64Url(data: string): Promise<string> {\n const encoder = new TextEncoder();\n const hash = await crypto.subtle.digest('SHA-256', encoder.encode(data));\n return base64UrlEncode(hash);\n}\n\n/**\n * Sign a JWT with an ECDSA P-256 private key\n */\nexport async function signJwt(\n header: Record<string, unknown>,\n payload: Record<string, unknown>,\n privateKey: CryptoKey\n): Promise<string> {\n const encoder = new TextEncoder();\n\n const headerB64 = base64UrlEncode(encoder.encode(JSON.stringify(header)));\n const payloadB64 = base64UrlEncode(encoder.encode(JSON.stringify(payload)));\n\n const signingInput = `${headerB64}.${payloadB64}`;\n\n const signature = await crypto.subtle.sign(\n { name: 'ECDSA', hash: 'SHA-256' },\n privateKey,\n encoder.encode(signingInput)\n );\n\n const signatureB64 = base64UrlEncode(signature);\n\n return `${signingInput}.${signatureB64}`;\n}\n", "import { generateRandomString } from '../utils/base64url';\nimport { sha256Base64Url, signJwt } from '../utils/crypto';\n\nconst DB_NAME = 'quickslice-oauth';\nconst DB_VERSION = 1;\nconst KEY_STORE = 'dpop-keys';\nconst KEY_ID = 'dpop-key';\n\ninterface DPoPKeyData {\n id: string;\n privateKey: CryptoKey;\n publicJwk: JsonWebKey;\n createdAt: number;\n}\n\nlet dbPromise: Promise<IDBDatabase> | null = null;\n\nfunction openDatabase(): Promise<IDBDatabase> {\n if (dbPromise) return dbPromise;\n\n dbPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result);\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n if (!db.objectStoreNames.contains(KEY_STORE)) {\n db.createObjectStore(KEY_STORE, { keyPath: 'id' });\n }\n };\n });\n\n return dbPromise;\n}\n\nasync function getDPoPKey(): Promise<DPoPKeyData | null> {\n const db = await openDatabase();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(KEY_STORE, 'readonly');\n const store = tx.objectStore(KEY_STORE);\n const request = store.get(KEY_ID);\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result || null);\n });\n}\n\nasync function storeDPoPKey(\n privateKey: CryptoKey,\n publicJwk: JsonWebKey\n): Promise<void> {\n const db = await openDatabase();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(KEY_STORE, 'readwrite');\n const store = tx.objectStore(KEY_STORE);\n const request = store.put({\n id: KEY_ID,\n privateKey,\n publicJwk,\n createdAt: Date.now(),\n });\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve();\n });\n}\n\nexport async function getOrCreateDPoPKey(): Promise<DPoPKeyData> {\n const keyData = await getDPoPKey();\n\n if (keyData) {\n return keyData;\n }\n\n // Generate new P-256 key pair\n const keyPair = await crypto.subtle.generateKey(\n { name: 'ECDSA', namedCurve: 'P-256' },\n false, // NOT extractable - critical for security\n ['sign']\n );\n\n // Export public key as JWK\n const publicJwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);\n\n // Store in IndexedDB\n await storeDPoPKey(keyPair.privateKey, publicJwk);\n\n return {\n id: KEY_ID,\n privateKey: keyPair.privateKey,\n publicJwk,\n createdAt: Date.now(),\n };\n}\n\n/**\n * Create a DPoP proof JWT\n */\nexport async function createDPoPProof(\n method: string,\n url: string,\n accessToken: string | null = null\n): Promise<string> {\n const keyData = await getOrCreateDPoPKey();\n\n // Strip WebCrypto-specific fields from JWK for interoperability\n const { kty, crv, x, y } = keyData.publicJwk;\n const minimalJwk = { kty, crv, x, y };\n\n const header = {\n alg: 'ES256',\n typ: 'dpop+jwt',\n jwk: minimalJwk,\n };\n\n const payload: Record<string, unknown> = {\n jti: generateRandomString(16),\n htm: method,\n htu: url,\n iat: Math.floor(Date.now() / 1000),\n };\n\n // Add access token hash if provided (for resource requests)\n if (accessToken) {\n payload.ath = await sha256Base64Url(accessToken);\n }\n\n return await signJwt(header, payload, keyData.privateKey);\n}\n\n/**\n * Clear DPoP keys from IndexedDB\n */\nexport async function clearDPoPKeys(): Promise<void> {\n const db = await openDatabase();\n return new Promise((resolve, reject) => {\n const tx = db.transaction(KEY_STORE, 'readwrite');\n const store = tx.objectStore(KEY_STORE);\n const request = store.clear();\n\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve();\n });\n}\n", "import { base64UrlEncode, generateRandomString } from '../utils/base64url';\n\n/**\n * Generate a PKCE code verifier (32 random bytes, base64url encoded)\n */\nexport function generateCodeVerifier(): string {\n return generateRandomString(32);\n}\n\n/**\n * Generate a PKCE code challenge from a verifier (SHA-256, base64url encoded)\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const hash = await crypto.subtle.digest('SHA-256', data);\n return base64UrlEncode(hash);\n}\n\n/**\n * Generate a random state parameter for CSRF protection\n */\nexport function generateState(): string {\n return generateRandomString(16);\n}\n", "const LOCK_TIMEOUT = 5000; // 5 seconds\nconst LOCK_PREFIX = 'quickslice_lock_';\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Acquire a lock using localStorage for multi-tab coordination\n */\nexport async function acquireLock(\n key: string,\n timeout = LOCK_TIMEOUT\n): Promise<string | null> {\n const lockKey = LOCK_PREFIX + key;\n const lockValue = `${Date.now()}_${Math.random()}`;\n const deadline = Date.now() + timeout;\n\n while (Date.now() < deadline) {\n const existing = localStorage.getItem(lockKey);\n\n if (existing) {\n // Check if lock is stale (older than timeout)\n const [timestamp] = existing.split('_');\n if (Date.now() - parseInt(timestamp) > LOCK_TIMEOUT) {\n // Lock is stale, remove it\n localStorage.removeItem(lockKey);\n } else {\n // Lock is held, wait and retry\n await sleep(50);\n continue;\n }\n }\n\n // Try to acquire\n localStorage.setItem(lockKey, lockValue);\n\n // Verify we got it (handle race condition)\n await sleep(10);\n if (localStorage.getItem(lockKey) === lockValue) {\n return lockValue; // Lock acquired\n }\n }\n\n return null; // Failed to acquire\n}\n\n/**\n * Release a lock\n */\nexport function releaseLock(key: string, lockValue: string): void {\n const lockKey = LOCK_PREFIX + key;\n // Only release if we still hold it\n if (localStorage.getItem(lockKey) === lockValue) {\n localStorage.removeItem(lockKey);\n }\n}\n", "import { storage } from '../storage/storage';\nimport { STORAGE_KEYS } from '../storage/keys';\nimport { acquireLock, releaseLock } from '../storage/lock';\nimport { createDPoPProof } from './dpop';\n\nconst TOKEN_REFRESH_BUFFER_MS = 60000; // 60 seconds before expiry\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Refresh tokens using the refresh token\n */\nasync function refreshTokens(tokenUrl: string): Promise<string> {\n const refreshToken = storage.get(STORAGE_KEYS.refreshToken);\n const clientId = storage.get(STORAGE_KEYS.clientId);\n\n if (!refreshToken || !clientId) {\n throw new Error('No refresh token available');\n }\n\n const dpopProof = await createDPoPProof('POST', tokenUrl);\n\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n DPoP: dpopProof,\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n }),\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(\n `Token refresh failed: ${errorData.error_description || response.statusText}`\n );\n }\n\n const tokens = await response.json();\n\n // Store new tokens (rotation - new refresh token each time)\n storage.set(STORAGE_KEYS.accessToken, tokens.access_token);\n if (tokens.refresh_token) {\n storage.set(STORAGE_KEYS.refreshToken, tokens.refresh_token);\n }\n\n const expiresAt = Date.now() + tokens.expires_in * 1000;\n storage.set(STORAGE_KEYS.tokenExpiresAt, expiresAt.toString());\n\n return tokens.access_token;\n}\n\n/**\n * Get a valid access token, refreshing if necessary.\n * Uses multi-tab locking to prevent duplicate refresh requests.\n */\nexport async function getValidAccessToken(tokenUrl: string): Promise<string> {\n const accessToken = storage.get(STORAGE_KEYS.accessToken);\n const expiresAt = parseInt(storage.get(STORAGE_KEYS.tokenExpiresAt) || '0');\n\n // Check if token is still valid (with buffer)\n if (accessToken && Date.now() < expiresAt - TOKEN_REFRESH_BUFFER_MS) {\n return accessToken;\n }\n\n // Need to refresh - acquire lock first\n const clientId = storage.get(STORAGE_KEYS.clientId);\n const lockKey = `token_refresh_${clientId}`;\n const lockValue = await acquireLock(lockKey);\n\n if (!lockValue) {\n // Failed to acquire lock, another tab is refreshing\n // Wait a bit and check cache again\n await sleep(100);\n const freshToken = storage.get(STORAGE_KEYS.accessToken);\n const freshExpiry = parseInt(\n storage.get(STORAGE_KEYS.tokenExpiresAt) || '0'\n );\n if (freshToken && Date.now() < freshExpiry - TOKEN_REFRESH_BUFFER_MS) {\n return freshToken;\n }\n throw new Error('Failed to refresh token');\n }\n\n try {\n // Double-check after acquiring lock\n const freshToken = storage.get(STORAGE_KEYS.accessToken);\n const freshExpiry = parseInt(\n storage.get(STORAGE_KEYS.tokenExpiresAt) || '0'\n );\n if (freshToken && Date.now() < freshExpiry - TOKEN_REFRESH_BUFFER_MS) {\n return freshToken;\n }\n\n // Actually refresh\n return await refreshTokens(tokenUrl);\n } finally {\n releaseLock(lockKey, lockValue);\n }\n}\n\n/**\n * Store tokens from OAuth response\n */\nexport function storeTokens(tokens: {\n access_token: string;\n refresh_token?: string;\n expires_in: number;\n sub?: string;\n}): void {\n storage.set(STORAGE_KEYS.accessToken, tokens.access_token);\n if (tokens.refresh_token) {\n storage.set(STORAGE_KEYS.refreshToken, tokens.refresh_token);\n }\n\n const expiresAt = Date.now() + tokens.expires_in * 1000;\n storage.set(STORAGE_KEYS.tokenExpiresAt, expiresAt.toString());\n\n if (tokens.sub) {\n storage.set(STORAGE_KEYS.userDid, tokens.sub);\n }\n}\n\n/**\n * Check if we have a valid session\n */\nexport function hasValidSession(): boolean {\n const accessToken = storage.get(STORAGE_KEYS.accessToken);\n const refreshToken = storage.get(STORAGE_KEYS.refreshToken);\n return !!(accessToken || refreshToken);\n}\n", "import { storage } from '../storage/storage';\nimport { STORAGE_KEYS } from '../storage/keys';\nimport { createDPoPProof, clearDPoPKeys } from './dpop';\nimport { generateCodeVerifier, generateCodeChallenge, generateState } from './pkce';\nimport { storeTokens } from './tokens';\n\nexport interface LoginOptions {\n handle?: string;\n redirectUri?: string;\n}\n\n/**\n * Initiate OAuth login flow with PKCE\n */\nexport async function initiateLogin(\n authorizeUrl: string,\n clientId: string,\n options: LoginOptions = {}\n): Promise<void> {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n const state = generateState();\n\n // Build redirect URI (use provided or derive from current page)\n const redirectUri = options.redirectUri || (window.location.origin + window.location.pathname);\n\n // Store for callback\n storage.set(STORAGE_KEYS.codeVerifier, codeVerifier);\n storage.set(STORAGE_KEYS.oauthState, state);\n storage.set(STORAGE_KEYS.clientId, clientId);\n storage.set(STORAGE_KEYS.redirectUri, redirectUri);\n\n // Build authorization URL\n const params = new URLSearchParams({\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: 'code',\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n state: state,\n });\n\n if (options.handle) {\n params.set('login_hint', options.handle);\n }\n\n window.location.href = `${authorizeUrl}?${params.toString()}`;\n}\n\n/**\n * Handle OAuth callback - exchange code for tokens\n * Returns true if callback was handled, false if not a callback\n */\nexport async function handleOAuthCallback(tokenUrl: string): Promise<boolean> {\n const params = new URLSearchParams(window.location.search);\n const code = params.get('code');\n const state = params.get('state');\n const error = params.get('error');\n\n if (error) {\n throw new Error(\n `OAuth error: ${error} - ${params.get('error_description') || ''}`\n );\n }\n\n if (!code || !state) {\n return false; // Not a callback\n }\n\n // Verify state\n const storedState = storage.get(STORAGE_KEYS.oauthState);\n if (state !== storedState) {\n throw new Error('OAuth state mismatch - possible CSRF attack');\n }\n\n // Get stored values\n const codeVerifier = storage.get(STORAGE_KEYS.codeVerifier);\n const clientId = storage.get(STORAGE_KEYS.clientId);\n const redirectUri = storage.get(STORAGE_KEYS.redirectUri);\n\n if (!codeVerifier || !clientId || !redirectUri) {\n throw new Error('Missing OAuth session data');\n }\n\n // Exchange code for tokens with DPoP\n const dpopProof = await createDPoPProof('POST', tokenUrl);\n\n const tokenResponse = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n DPoP: dpopProof,\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: code,\n redirect_uri: redirectUri,\n client_id: clientId,\n code_verifier: codeVerifier,\n }),\n });\n\n if (!tokenResponse.ok) {\n const errorData = await tokenResponse.json().catch(() => ({}));\n throw new Error(\n `Token exchange failed: ${errorData.error_description || tokenResponse.statusText}`\n );\n }\n\n const tokens = await tokenResponse.json();\n\n // Store tokens\n storeTokens(tokens);\n\n // Clean up OAuth state\n storage.remove(STORAGE_KEYS.codeVerifier);\n storage.remove(STORAGE_KEYS.oauthState);\n storage.remove(STORAGE_KEYS.redirectUri);\n\n // Clear URL params\n window.history.replaceState({}, document.title, window.location.pathname);\n\n return true;\n}\n\n/**\n * Logout - clear all stored data\n */\nexport async function logout(options: { reload?: boolean } = {}): Promise<void> {\n storage.clear();\n await clearDPoPKeys();\n\n if (options.reload !== false) {\n window.location.reload();\n }\n}\n", "import { createDPoPProof } from './auth/dpop';\nimport { getValidAccessToken } from './auth/tokens';\n\nexport interface GraphQLResponse<T = unknown> {\n data?: T;\n errors?: Array<{ message: string; path?: string[] }>;\n}\n\n/**\n * Execute a GraphQL query or mutation\n */\nexport async function graphqlRequest<T = unknown>(\n graphqlUrl: string,\n tokenUrl: string,\n query: string,\n variables: Record<string, unknown> = {},\n requireAuth = false\n): Promise<T> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (requireAuth) {\n const token = await getValidAccessToken(tokenUrl);\n if (!token) {\n throw new Error('Not authenticated');\n }\n\n // Create DPoP proof bound to this request\n const dpopProof = await createDPoPProof('POST', graphqlUrl, token);\n\n headers['Authorization'] = `DPoP ${token}`;\n headers['DPoP'] = dpopProof;\n }\n\n const response = await fetch(graphqlUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify({ query, variables }),\n });\n\n if (!response.ok) {\n throw new Error(`GraphQL request failed: ${response.statusText}`);\n }\n\n const result: GraphQLResponse<T> = await response.json();\n\n if (result.errors && result.errors.length > 0) {\n throw new Error(`GraphQL error: ${result.errors[0].message}`);\n }\n\n return result.data as T;\n}\n", "import { storage } from './storage/storage';\nimport { STORAGE_KEYS } from './storage/keys';\nimport { getOrCreateDPoPKey } from './auth/dpop';\nimport { initiateLogin, handleOAuthCallback, logout as doLogout, LoginOptions } from './auth/oauth';\nimport { getValidAccessToken, hasValidSession } from './auth/tokens';\nimport { graphqlRequest } from './graphql';\n\nexport interface QuicksliceClientOptions {\n server: string;\n clientId: string;\n redirectUri?: string;\n}\n\nexport interface User {\n did: string;\n}\n\nexport class QuicksliceClient {\n private server: string;\n private clientId: string;\n private redirectUri?: string;\n private graphqlUrl: string;\n private authorizeUrl: string;\n private tokenUrl: string;\n private initialized = false;\n\n constructor(options: QuicksliceClientOptions) {\n this.server = options.server.replace(/\\/$/, ''); // Remove trailing slash\n this.clientId = options.clientId;\n this.redirectUri = options.redirectUri;\n\n this.graphqlUrl = `${this.server}/graphql`;\n this.authorizeUrl = `${this.server}/oauth/authorize`;\n this.tokenUrl = `${this.server}/oauth/token`;\n }\n\n /**\n * Initialize the client - must be called before other methods\n */\n async init(): Promise<void> {\n if (this.initialized) return;\n\n // Ensure DPoP key exists\n await getOrCreateDPoPKey();\n\n this.initialized = true;\n }\n\n /**\n * Start OAuth login flow\n */\n async loginWithRedirect(options: LoginOptions = {}): Promise<void> {\n await this.init();\n await initiateLogin(this.authorizeUrl, this.clientId, {\n ...options,\n redirectUri: options.redirectUri || this.redirectUri,\n });\n }\n\n /**\n * Handle OAuth callback after redirect\n * Returns true if callback was handled\n */\n async handleRedirectCallback(): Promise<boolean> {\n await this.init();\n return await handleOAuthCallback(this.tokenUrl);\n }\n\n /**\n * Logout and clear all stored data\n */\n async logout(options: { reload?: boolean } = {}): Promise<void> {\n await doLogout(options);\n }\n\n /**\n * Check if user is authenticated\n */\n async isAuthenticated(): Promise<boolean> {\n return hasValidSession();\n }\n\n /**\n * Get current user's DID (from stored token data)\n * For richer profile info, use client.query() with your own schema\n */\n getUser(): User | null {\n if (!hasValidSession()) {\n return null;\n }\n\n const did = storage.get(STORAGE_KEYS.userDid);\n if (!did) {\n return null;\n }\n\n return { did };\n }\n\n /**\n * Get access token (auto-refreshes if needed)\n */\n async getAccessToken(): Promise<string> {\n await this.init();\n return await getValidAccessToken(this.tokenUrl);\n }\n\n /**\n * Execute a GraphQL query (authenticated)\n */\n async query<T = unknown>(\n query: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n await this.init();\n return await graphqlRequest<T>(\n this.graphqlUrl,\n this.tokenUrl,\n query,\n variables,\n true\n );\n }\n\n /**\n * Execute a GraphQL mutation (authenticated)\n */\n async mutate<T = unknown>(\n mutation: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n return this.query<T>(mutation, variables);\n }\n\n /**\n * Execute a public GraphQL query (no auth)\n */\n async publicQuery<T = unknown>(\n query: string,\n variables: Record<string, unknown> = {}\n ): Promise<T> {\n await this.init();\n return await graphqlRequest<T>(\n this.graphqlUrl,\n this.tokenUrl,\n query,\n variables,\n false\n );\n }\n}\n", "/**\n * Base error class for Quickslice client errors\n */\nexport class QuicksliceError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'QuicksliceError';\n }\n}\n\n/**\n * Thrown when authentication is required but user is not logged in\n */\nexport class LoginRequiredError extends QuicksliceError {\n constructor(message = 'Login required') {\n super(message);\n this.name = 'LoginRequiredError';\n }\n}\n\n/**\n * Thrown when network request fails\n */\nexport class NetworkError extends QuicksliceError {\n constructor(message: string) {\n super(message);\n this.name = 'NetworkError';\n }\n}\n\n/**\n * Thrown when OAuth flow fails\n */\nexport class OAuthError extends QuicksliceError {\n public code: string;\n public description?: string;\n\n constructor(code: string, description?: string) {\n super(`OAuth error: ${code}${description ? ` - ${description}` : ''}`);\n this.name = 'OAuthError';\n this.code = code;\n this.description = description;\n }\n}\n"], 5 + "mappings": "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", 6 6 "names": ["sleep"] 7 7 }
+1 -1
quickslice-client-js/dist/quickslice-client.min.js
··· 1 - "use strict";var QuicksliceClient=(()=>{var _=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var F=Object.prototype.hasOwnProperty;var H=(t,e)=>{for(var r in e)_(t,r,{get:e[r],enumerable:!0})},W=(t,e,r,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of M(e))!F.call(t,s)&&s!==r&&_(t,s,{get:()=>e[s],enumerable:!(o=Y(e,s))||o.enumerable});return t};var X=t=>W(_({},"__esModule",{value:!0}),t);var se={};H(se,{LoginRequiredError:()=>S,NetworkError:()=>T,OAuthError:()=>x,QuicksliceClient:()=>m,QuicksliceError:()=>h,createQuicksliceClient:()=>ie});var n={accessToken:"quickslice_access_token",refreshToken:"quickslice_refresh_token",tokenExpiresAt:"quickslice_token_expires_at",clientId:"quickslice_client_id",userDid:"quickslice_user_did",codeVerifier:"quickslice_code_verifier",oauthState:"quickslice_oauth_state"};var a={get(t){return t===n.codeVerifier||t===n.oauthState?sessionStorage.getItem(t):localStorage.getItem(t)},set(t,e){t===n.codeVerifier||t===n.oauthState?sessionStorage.setItem(t,e):localStorage.setItem(t,e)},remove(t){sessionStorage.removeItem(t),localStorage.removeItem(t)},clear(){Object.values(n).forEach(t=>{sessionStorage.removeItem(t),localStorage.removeItem(t)})}};function d(t){let e=t instanceof Uint8Array?t:new Uint8Array(t),r="";for(let o=0;o<e.length;o++)r+=String.fromCharCode(e[o]);return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function k(t){let e=new Uint8Array(t);return crypto.getRandomValues(e),d(e)}async function K(t){let e=new TextEncoder,r=await crypto.subtle.digest("SHA-256",e.encode(t));return d(r)}async function I(t,e,r){let o=new TextEncoder,s=d(o.encode(JSON.stringify(t))),i=d(o.encode(JSON.stringify(e))),c=`${s}.${i}`,l=await crypto.subtle.sign({name:"ECDSA",hash:"SHA-256"},r,o.encode(c)),u=d(l);return`${c}.${u}`}var Z="quickslice-oauth",ee=1,g="dpop-keys",E="dpop-key",y=null;function b(){return y||(y=new Promise((t,e)=>{let r=indexedDB.open(Z,ee);r.onerror=()=>e(r.error),r.onsuccess=()=>t(r.result),r.onupgradeneeded=o=>{let s=o.target.result;s.objectStoreNames.contains(g)||s.createObjectStore(g,{keyPath:"id"})}}),y)}async function te(){let t=await b();return new Promise((e,r)=>{let i=t.transaction(g,"readonly").objectStore(g).get(E);i.onerror=()=>r(i.error),i.onsuccess=()=>e(i.result||null)})}async function re(t,e){let r=await b();return new Promise((o,s)=>{let l=r.transaction(g,"readwrite").objectStore(g).put({id:E,privateKey:t,publicJwk:e,createdAt:Date.now()});l.onerror=()=>s(l.error),l.onsuccess=()=>o()})}async function D(){let t=await te();if(t)return t;let e=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!1,["sign"]),r=await crypto.subtle.exportKey("jwk",e.publicKey);return await re(e.privateKey,r),{id:E,privateKey:e.privateKey,publicJwk:r,createdAt:Date.now()}}async function f(t,e,r=null){let o=await D(),{kty:s,crv:i,x:c,y:l}=o.publicJwk,w={alg:"ES256",typ:"dpop+jwt",jwk:{kty:s,crv:i,x:c,y:l}},p={jti:k(16),htm:t,htu:e,iat:Math.floor(Date.now()/1e3)};return r&&(p.ath=await K(r)),await I(w,p,o.privateKey)}async function R(){let t=await b();return new Promise((e,r)=>{let i=t.transaction(g,"readwrite").objectStore(g).clear();i.onerror=()=>r(i.error),i.onsuccess=()=>e()})}function C(){return k(32)}async function U(t){let r=new TextEncoder().encode(t),o=await crypto.subtle.digest("SHA-256",r);return d(o)}function q(){return k(16)}var $="quickslice_lock_";function L(t){return new Promise(e=>setTimeout(e,t))}async function V(t,e=5e3){let r=$+t,o=`${Date.now()}_${Math.random()}`,s=Date.now()+e;for(;Date.now()<s;){let i=localStorage.getItem(r);if(i){let[c]=i.split("_");if(Date.now()-parseInt(c)>5e3)localStorage.removeItem(r);else{await L(50);continue}}if(localStorage.setItem(r,o),await L(10),localStorage.getItem(r)===o)return o}return null}function j(t,e){let r=$+t;localStorage.getItem(r)===e&&localStorage.removeItem(r)}var O=6e4;function oe(t){return new Promise(e=>setTimeout(e,t))}async function ne(t){let e=a.get(n.refreshToken),r=a.get(n.clientId);if(!e||!r)throw new Error("No refresh token available");let o=await f("POST",t),s=await fetch(t,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",DPoP:o},body:new URLSearchParams({grant_type:"refresh_token",refresh_token:e,client_id:r})});if(!s.ok){let l=await s.json().catch(()=>({}));throw new Error(`Token refresh failed: ${l.error_description||s.statusText}`)}let i=await s.json();a.set(n.accessToken,i.access_token),i.refresh_token&&a.set(n.refreshToken,i.refresh_token);let c=Date.now()+i.expires_in*1e3;return a.set(n.tokenExpiresAt,c.toString()),i.access_token}async function P(t){let e=a.get(n.accessToken),r=parseInt(a.get(n.tokenExpiresAt)||"0");if(e&&Date.now()<r-O)return e;let s=`token_refresh_${a.get(n.clientId)}`,i=await V(s);if(!i){await oe(100);let c=a.get(n.accessToken),l=parseInt(a.get(n.tokenExpiresAt)||"0");if(c&&Date.now()<l-O)return c;throw new Error("Failed to refresh token")}try{let c=a.get(n.accessToken),l=parseInt(a.get(n.tokenExpiresAt)||"0");return c&&Date.now()<l-O?c:await ne(t)}finally{j(s,i)}}function B(t){a.set(n.accessToken,t.access_token),t.refresh_token&&a.set(n.refreshToken,t.refresh_token);let e=Date.now()+t.expires_in*1e3;a.set(n.tokenExpiresAt,e.toString()),t.sub&&a.set(n.userDid,t.sub)}function v(){let t=a.get(n.accessToken),e=a.get(n.refreshToken);return!!(t||e)}async function Q(t,e,r={}){let o=C(),s=await U(o),i=q();a.set(n.codeVerifier,o),a.set(n.oauthState,i),a.set(n.clientId,e);let c=window.location.origin+window.location.pathname,l=new URLSearchParams({client_id:e,redirect_uri:c,response_type:"code",code_challenge:s,code_challenge_method:"S256",state:i});r.handle&&l.set("login_hint",r.handle),window.location.href=`${t}?${l.toString()}`}async function J(t){let e=new URLSearchParams(window.location.search),r=e.get("code"),o=e.get("state"),s=e.get("error");if(s)throw new Error(`OAuth error: ${s} - ${e.get("error_description")||""}`);if(!r||!o)return!1;let i=a.get(n.oauthState);if(o!==i)throw new Error("OAuth state mismatch - possible CSRF attack");let c=a.get(n.codeVerifier),l=a.get(n.clientId),u=window.location.origin+window.location.pathname;if(!c||!l)throw new Error("Missing OAuth session data");let w=await f("POST",t),p=await fetch(t,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",DPoP:w},body:new URLSearchParams({grant_type:"authorization_code",code:r,redirect_uri:u,client_id:l,code_verifier:c})});if(!p.ok){let z=await p.json().catch(()=>({}));throw new Error(`Token exchange failed: ${z.error_description||p.statusText}`)}let N=await p.json();return B(N),a.remove(n.codeVerifier),a.remove(n.oauthState),window.history.replaceState({},document.title,window.location.pathname),!0}async function G(t={}){a.clear(),await R(),t.reload!==!1&&window.location.reload()}async function A(t,e,r,o={},s=!1){let i={"Content-Type":"application/json"};if(s){let u=await P(e);if(!u)throw new Error("Not authenticated");let w=await f("POST",t,u);i.Authorization=`DPoP ${u}`,i.DPoP=w}let c=await fetch(t,{method:"POST",headers:i,body:JSON.stringify({query:r,variables:o})});if(!c.ok)throw new Error(`GraphQL request failed: ${c.statusText}`);let l=await c.json();if(l.errors&&l.errors.length>0)throw new Error(`GraphQL error: ${l.errors[0].message}`);return l.data}var m=class{constructor(e){this.initialized=!1;this.server=e.server.replace(/\/$/,""),this.clientId=e.clientId,this.graphqlUrl=`${this.server}/graphql`,this.authorizeUrl=`${this.server}/oauth/authorize`,this.tokenUrl=`${this.server}/oauth/token`}async init(){this.initialized||(await D(),this.initialized=!0)}async loginWithRedirect(e={}){await this.init(),await Q(this.authorizeUrl,this.clientId,e)}async handleRedirectCallback(){return await this.init(),await J(this.tokenUrl)}async logout(e={}){await G(e)}async isAuthenticated(){return v()}getUser(){if(!v())return null;let e=a.get(n.userDid);return e?{did:e}:null}async getAccessToken(){return await this.init(),await P(this.tokenUrl)}async query(e,r={}){return await this.init(),await A(this.graphqlUrl,this.tokenUrl,e,r,!0)}async mutate(e,r={}){return this.query(e,r)}async publicQuery(e,r={}){return await this.init(),await A(this.graphqlUrl,this.tokenUrl,e,r,!1)}};var h=class extends Error{constructor(e){super(e),this.name="QuicksliceError"}},S=class extends h{constructor(e="Login required"){super(e),this.name="LoginRequiredError"}},T=class extends h{constructor(e){super(e),this.name="NetworkError"}},x=class extends h{constructor(e,r){super(`OAuth error: ${e}${r?` - ${r}`:""}`),this.name="OAuthError",this.code=e,this.description=r}};async function ie(t){let e=new m(t);return await e.init(),e}return X(se);})(); 1 + "use strict";var QuicksliceClient=(()=>{var x=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var F=Object.prototype.hasOwnProperty;var H=(t,e)=>{for(var r in e)x(t,r,{get:e[r],enumerable:!0})},W=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of M(e))!F.call(t,a)&&a!==r&&x(t,a,{get:()=>e[a],enumerable:!(n=Y(e,a))||n.enumerable});return t};var X=t=>W(x({},"__esModule",{value:!0}),t);var se={};H(se,{LoginRequiredError:()=>S,NetworkError:()=>T,OAuthError:()=>_,QuicksliceClient:()=>m,QuicksliceError:()=>h,createQuicksliceClient:()=>ie});var o={accessToken:"quickslice_access_token",refreshToken:"quickslice_refresh_token",tokenExpiresAt:"quickslice_token_expires_at",clientId:"quickslice_client_id",userDid:"quickslice_user_did",codeVerifier:"quickslice_code_verifier",oauthState:"quickslice_oauth_state",redirectUri:"quickslice_redirect_uri"};var s={get(t){return t===o.codeVerifier||t===o.oauthState?sessionStorage.getItem(t):localStorage.getItem(t)},set(t,e){t===o.codeVerifier||t===o.oauthState?sessionStorage.setItem(t,e):localStorage.setItem(t,e)},remove(t){sessionStorage.removeItem(t),localStorage.removeItem(t)},clear(){Object.values(o).forEach(t=>{sessionStorage.removeItem(t),localStorage.removeItem(t)})}};function d(t){let e=t instanceof Uint8Array?t:new Uint8Array(t),r="";for(let n=0;n<e.length;n++)r+=String.fromCharCode(e[n]);return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function k(t){let e=new Uint8Array(t);return crypto.getRandomValues(e),d(e)}async function A(t){let e=new TextEncoder,r=await crypto.subtle.digest("SHA-256",e.encode(t));return d(r)}async function K(t,e,r){let n=new TextEncoder,a=d(n.encode(JSON.stringify(t))),i=d(n.encode(JSON.stringify(e))),c=`${a}.${i}`,l=await crypto.subtle.sign({name:"ECDSA",hash:"SHA-256"},r,n.encode(c)),u=d(l);return`${c}.${u}`}var Z="quickslice-oauth",ee=1,g="dpop-keys",E="dpop-key",y=null;function b(){return y||(y=new Promise((t,e)=>{let r=indexedDB.open(Z,ee);r.onerror=()=>e(r.error),r.onsuccess=()=>t(r.result),r.onupgradeneeded=n=>{let a=n.target.result;a.objectStoreNames.contains(g)||a.createObjectStore(g,{keyPath:"id"})}}),y)}async function te(){let t=await b();return new Promise((e,r)=>{let i=t.transaction(g,"readonly").objectStore(g).get(E);i.onerror=()=>r(i.error),i.onsuccess=()=>e(i.result||null)})}async function re(t,e){let r=await b();return new Promise((n,a)=>{let l=r.transaction(g,"readwrite").objectStore(g).put({id:E,privateKey:t,publicJwk:e,createdAt:Date.now()});l.onerror=()=>a(l.error),l.onsuccess=()=>n()})}async function D(){let t=await te();if(t)return t;let e=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!1,["sign"]),r=await crypto.subtle.exportKey("jwk",e.publicKey);return await re(e.privateKey,r),{id:E,privateKey:e.privateKey,publicJwk:r,createdAt:Date.now()}}async function f(t,e,r=null){let n=await D(),{kty:a,crv:i,x:c,y:l}=n.publicJwk,w={alg:"ES256",typ:"dpop+jwt",jwk:{kty:a,crv:i,x:c,y:l}},p={jti:k(16),htm:t,htu:e,iat:Math.floor(Date.now()/1e3)};return r&&(p.ath=await A(r)),await K(w,p,n.privateKey)}async function I(){let t=await b();return new Promise((e,r)=>{let i=t.transaction(g,"readwrite").objectStore(g).clear();i.onerror=()=>r(i.error),i.onsuccess=()=>e()})}function R(){return k(32)}async function C(t){let r=new TextEncoder().encode(t),n=await crypto.subtle.digest("SHA-256",r);return d(n)}function q(){return k(16)}var $="quickslice_lock_";function L(t){return new Promise(e=>setTimeout(e,t))}async function V(t,e=5e3){let r=$+t,n=`${Date.now()}_${Math.random()}`,a=Date.now()+e;for(;Date.now()<a;){let i=localStorage.getItem(r);if(i){let[c]=i.split("_");if(Date.now()-parseInt(c)>5e3)localStorage.removeItem(r);else{await L(50);continue}}if(localStorage.setItem(r,n),await L(10),localStorage.getItem(r)===n)return n}return null}function j(t,e){let r=$+t;localStorage.getItem(r)===e&&localStorage.removeItem(r)}var v=6e4;function oe(t){return new Promise(e=>setTimeout(e,t))}async function ne(t){let e=s.get(o.refreshToken),r=s.get(o.clientId);if(!e||!r)throw new Error("No refresh token available");let n=await f("POST",t),a=await fetch(t,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",DPoP:n},body:new URLSearchParams({grant_type:"refresh_token",refresh_token:e,client_id:r})});if(!a.ok){let l=await a.json().catch(()=>({}));throw new Error(`Token refresh failed: ${l.error_description||a.statusText}`)}let i=await a.json();s.set(o.accessToken,i.access_token),i.refresh_token&&s.set(o.refreshToken,i.refresh_token);let c=Date.now()+i.expires_in*1e3;return s.set(o.tokenExpiresAt,c.toString()),i.access_token}async function P(t){let e=s.get(o.accessToken),r=parseInt(s.get(o.tokenExpiresAt)||"0");if(e&&Date.now()<r-v)return e;let a=`token_refresh_${s.get(o.clientId)}`,i=await V(a);if(!i){await oe(100);let c=s.get(o.accessToken),l=parseInt(s.get(o.tokenExpiresAt)||"0");if(c&&Date.now()<l-v)return c;throw new Error("Failed to refresh token")}try{let c=s.get(o.accessToken),l=parseInt(s.get(o.tokenExpiresAt)||"0");return c&&Date.now()<l-v?c:await ne(t)}finally{j(a,i)}}function B(t){s.set(o.accessToken,t.access_token),t.refresh_token&&s.set(o.refreshToken,t.refresh_token);let e=Date.now()+t.expires_in*1e3;s.set(o.tokenExpiresAt,e.toString()),t.sub&&s.set(o.userDid,t.sub)}function O(){let t=s.get(o.accessToken),e=s.get(o.refreshToken);return!!(t||e)}async function Q(t,e,r={}){let n=R(),a=await C(n),i=q(),c=r.redirectUri||window.location.origin+window.location.pathname;s.set(o.codeVerifier,n),s.set(o.oauthState,i),s.set(o.clientId,e),s.set(o.redirectUri,c);let l=new URLSearchParams({client_id:e,redirect_uri:c,response_type:"code",code_challenge:a,code_challenge_method:"S256",state:i});r.handle&&l.set("login_hint",r.handle),window.location.href=`${t}?${l.toString()}`}async function J(t){let e=new URLSearchParams(window.location.search),r=e.get("code"),n=e.get("state"),a=e.get("error");if(a)throw new Error(`OAuth error: ${a} - ${e.get("error_description")||""}`);if(!r||!n)return!1;let i=s.get(o.oauthState);if(n!==i)throw new Error("OAuth state mismatch - possible CSRF attack");let c=s.get(o.codeVerifier),l=s.get(o.clientId),u=s.get(o.redirectUri);if(!c||!l||!u)throw new Error("Missing OAuth session data");let w=await f("POST",t),p=await fetch(t,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",DPoP:w},body:new URLSearchParams({grant_type:"authorization_code",code:r,redirect_uri:u,client_id:l,code_verifier:c})});if(!p.ok){let z=await p.json().catch(()=>({}));throw new Error(`Token exchange failed: ${z.error_description||p.statusText}`)}let N=await p.json();return B(N),s.remove(o.codeVerifier),s.remove(o.oauthState),s.remove(o.redirectUri),window.history.replaceState({},document.title,window.location.pathname),!0}async function G(t={}){s.clear(),await I(),t.reload!==!1&&window.location.reload()}async function U(t,e,r,n={},a=!1){let i={"Content-Type":"application/json"};if(a){let u=await P(e);if(!u)throw new Error("Not authenticated");let w=await f("POST",t,u);i.Authorization=`DPoP ${u}`,i.DPoP=w}let c=await fetch(t,{method:"POST",headers:i,body:JSON.stringify({query:r,variables:n})});if(!c.ok)throw new Error(`GraphQL request failed: ${c.statusText}`);let l=await c.json();if(l.errors&&l.errors.length>0)throw new Error(`GraphQL error: ${l.errors[0].message}`);return l.data}var m=class{constructor(e){this.initialized=!1;this.server=e.server.replace(/\/$/,""),this.clientId=e.clientId,this.redirectUri=e.redirectUri,this.graphqlUrl=`${this.server}/graphql`,this.authorizeUrl=`${this.server}/oauth/authorize`,this.tokenUrl=`${this.server}/oauth/token`}async init(){this.initialized||(await D(),this.initialized=!0)}async loginWithRedirect(e={}){await this.init(),await Q(this.authorizeUrl,this.clientId,{...e,redirectUri:e.redirectUri||this.redirectUri})}async handleRedirectCallback(){return await this.init(),await J(this.tokenUrl)}async logout(e={}){await G(e)}async isAuthenticated(){return O()}getUser(){if(!O())return null;let e=s.get(o.userDid);return e?{did:e}:null}async getAccessToken(){return await this.init(),await P(this.tokenUrl)}async query(e,r={}){return await this.init(),await U(this.graphqlUrl,this.tokenUrl,e,r,!0)}async mutate(e,r={}){return this.query(e,r)}async publicQuery(e,r={}){return await this.init(),await U(this.graphqlUrl,this.tokenUrl,e,r,!1)}};var h=class extends Error{constructor(e){super(e),this.name="QuicksliceError"}},S=class extends h{constructor(e="Login required"){super(e),this.name="LoginRequiredError"}},T=class extends h{constructor(e){super(e),this.name="NetworkError"}},_=class extends h{constructor(e,r){super(`OAuth error: ${e}${r?` - ${r}`:""}`),this.name="OAuthError",this.code=e,this.description=r}};async function ie(t){let e=new m(t);return await e.init(),e}return X(se);})();
+1
quickslice-client-js/dist/storage/keys.d.ts
··· 9 9 readonly userDid: "quickslice_user_did"; 10 10 readonly codeVerifier: "quickslice_code_verifier"; 11 11 readonly oauthState: "quickslice_oauth_state"; 12 + readonly redirectUri: "quickslice_redirect_uri"; 12 13 }; 13 14 export type StorageKey = (typeof STORAGE_KEYS)[keyof typeof STORAGE_KEYS];
+8 -5
quickslice-client-js/src/auth/oauth.ts
··· 6 6 7 7 export interface LoginOptions { 8 8 handle?: string; 9 + redirectUri?: string; 9 10 } 10 11 11 12 /** ··· 20 21 const codeChallenge = await generateCodeChallenge(codeVerifier); 21 22 const state = generateState(); 22 23 24 + // Build redirect URI (use provided or derive from current page) 25 + const redirectUri = options.redirectUri || (window.location.origin + window.location.pathname); 26 + 23 27 // Store for callback 24 28 storage.set(STORAGE_KEYS.codeVerifier, codeVerifier); 25 29 storage.set(STORAGE_KEYS.oauthState, state); 26 30 storage.set(STORAGE_KEYS.clientId, clientId); 27 - 28 - // Build redirect URI (current page without query params) 29 - const redirectUri = window.location.origin + window.location.pathname; 31 + storage.set(STORAGE_KEYS.redirectUri, redirectUri); 30 32 31 33 // Build authorization URL 32 34 const params = new URLSearchParams({ ··· 74 76 // Get stored values 75 77 const codeVerifier = storage.get(STORAGE_KEYS.codeVerifier); 76 78 const clientId = storage.get(STORAGE_KEYS.clientId); 77 - const redirectUri = window.location.origin + window.location.pathname; 79 + const redirectUri = storage.get(STORAGE_KEYS.redirectUri); 78 80 79 - if (!codeVerifier || !clientId) { 81 + if (!codeVerifier || !clientId || !redirectUri) { 80 82 throw new Error('Missing OAuth session data'); 81 83 } 82 84 ··· 113 115 // Clean up OAuth state 114 116 storage.remove(STORAGE_KEYS.codeVerifier); 115 117 storage.remove(STORAGE_KEYS.oauthState); 118 + storage.remove(STORAGE_KEYS.redirectUri); 116 119 117 120 // Clear URL params 118 121 window.history.replaceState({}, document.title, window.location.pathname);
+7 -1
quickslice-client-js/src/client.ts
··· 8 8 export interface QuicksliceClientOptions { 9 9 server: string; 10 10 clientId: string; 11 + redirectUri?: string; 11 12 } 12 13 13 14 export interface User { ··· 17 18 export class QuicksliceClient { 18 19 private server: string; 19 20 private clientId: string; 21 + private redirectUri?: string; 20 22 private graphqlUrl: string; 21 23 private authorizeUrl: string; 22 24 private tokenUrl: string; ··· 25 27 constructor(options: QuicksliceClientOptions) { 26 28 this.server = options.server.replace(/\/$/, ''); // Remove trailing slash 27 29 this.clientId = options.clientId; 30 + this.redirectUri = options.redirectUri; 28 31 29 32 this.graphqlUrl = `${this.server}/graphql`; 30 33 this.authorizeUrl = `${this.server}/oauth/authorize`; ··· 48 51 */ 49 52 async loginWithRedirect(options: LoginOptions = {}): Promise<void> { 50 53 await this.init(); 51 - await initiateLogin(this.authorizeUrl, this.clientId, options); 54 + await initiateLogin(this.authorizeUrl, this.clientId, { 55 + ...options, 56 + redirectUri: options.redirectUri || this.redirectUri, 57 + }); 52 58 } 53 59 54 60 /**
+1
quickslice-client-js/src/storage/keys.ts
··· 9 9 userDid: 'quickslice_user_did', 10 10 codeVerifier: 'quickslice_code_verifier', 11 11 oauthState: 'quickslice_oauth_state', 12 + redirectUri: 'quickslice_redirect_uri', 12 13 } as const; 13 14 14 15 export type StorageKey = (typeof STORAGE_KEYS)[keyof typeof STORAGE_KEYS];