@armco/iam-client 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -107,7 +107,21 @@ declare class StuffleIAMClient {
107
107
  private discovery;
108
108
  private refreshTimer;
109
109
  private listeners;
110
+ private _initialized;
111
+ private _initPromise;
110
112
  constructor(config: StuffleIAMConfig);
113
+ /**
114
+ * Initialize / restore session from storage.
115
+ * Call this on app startup (page load) to silently restore a session.
116
+ *
117
+ * - If access token is valid → re-setup auto-refresh timer, return true
118
+ * - If access token is expired but refresh token exists → silent refresh, return true/false
119
+ * - If no tokens → return false (not authenticated)
120
+ *
121
+ * Safe to call multiple times — subsequent calls return the same promise.
122
+ */
123
+ init(): Promise<boolean>;
124
+ private _doInit;
111
125
  /**
112
126
  * Fetch OIDC discovery document
113
127
  */
@@ -149,9 +163,19 @@ declare class StuffleIAMClient {
149
163
  */
150
164
  fetchUserInfo(accessToken?: string): Promise<User | null>;
151
165
  /**
152
- * Check if user is authenticated
166
+ * Check if user is authenticated (synchronous).
167
+ * Returns true if access token exists and is not expired.
168
+ *
169
+ * NOTE: If the access token is expired but a refresh token exists,
170
+ * this returns false. Call init() or getAccessToken() first to
171
+ * attempt a silent refresh.
153
172
  */
154
173
  isAuthenticated(): boolean;
174
+ /**
175
+ * Check if a refresh token exists in storage.
176
+ * Useful for determining if a silent refresh is possible.
177
+ */
178
+ hasRefreshToken(): boolean;
155
179
  /**
156
180
  * Get current auth state
157
181
  */
@@ -107,7 +107,21 @@ declare class StuffleIAMClient {
107
107
  private discovery;
108
108
  private refreshTimer;
109
109
  private listeners;
110
+ private _initialized;
111
+ private _initPromise;
110
112
  constructor(config: StuffleIAMConfig);
113
+ /**
114
+ * Initialize / restore session from storage.
115
+ * Call this on app startup (page load) to silently restore a session.
116
+ *
117
+ * - If access token is valid → re-setup auto-refresh timer, return true
118
+ * - If access token is expired but refresh token exists → silent refresh, return true/false
119
+ * - If no tokens → return false (not authenticated)
120
+ *
121
+ * Safe to call multiple times — subsequent calls return the same promise.
122
+ */
123
+ init(): Promise<boolean>;
124
+ private _doInit;
111
125
  /**
112
126
  * Fetch OIDC discovery document
113
127
  */
@@ -149,9 +163,19 @@ declare class StuffleIAMClient {
149
163
  */
150
164
  fetchUserInfo(accessToken?: string): Promise<User | null>;
151
165
  /**
152
- * Check if user is authenticated
166
+ * Check if user is authenticated (synchronous).
167
+ * Returns true if access token exists and is not expired.
168
+ *
169
+ * NOTE: If the access token is expired but a refresh token exists,
170
+ * this returns false. Call init() or getAccessToken() first to
171
+ * attempt a silent refresh.
153
172
  */
154
173
  isAuthenticated(): boolean;
174
+ /**
175
+ * Check if a refresh token exists in storage.
176
+ * Useful for determining if a silent refresh is possible.
177
+ */
178
+ hasRefreshToken(): boolean;
155
179
  /**
156
180
  * Get current auth state
157
181
  */
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AuthState, C as CallbackResult, L as LoginOptions, b as LogoutOptions, O as OIDCDiscovery, S as StuffleIAMClient, a as StuffleIAMConfig, T as TokenResponse, U as User, c as createStuffleIAMClient } from './client-CTKWBZ26.mjs';
1
+ export { A as AuthState, C as CallbackResult, L as LoginOptions, b as LogoutOptions, O as OIDCDiscovery, S as StuffleIAMClient, a as StuffleIAMConfig, T as TokenResponse, U as User, c as createStuffleIAMClient } from './client-CrQnMix2.mjs';
2
2
 
3
3
  /**
4
4
  * Utility functions for PKCE and crypto operations
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AuthState, C as CallbackResult, L as LoginOptions, b as LogoutOptions, O as OIDCDiscovery, S as StuffleIAMClient, a as StuffleIAMConfig, T as TokenResponse, U as User, c as createStuffleIAMClient } from './client-CTKWBZ26.js';
1
+ export { A as AuthState, C as CallbackResult, L as LoginOptions, b as LogoutOptions, O as OIDCDiscovery, S as StuffleIAMClient, a as StuffleIAMConfig, T as TokenResponse, U as User, c as createStuffleIAMClient } from './client-CrQnMix2.js';
2
2
 
3
3
  /**
4
4
  * Utility functions for PKCE and crypto operations
package/dist/index.js CHANGED
@@ -203,6 +203,8 @@ var StuffleIAMClient = class {
203
203
  this.discovery = null;
204
204
  this.refreshTimer = null;
205
205
  this.listeners = /* @__PURE__ */ new Set();
206
+ this._initialized = false;
207
+ this._initPromise = null;
206
208
  this.config = {
207
209
  issuer: config.issuer.replace(/\/$/, ""),
208
210
  // Remove trailing slash
@@ -217,6 +219,56 @@ var StuffleIAMClient = class {
217
219
  };
218
220
  this.storage = getStorage(this.config.storage);
219
221
  }
222
+ /**
223
+ * Initialize / restore session from storage.
224
+ * Call this on app startup (page load) to silently restore a session.
225
+ *
226
+ * - If access token is valid → re-setup auto-refresh timer, return true
227
+ * - If access token is expired but refresh token exists → silent refresh, return true/false
228
+ * - If no tokens → return false (not authenticated)
229
+ *
230
+ * Safe to call multiple times — subsequent calls return the same promise.
231
+ */
232
+ async init() {
233
+ if (this._initialized) return this.isAuthenticated();
234
+ if (this._initPromise) return this._initPromise;
235
+ this._initPromise = this._doInit();
236
+ return this._initPromise;
237
+ }
238
+ async _doInit() {
239
+ try {
240
+ const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);
241
+ const refreshTokenValue = this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);
242
+ if (!accessToken && !refreshTokenValue) {
243
+ this._initialized = true;
244
+ return false;
245
+ }
246
+ if (accessToken && !isTokenExpired(accessToken)) {
247
+ if (this.config.autoRefresh && refreshTokenValue) {
248
+ this.setupAutoRefresh();
249
+ }
250
+ this._initialized = true;
251
+ this.notifyListeners();
252
+ return true;
253
+ }
254
+ if (refreshTokenValue) {
255
+ const tokens = await this.refreshToken();
256
+ if (tokens) {
257
+ if (this.config.autoRefresh) {
258
+ this.setupAutoRefresh();
259
+ }
260
+ this._initialized = true;
261
+ return true;
262
+ }
263
+ }
264
+ this._initialized = true;
265
+ return false;
266
+ } catch (err) {
267
+ console.error("[StuffleIAMClient] init() failed:", err);
268
+ this._initialized = true;
269
+ return false;
270
+ }
271
+ }
220
272
  /**
221
273
  * Fetch OIDC discovery document
222
274
  */
@@ -447,13 +499,25 @@ var StuffleIAMClient = class {
447
499
  return user;
448
500
  }
449
501
  /**
450
- * Check if user is authenticated
502
+ * Check if user is authenticated (synchronous).
503
+ * Returns true if access token exists and is not expired.
504
+ *
505
+ * NOTE: If the access token is expired but a refresh token exists,
506
+ * this returns false. Call init() or getAccessToken() first to
507
+ * attempt a silent refresh.
451
508
  */
452
509
  isAuthenticated() {
453
510
  const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);
454
511
  if (!accessToken) return false;
455
512
  return !isTokenExpired(accessToken);
456
513
  }
514
+ /**
515
+ * Check if a refresh token exists in storage.
516
+ * Useful for determining if a silent refresh is possible.
517
+ */
518
+ hasRefreshToken() {
519
+ return !!this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);
520
+ }
457
521
  /**
458
522
  * Get current auth state
459
523
  */
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["/**\n * Stuffle IAM SDK\n * \n * JavaScript/TypeScript SDK for integrating with Stuffle IAM.\n * Supports OIDC/OAuth2 authorization code flow with PKCE.\n * \n * @example\n * ```ts\n * import { createStuffleIAMClient } from '@stuffle/iam-sdk';\n * \n * const iam = createStuffleIAMClient({\n * issuer: 'http://localhost:5000',\n * clientId: 'my-app',\n * redirectUri: 'http://localhost:3000/callback',\n * });\n * \n * // Start login\n * await iam.login();\n * \n * // Handle callback\n * const result = await iam.handleCallback();\n * \n * // Get user\n * const user = iam.getUser();\n * ```\n */\n\nexport { StuffleIAMClient, createStuffleIAMClient } from './client';\nexport type {\n StuffleIAMConfig,\n TokenResponse,\n User,\n AuthState,\n LoginOptions,\n LogoutOptions,\n CallbackResult,\n OIDCDiscovery,\n} from './types';\nexport {\n generateRandomString,\n generateCodeVerifier,\n generateCodeChallenge,\n decodeJwtPayload,\n isTokenExpired,\n} from './utils';\nexport { getStorage, type TokenStorage } from './storage';\n","/**\n * Utility functions for PKCE and crypto operations\n */\n\n/**\n * Generate a random string for state/nonce\n */\nexport function generateRandomString(length: number = 32): string {\n const array = new Uint8Array(length);\n crypto.getRandomValues(array);\n return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Generate PKCE code verifier (43-128 characters)\n */\nexport function generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\n/**\n * Generate PKCE code challenge from verifier\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', data);\n return base64UrlEncode(new Uint8Array(digest));\n}\n\n/**\n * Base64 URL encode (no padding, URL-safe characters)\n */\nexport function base64UrlEncode(buffer: Uint8Array): string {\n let binary = '';\n for (let i = 0; i < buffer.length; i++) {\n binary += String.fromCharCode(buffer[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\n/**\n * Decode JWT payload (without verification)\n */\nexport function decodeJwtPayload<T = Record<string, unknown>>(token: string): T | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n \n const payload = parts[1];\n const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));\n return JSON.parse(decoded) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Check if token is expired\n */\nexport function isTokenExpired(token: string, thresholdSeconds: number = 0): boolean {\n const payload = decodeJwtPayload<{ exp?: number }>(token);\n if (!payload?.exp) return true;\n \n const now = Math.floor(Date.now() / 1000);\n return payload.exp - thresholdSeconds <= now;\n}\n\n/**\n * Parse URL hash or query parameters\n */\nexport function parseUrlParams(url: string): Record<string, string> {\n const params: Record<string, string> = {};\n \n // Check hash first (implicit flow), then query (code flow)\n const hashIndex = url.indexOf('#');\n const queryIndex = url.indexOf('?');\n \n let paramString = '';\n if (hashIndex !== -1) {\n paramString = url.substring(hashIndex + 1);\n } else if (queryIndex !== -1) {\n paramString = url.substring(queryIndex + 1);\n }\n \n if (!paramString) return params;\n \n const searchParams = new URLSearchParams(paramString);\n searchParams.forEach((value, key) => {\n params[key] = value;\n });\n \n return params;\n}\n\n/**\n * Build URL with query parameters\n */\nexport function buildUrl(base: string, params: Record<string, string | undefined>): string {\n const url = new URL(base);\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.append(key, value);\n }\n });\n return url.toString();\n}\n","/**\n * Token storage abstraction\n */\n\nexport interface TokenStorage {\n get(key: string): string | null;\n set(key: string, value: string): void;\n remove(key: string): void;\n clear(): void;\n}\n\nconst STORAGE_PREFIX = 'stuffle_iam_';\n\n/**\n * LocalStorage implementation\n */\nexport class LocalStorageAdapter implements TokenStorage {\n get(key: string): string | null {\n try {\n return localStorage.getItem(STORAGE_PREFIX + key);\n } catch {\n return null;\n }\n }\n\n set(key: string, value: string): void {\n try {\n localStorage.setItem(STORAGE_PREFIX + key, value);\n } catch {\n console.warn('LocalStorage not available');\n }\n }\n\n remove(key: string): void {\n try {\n localStorage.removeItem(STORAGE_PREFIX + key);\n } catch {\n // Ignore\n }\n }\n\n clear(): void {\n try {\n Object.keys(localStorage)\n .filter(k => k.startsWith(STORAGE_PREFIX))\n .forEach(k => localStorage.removeItem(k));\n } catch {\n // Ignore\n }\n }\n}\n\n/**\n * SessionStorage implementation\n */\nexport class SessionStorageAdapter implements TokenStorage {\n get(key: string): string | null {\n try {\n return sessionStorage.getItem(STORAGE_PREFIX + key);\n } catch {\n return null;\n }\n }\n\n set(key: string, value: string): void {\n try {\n sessionStorage.setItem(STORAGE_PREFIX + key, value);\n } catch {\n console.warn('SessionStorage not available');\n }\n }\n\n remove(key: string): void {\n try {\n sessionStorage.removeItem(STORAGE_PREFIX + key);\n } catch {\n // Ignore\n }\n }\n\n clear(): void {\n try {\n Object.keys(sessionStorage)\n .filter(k => k.startsWith(STORAGE_PREFIX))\n .forEach(k => sessionStorage.removeItem(k));\n } catch {\n // Ignore\n }\n }\n}\n\n/**\n * In-memory storage (for SSR or when storage is unavailable)\n */\nexport class MemoryStorageAdapter implements TokenStorage {\n private store = new Map<string, string>();\n\n get(key: string): string | null {\n return this.store.get(key) ?? null;\n }\n\n set(key: string, value: string): void {\n this.store.set(key, value);\n }\n\n remove(key: string): void {\n this.store.delete(key);\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n\n/**\n * Get storage adapter based on type\n */\nexport function getStorage(type: 'localStorage' | 'sessionStorage' | 'memory'): TokenStorage {\n switch (type) {\n case 'localStorage':\n return new LocalStorageAdapter();\n case 'sessionStorage':\n return new SessionStorageAdapter();\n case 'memory':\n return new MemoryStorageAdapter();\n default:\n return new SessionStorageAdapter();\n }\n}\n","/**\n * Stuffle IAM Client\n * \n * OIDC/OAuth2 client for browser-based applications.\n * Supports authorization code flow with PKCE.\n */\n\nimport type {\n StuffleIAMConfig,\n TokenResponse,\n User,\n AuthState,\n LoginOptions,\n LogoutOptions,\n CallbackResult,\n OIDCDiscovery,\n} from './types';\nimport {\n generateRandomString,\n generateCodeVerifier,\n generateCodeChallenge,\n decodeJwtPayload,\n isTokenExpired,\n parseUrlParams,\n buildUrl,\n} from './utils';\nimport { getStorage, type TokenStorage } from './storage';\n\nconst STORAGE_KEYS = {\n ACCESS_TOKEN: 'access_token',\n REFRESH_TOKEN: 'refresh_token',\n ID_TOKEN: 'id_token',\n CODE_VERIFIER: 'code_verifier',\n STATE: 'state',\n NONCE: 'nonce',\n USER: 'user',\n EXPIRES_AT: 'expires_at',\n};\n\nexport class StuffleIAMClient {\n private config: Required<StuffleIAMConfig>;\n private storage: TokenStorage;\n private discovery: OIDCDiscovery | null = null;\n private refreshTimer: ReturnType<typeof setTimeout> | null = null;\n private listeners: Set<(state: AuthState) => void> = new Set();\n\n constructor(config: StuffleIAMConfig) {\n this.config = {\n issuer: config.issuer.replace(/\\/$/, ''), // Remove trailing slash\n clientId: config.clientId,\n redirectUri: config.redirectUri,\n scopes: config.scopes ?? ['openid', 'profile', 'email'],\n postLogoutRedirectUri: config.postLogoutRedirectUri ?? config.redirectUri,\n usePkce: config.usePkce ?? true,\n storage: config.storage ?? 'sessionStorage',\n autoRefresh: config.autoRefresh ?? true,\n refreshThreshold: config.refreshThreshold ?? 60,\n };\n\n this.storage = getStorage(this.config.storage);\n }\n\n /**\n * Fetch OIDC discovery document\n */\n async getDiscovery(): Promise<OIDCDiscovery> {\n if (this.discovery) return this.discovery;\n\n const response = await fetch(\n `${this.config.issuer}/.well-known/openid-configuration`\n );\n\n if (!response.ok) {\n throw new Error(`Failed to fetch OIDC discovery: ${response.status}`);\n }\n\n this.discovery = await response.json();\n return this.discovery!;\n }\n\n /**\n * Start login flow - redirects to authorization endpoint\n */\n async login(options: LoginOptions = {}): Promise<void> {\n const discovery = await this.getDiscovery();\n\n const state = options.state ?? generateRandomString();\n const nonce = options.nonce ?? generateRandomString();\n const scopes = [...this.config.scopes, ...(options.scopes ?? [])];\n\n // Store state and nonce for callback validation\n this.storage.set(STORAGE_KEYS.STATE, state);\n this.storage.set(STORAGE_KEYS.NONCE, nonce);\n\n const params: Record<string, string | undefined> = {\n client_id: this.config.clientId,\n redirect_uri: this.config.redirectUri,\n response_type: 'code',\n scope: scopes.join(' '),\n state,\n nonce,\n prompt: options.prompt,\n login_hint: options.loginHint,\n };\n\n // Add PKCE if enabled\n if (this.config.usePkce) {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n \n this.storage.set(STORAGE_KEYS.CODE_VERIFIER, codeVerifier);\n params.code_challenge = codeChallenge;\n params.code_challenge_method = 'S256';\n }\n\n // Use signup endpoint if requested\n const endpoint = options.signup\n ? discovery.authorization_endpoint.replace('/authorize', '/authorize/signup')\n : discovery.authorization_endpoint;\n\n const authUrl = buildUrl(endpoint, params);\n window.location.href = authUrl;\n }\n\n /**\n * Alias for login({ signup: true })\n */\n async signup(options: Omit<LoginOptions, 'signup'> = {}): Promise<void> {\n return this.login({ ...options, signup: true });\n }\n\n /**\n * Handle callback from authorization server\n */\n async handleCallback(url?: string): Promise<CallbackResult> {\n const callbackUrl = url ?? window.location.href;\n const params = parseUrlParams(callbackUrl);\n\n // Check for errors\n if (params.error) {\n return {\n success: false,\n error: params.error,\n errorDescription: params.error_description,\n };\n }\n\n // Validate state\n const storedState = this.storage.get(STORAGE_KEYS.STATE);\n if (!storedState || storedState !== params.state) {\n return {\n success: false,\n error: 'invalid_state',\n errorDescription: 'State mismatch - possible CSRF attack',\n };\n }\n\n // Exchange code for tokens\n if (!params.code) {\n return {\n success: false,\n error: 'missing_code',\n errorDescription: 'No authorization code received',\n };\n }\n\n try {\n const tokens = await this.exchangeCode(params.code);\n \n // Validate nonce in ID token\n if (tokens.id_token) {\n const payload = decodeJwtPayload<{ nonce?: string }>(tokens.id_token);\n const storedNonce = this.storage.get(STORAGE_KEYS.NONCE);\n if (payload?.nonce !== storedNonce) {\n return {\n success: false,\n error: 'invalid_nonce',\n errorDescription: 'Nonce mismatch - possible replay attack',\n };\n }\n }\n\n // Store tokens\n this.storeTokens(tokens);\n\n // Clear temporary storage\n this.storage.remove(STORAGE_KEYS.STATE);\n this.storage.remove(STORAGE_KEYS.NONCE);\n this.storage.remove(STORAGE_KEYS.CODE_VERIFIER);\n\n // Get user info\n const user = await this.fetchUserInfo(tokens.access_token);\n\n // Setup auto-refresh\n if (this.config.autoRefresh && tokens.refresh_token) {\n this.setupAutoRefresh();\n }\n\n // Notify listeners\n this.notifyListeners();\n\n return {\n success: true,\n user: user || undefined,\n accessToken: tokens.access_token,\n idToken: tokens.id_token,\n refreshToken: tokens.refresh_token,\n };\n } catch (error) {\n return {\n success: false,\n error: 'token_exchange_failed',\n errorDescription: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n }\n\n /**\n * Exchange authorization code for tokens\n */\n private async exchangeCode(code: string): Promise<TokenResponse> {\n const discovery = await this.getDiscovery();\n\n const body: Record<string, string> = {\n grant_type: 'authorization_code',\n client_id: this.config.clientId,\n code,\n redirect_uri: this.config.redirectUri,\n };\n\n // Add PKCE code verifier\n if (this.config.usePkce) {\n const codeVerifier = this.storage.get(STORAGE_KEYS.CODE_VERIFIER);\n if (codeVerifier) {\n body.code_verifier = codeVerifier;\n }\n }\n\n const response = await fetch(discovery.token_endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams(body),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(error.error_description || error.error || 'Token exchange failed');\n }\n\n return response.json();\n }\n\n /**\n * Refresh access token using refresh token\n */\n async refreshToken(): Promise<TokenResponse | null> {\n const refreshToken = this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);\n if (!refreshToken) return null;\n\n const discovery = await this.getDiscovery();\n\n const response = await fetch(discovery.token_endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: this.config.clientId,\n refresh_token: refreshToken,\n }),\n });\n\n if (!response.ok) {\n // Refresh failed - clear tokens\n this.clearTokens();\n this.notifyListeners();\n return null;\n }\n\n const tokens: TokenResponse = await response.json();\n this.storeTokens(tokens);\n this.notifyListeners();\n\n return tokens;\n }\n\n /**\n * Logout - end session\n */\n async logout(options: LogoutOptions = {}): Promise<void> {\n const discovery = await this.getDiscovery();\n const idToken = this.storage.get(STORAGE_KEYS.ID_TOKEN);\n\n // Clear local tokens first\n this.clearTokens();\n this.notifyListeners();\n\n // Redirect to end session endpoint if available\n if (discovery.end_session_endpoint) {\n const params: Record<string, string | undefined> = {\n post_logout_redirect_uri: options.returnTo ?? this.config.postLogoutRedirectUri,\n id_token_hint: options.idTokenHint ?? idToken ?? undefined,\n client_id: this.config.clientId,\n };\n\n const logoutUrl = buildUrl(discovery.end_session_endpoint, params);\n window.location.href = logoutUrl;\n }\n }\n\n /**\n * Get current access token (refreshes if needed)\n */\n async getAccessToken(): Promise<string | null> {\n const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);\n \n if (!accessToken) return null;\n\n // Check if token needs refresh\n if (isTokenExpired(accessToken, this.config.refreshThreshold)) {\n const tokens = await this.refreshToken();\n return tokens?.access_token ?? null;\n }\n\n return accessToken;\n }\n\n /**\n * Get current user from stored ID token\n */\n getUser(): User | null {\n const idToken = this.storage.get(STORAGE_KEYS.ID_TOKEN);\n if (!idToken) return null;\n\n return decodeJwtPayload<User>(idToken);\n }\n\n /**\n * Fetch user info from userinfo endpoint\n */\n async fetchUserInfo(accessToken?: string): Promise<User | null> {\n const token = accessToken ?? await this.getAccessToken();\n if (!token) return null;\n\n const discovery = await this.getDiscovery();\n\n const response = await fetch(discovery.userinfo_endpoint, {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!response.ok) return null;\n\n const user: User = await response.json();\n this.storage.set(STORAGE_KEYS.USER, JSON.stringify(user));\n return user;\n }\n\n /**\n * Check if user is authenticated\n */\n isAuthenticated(): boolean {\n const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);\n if (!accessToken) return false;\n\n // Consider authenticated if token exists and not expired\n return !isTokenExpired(accessToken);\n }\n\n /**\n * Get current auth state\n */\n getAuthState(): AuthState {\n const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);\n const idToken = this.storage.get(STORAGE_KEYS.ID_TOKEN);\n const user = this.getUser();\n\n return {\n isAuthenticated: this.isAuthenticated(),\n isLoading: false,\n user,\n accessToken,\n idToken,\n error: null,\n };\n }\n\n /**\n * Subscribe to auth state changes\n */\n subscribe(listener: (state: AuthState) => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Store tokens in storage\n */\n private storeTokens(tokens: TokenResponse): void {\n this.storage.set(STORAGE_KEYS.ACCESS_TOKEN, tokens.access_token);\n \n if (tokens.refresh_token) {\n this.storage.set(STORAGE_KEYS.REFRESH_TOKEN, tokens.refresh_token);\n }\n \n if (tokens.id_token) {\n this.storage.set(STORAGE_KEYS.ID_TOKEN, tokens.id_token);\n }\n\n // Store expiry time\n const expiresAt = Date.now() + (tokens.expires_in * 1000);\n this.storage.set(STORAGE_KEYS.EXPIRES_AT, expiresAt.toString());\n }\n\n /**\n * Clear all stored tokens\n */\n private clearTokens(): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n\n this.storage.clear();\n }\n\n /**\n * Setup auto-refresh timer\n */\n private setupAutoRefresh(): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n }\n\n const expiresAt = this.storage.get(STORAGE_KEYS.EXPIRES_AT);\n if (!expiresAt) return;\n\n const expiresAtMs = parseInt(expiresAt, 10);\n const refreshAt = expiresAtMs - (this.config.refreshThreshold * 1000);\n const delay = refreshAt - Date.now();\n\n if (delay > 0) {\n this.refreshTimer = setTimeout(async () => {\n await this.refreshToken();\n this.setupAutoRefresh();\n }, delay);\n }\n }\n\n /**\n * Notify all listeners of state change\n */\n private notifyListeners(): void {\n const state = this.getAuthState();\n this.listeners.forEach(listener => listener(state));\n }\n}\n\n/**\n * Create a new Stuffle IAM client instance\n */\nexport function createStuffleIAMClient(config: StuffleIAMConfig): StuffleIAMClient {\n return new StuffleIAMClient(config);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACOO,SAAS,qBAAqB,SAAiB,IAAY;AAChE,QAAM,QAAQ,IAAI,WAAW,MAAM;AACnC,SAAO,gBAAgB,KAAK;AAC5B,SAAO,MAAM,KAAK,OAAO,UAAQ,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC9E;AAKO,SAAS,uBAA+B;AAC7C,QAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,SAAO,gBAAgB,KAAK;AAC5B,SAAO,gBAAgB,KAAK;AAC9B;AAKA,eAAsB,sBAAsB,UAAmC;AAC7E,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,OAAO,QAAQ,OAAO,QAAQ;AACpC,QAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI;AACzD,SAAO,gBAAgB,IAAI,WAAW,MAAM,CAAC;AAC/C;AAKO,SAAS,gBAAgB,QAA4B;AAC1D,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,cAAU,OAAO,aAAa,OAAO,CAAC,CAAC;AAAA,EACzC;AACA,SAAO,KAAK,MAAM,EACf,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AACtB;AAKO,SAAS,iBAA8C,OAAyB;AACrF,MAAI;AACF,UAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,UAAM,UAAU,MAAM,CAAC;AACvB,UAAM,UAAU,KAAK,QAAQ,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,CAAC;AAClE,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,eAAe,OAAe,mBAA2B,GAAY;AACnF,QAAM,UAAU,iBAAmC,KAAK;AACxD,MAAI,CAAC,SAAS,IAAK,QAAO;AAE1B,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,SAAO,QAAQ,MAAM,oBAAoB;AAC3C;AAKO,SAAS,eAAe,KAAqC;AAClE,QAAM,SAAiC,CAAC;AAGxC,QAAM,YAAY,IAAI,QAAQ,GAAG;AACjC,QAAM,aAAa,IAAI,QAAQ,GAAG;AAElC,MAAI,cAAc;AAClB,MAAI,cAAc,IAAI;AACpB,kBAAc,IAAI,UAAU,YAAY,CAAC;AAAA,EAC3C,WAAW,eAAe,IAAI;AAC5B,kBAAc,IAAI,UAAU,aAAa,CAAC;AAAA,EAC5C;AAEA,MAAI,CAAC,YAAa,QAAO;AAEzB,QAAM,eAAe,IAAI,gBAAgB,WAAW;AACpD,eAAa,QAAQ,CAAC,OAAO,QAAQ;AACnC,WAAO,GAAG,IAAI;AAAA,EAChB,CAAC;AAED,SAAO;AACT;AAKO,SAAS,SAAS,MAAc,QAAoD;AACzF,QAAM,MAAM,IAAI,IAAI,IAAI;AACxB,SAAO,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAC/C,QAAI,UAAU,UAAa,UAAU,MAAM;AACzC,UAAI,aAAa,OAAO,KAAK,KAAK;AAAA,IACpC;AAAA,EACF,CAAC;AACD,SAAO,IAAI,SAAS;AACtB;;;ACpGA,IAAM,iBAAiB;AAKhB,IAAM,sBAAN,MAAkD;AAAA,EACvD,IAAI,KAA4B;AAC9B,QAAI;AACF,aAAO,aAAa,QAAQ,iBAAiB,GAAG;AAAA,IAClD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,IAAI,KAAa,OAAqB;AACpC,QAAI;AACF,mBAAa,QAAQ,iBAAiB,KAAK,KAAK;AAAA,IAClD,QAAQ;AACN,cAAQ,KAAK,4BAA4B;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,OAAO,KAAmB;AACxB,QAAI;AACF,mBAAa,WAAW,iBAAiB,GAAG;AAAA,IAC9C,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,QAAI;AACF,aAAO,KAAK,YAAY,EACrB,OAAO,OAAK,EAAE,WAAW,cAAc,CAAC,EACxC,QAAQ,OAAK,aAAa,WAAW,CAAC,CAAC;AAAA,IAC5C,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAKO,IAAM,wBAAN,MAAoD;AAAA,EACzD,IAAI,KAA4B;AAC9B,QAAI;AACF,aAAO,eAAe,QAAQ,iBAAiB,GAAG;AAAA,IACpD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,IAAI,KAAa,OAAqB;AACpC,QAAI;AACF,qBAAe,QAAQ,iBAAiB,KAAK,KAAK;AAAA,IACpD,QAAQ;AACN,cAAQ,KAAK,8BAA8B;AAAA,IAC7C;AAAA,EACF;AAAA,EAEA,OAAO,KAAmB;AACxB,QAAI;AACF,qBAAe,WAAW,iBAAiB,GAAG;AAAA,IAChD,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,QAAI;AACF,aAAO,KAAK,cAAc,EACvB,OAAO,OAAK,EAAE,WAAW,cAAc,CAAC,EACxC,QAAQ,OAAK,eAAe,WAAW,CAAC,CAAC;AAAA,IAC9C,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAKO,IAAM,uBAAN,MAAmD;AAAA,EAAnD;AACL,SAAQ,QAAQ,oBAAI,IAAoB;AAAA;AAAA,EAExC,IAAI,KAA4B;AAC9B,WAAO,KAAK,MAAM,IAAI,GAAG,KAAK;AAAA,EAChC;AAAA,EAEA,IAAI,KAAa,OAAqB;AACpC,SAAK,MAAM,IAAI,KAAK,KAAK;AAAA,EAC3B;AAAA,EAEA,OAAO,KAAmB;AACxB,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AAAA,EAEA,QAAc;AACZ,SAAK,MAAM,MAAM;AAAA,EACnB;AACF;AAKO,SAAS,WAAW,MAAkE;AAC3F,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,IAAI,oBAAoB;AAAA,IACjC,KAAK;AACH,aAAO,IAAI,sBAAsB;AAAA,IACnC,KAAK;AACH,aAAO,IAAI,qBAAqB;AAAA,IAClC;AACE,aAAO,IAAI,sBAAsB;AAAA,EACrC;AACF;;;ACpGA,IAAM,eAAe;AAAA,EACnB,cAAc;AAAA,EACd,eAAe;AAAA,EACf,UAAU;AAAA,EACV,eAAe;AAAA,EACf,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAM;AAAA,EACN,YAAY;AACd;AAEO,IAAM,mBAAN,MAAuB;AAAA,EAO5B,YAAY,QAA0B;AAJtC,SAAQ,YAAkC;AAC1C,SAAQ,eAAqD;AAC7D,SAAQ,YAA6C,oBAAI,IAAI;AAG3D,SAAK,SAAS;AAAA,MACZ,QAAQ,OAAO,OAAO,QAAQ,OAAO,EAAE;AAAA;AAAA,MACvC,UAAU,OAAO;AAAA,MACjB,aAAa,OAAO;AAAA,MACpB,QAAQ,OAAO,UAAU,CAAC,UAAU,WAAW,OAAO;AAAA,MACtD,uBAAuB,OAAO,yBAAyB,OAAO;AAAA,MAC9D,SAAS,OAAO,WAAW;AAAA,MAC3B,SAAS,OAAO,WAAW;AAAA,MAC3B,aAAa,OAAO,eAAe;AAAA,MACnC,kBAAkB,OAAO,oBAAoB;AAAA,IAC/C;AAEA,SAAK,UAAU,WAAW,KAAK,OAAO,OAAO;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAuC;AAC3C,QAAI,KAAK,UAAW,QAAO,KAAK;AAEhC,UAAM,WAAW,MAAM;AAAA,MACrB,GAAG,KAAK,OAAO,MAAM;AAAA,IACvB;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,mCAAmC,SAAS,MAAM,EAAE;AAAA,IACtE;AAEA,SAAK,YAAY,MAAM,SAAS,KAAK;AACrC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM,UAAwB,CAAC,GAAkB;AACrD,UAAM,YAAY,MAAM,KAAK,aAAa;AAE1C,UAAM,QAAQ,QAAQ,SAAS,qBAAqB;AACpD,UAAM,QAAQ,QAAQ,SAAS,qBAAqB;AACpD,UAAM,SAAS,CAAC,GAAG,KAAK,OAAO,QAAQ,GAAI,QAAQ,UAAU,CAAC,CAAE;AAGhE,SAAK,QAAQ,IAAI,aAAa,OAAO,KAAK;AAC1C,SAAK,QAAQ,IAAI,aAAa,OAAO,KAAK;AAE1C,UAAM,SAA6C;AAAA,MACjD,WAAW,KAAK,OAAO;AAAA,MACvB,cAAc,KAAK,OAAO;AAAA,MAC1B,eAAe;AAAA,MACf,OAAO,OAAO,KAAK,GAAG;AAAA,MACtB;AAAA,MACA;AAAA,MACA,QAAQ,QAAQ;AAAA,MAChB,YAAY,QAAQ;AAAA,IACtB;AAGA,QAAI,KAAK,OAAO,SAAS;AACvB,YAAM,eAAe,qBAAqB;AAC1C,YAAM,gBAAgB,MAAM,sBAAsB,YAAY;AAE9D,WAAK,QAAQ,IAAI,aAAa,eAAe,YAAY;AACzD,aAAO,iBAAiB;AACxB,aAAO,wBAAwB;AAAA,IACjC;AAGA,UAAM,WAAW,QAAQ,SACrB,UAAU,uBAAuB,QAAQ,cAAc,mBAAmB,IAC1E,UAAU;AAEd,UAAM,UAAU,SAAS,UAAU,MAAM;AACzC,WAAO,SAAS,OAAO;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,UAAwC,CAAC,GAAkB;AACtE,WAAO,KAAK,MAAM,EAAE,GAAG,SAAS,QAAQ,KAAK,CAAC;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,KAAuC;AAC1D,UAAM,cAAc,OAAO,OAAO,SAAS;AAC3C,UAAM,SAAS,eAAe,WAAW;AAGzC,QAAI,OAAO,OAAO;AAChB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,OAAO;AAAA,QACd,kBAAkB,OAAO;AAAA,MAC3B;AAAA,IACF;AAGA,UAAM,cAAc,KAAK,QAAQ,IAAI,aAAa,KAAK;AACvD,QAAI,CAAC,eAAe,gBAAgB,OAAO,OAAO;AAChD,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,QACP,kBAAkB;AAAA,MACpB;AAAA,IACF;AAGA,QAAI,CAAC,OAAO,MAAM;AAChB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,QACP,kBAAkB;AAAA,MACpB;AAAA,IACF;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,aAAa,OAAO,IAAI;AAGlD,UAAI,OAAO,UAAU;AACnB,cAAM,UAAU,iBAAqC,OAAO,QAAQ;AACpE,cAAM,cAAc,KAAK,QAAQ,IAAI,aAAa,KAAK;AACvD,YAAI,SAAS,UAAU,aAAa;AAClC,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,OAAO;AAAA,YACP,kBAAkB;AAAA,UACpB;AAAA,QACF;AAAA,MACF;AAGA,WAAK,YAAY,MAAM;AAGvB,WAAK,QAAQ,OAAO,aAAa,KAAK;AACtC,WAAK,QAAQ,OAAO,aAAa,KAAK;AACtC,WAAK,QAAQ,OAAO,aAAa,aAAa;AAG9C,YAAM,OAAO,MAAM,KAAK,cAAc,OAAO,YAAY;AAGzD,UAAI,KAAK,OAAO,eAAe,OAAO,eAAe;AACnD,aAAK,iBAAiB;AAAA,MACxB;AAGA,WAAK,gBAAgB;AAErB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,MAAM,QAAQ;AAAA,QACd,aAAa,OAAO;AAAA,QACpB,SAAS,OAAO;AAAA,QAChB,cAAc,OAAO;AAAA,MACvB;AAAA,IACF,SAAS,OAAO;AACd,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,QACP,kBAAkB,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aAAa,MAAsC;AAC/D,UAAM,YAAY,MAAM,KAAK,aAAa;AAE1C,UAAM,OAA+B;AAAA,MACnC,YAAY;AAAA,MACZ,WAAW,KAAK,OAAO;AAAA,MACvB;AAAA,MACA,cAAc,KAAK,OAAO;AAAA,IAC5B;AAGA,QAAI,KAAK,OAAO,SAAS;AACvB,YAAM,eAAe,KAAK,QAAQ,IAAI,aAAa,aAAa;AAChE,UAAI,cAAc;AAChB,aAAK,gBAAgB;AAAA,MACvB;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,MAAM,UAAU,gBAAgB;AAAA,MACrD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,IAAI,gBAAgB,IAAI;AAAA,IAChC,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,QAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACpD,YAAM,IAAI,MAAM,MAAM,qBAAqB,MAAM,SAAS,uBAAuB;AAAA,IACnF;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAA8C;AAClD,UAAM,eAAe,KAAK,QAAQ,IAAI,aAAa,aAAa;AAChE,QAAI,CAAC,aAAc,QAAO;AAE1B,UAAM,YAAY,MAAM,KAAK,aAAa;AAE1C,UAAM,WAAW,MAAM,MAAM,UAAU,gBAAgB;AAAA,MACrD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,IAAI,gBAAgB;AAAA,QACxB,YAAY;AAAA,QACZ,WAAW,KAAK,OAAO;AAAA,QACvB,eAAe;AAAA,MACjB,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAEhB,WAAK,YAAY;AACjB,WAAK,gBAAgB;AACrB,aAAO;AAAA,IACT;AAEA,UAAM,SAAwB,MAAM,SAAS,KAAK;AAClD,SAAK,YAAY,MAAM;AACvB,SAAK,gBAAgB;AAErB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,UAAyB,CAAC,GAAkB;AACvD,UAAM,YAAY,MAAM,KAAK,aAAa;AAC1C,UAAM,UAAU,KAAK,QAAQ,IAAI,aAAa,QAAQ;AAGtD,SAAK,YAAY;AACjB,SAAK,gBAAgB;AAGrB,QAAI,UAAU,sBAAsB;AAClC,YAAM,SAA6C;AAAA,QACjD,0BAA0B,QAAQ,YAAY,KAAK,OAAO;AAAA,QAC1D,eAAe,QAAQ,eAAe,WAAW;AAAA,QACjD,WAAW,KAAK,OAAO;AAAA,MACzB;AAEA,YAAM,YAAY,SAAS,UAAU,sBAAsB,MAAM;AACjE,aAAO,SAAS,OAAO;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAyC;AAC7C,UAAM,cAAc,KAAK,QAAQ,IAAI,aAAa,YAAY;AAE9D,QAAI,CAAC,YAAa,QAAO;AAGzB,QAAI,eAAe,aAAa,KAAK,OAAO,gBAAgB,GAAG;AAC7D,YAAM,SAAS,MAAM,KAAK,aAAa;AACvC,aAAO,QAAQ,gBAAgB;AAAA,IACjC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,UAAuB;AACrB,UAAM,UAAU,KAAK,QAAQ,IAAI,aAAa,QAAQ;AACtD,QAAI,CAAC,QAAS,QAAO;AAErB,WAAO,iBAAuB,OAAO;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,aAA4C;AAC9D,UAAM,QAAQ,eAAe,MAAM,KAAK,eAAe;AACvD,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,YAAY,MAAM,KAAK,aAAa;AAE1C,UAAM,WAAW,MAAM,MAAM,UAAU,mBAAmB;AAAA,MACxD,SAAS;AAAA,QACP,eAAe,UAAU,KAAK;AAAA,MAChC;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,GAAI,QAAO;AAEzB,UAAM,OAAa,MAAM,SAAS,KAAK;AACvC,SAAK,QAAQ,IAAI,aAAa,MAAM,KAAK,UAAU,IAAI,CAAC;AACxD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,kBAA2B;AACzB,UAAM,cAAc,KAAK,QAAQ,IAAI,aAAa,YAAY;AAC9D,QAAI,CAAC,YAAa,QAAO;AAGzB,WAAO,CAAC,eAAe,WAAW;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,eAA0B;AACxB,UAAM,cAAc,KAAK,QAAQ,IAAI,aAAa,YAAY;AAC9D,UAAM,UAAU,KAAK,QAAQ,IAAI,aAAa,QAAQ;AACtD,UAAM,OAAO,KAAK,QAAQ;AAE1B,WAAO;AAAA,MACL,iBAAiB,KAAK,gBAAgB;AAAA,MACtC,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,UAAkD;AAC1D,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM,KAAK,UAAU,OAAO,QAAQ;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY,QAA6B;AAC/C,SAAK,QAAQ,IAAI,aAAa,cAAc,OAAO,YAAY;AAE/D,QAAI,OAAO,eAAe;AACxB,WAAK,QAAQ,IAAI,aAAa,eAAe,OAAO,aAAa;AAAA,IACnE;AAEA,QAAI,OAAO,UAAU;AACnB,WAAK,QAAQ,IAAI,aAAa,UAAU,OAAO,QAAQ;AAAA,IACzD;AAGA,UAAM,YAAY,KAAK,IAAI,IAAK,OAAO,aAAa;AACpD,SAAK,QAAQ,IAAI,aAAa,YAAY,UAAU,SAAS,CAAC;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAoB;AAC1B,QAAI,KAAK,cAAc;AACrB,mBAAa,KAAK,YAAY;AAC9B,WAAK,eAAe;AAAA,IACtB;AAEA,SAAK,QAAQ,MAAM;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAyB;AAC/B,QAAI,KAAK,cAAc;AACrB,mBAAa,KAAK,YAAY;AAAA,IAChC;AAEA,UAAM,YAAY,KAAK,QAAQ,IAAI,aAAa,UAAU;AAC1D,QAAI,CAAC,UAAW;AAEhB,UAAM,cAAc,SAAS,WAAW,EAAE;AAC1C,UAAM,YAAY,cAAe,KAAK,OAAO,mBAAmB;AAChE,UAAM,QAAQ,YAAY,KAAK,IAAI;AAEnC,QAAI,QAAQ,GAAG;AACb,WAAK,eAAe,WAAW,YAAY;AACzC,cAAM,KAAK,aAAa;AACxB,aAAK,iBAAiB;AAAA,MACxB,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAwB;AAC9B,UAAM,QAAQ,KAAK,aAAa;AAChC,SAAK,UAAU,QAAQ,cAAY,SAAS,KAAK,CAAC;AAAA,EACpD;AACF;AAKO,SAAS,uBAAuB,QAA4C;AACjF,SAAO,IAAI,iBAAiB,MAAM;AACpC;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["/**\n * Stuffle IAM SDK\n * \n * JavaScript/TypeScript SDK for integrating with Stuffle IAM.\n * Supports OIDC/OAuth2 authorization code flow with PKCE.\n * \n * @example\n * ```ts\n * import { createStuffleIAMClient } from '@stuffle/iam-sdk';\n * \n * const iam = createStuffleIAMClient({\n * issuer: 'http://localhost:5000',\n * clientId: 'my-app',\n * redirectUri: 'http://localhost:3000/callback',\n * });\n * \n * // Start login\n * await iam.login();\n * \n * // Handle callback\n * const result = await iam.handleCallback();\n * \n * // Get user\n * const user = iam.getUser();\n * ```\n */\n\nexport { StuffleIAMClient, createStuffleIAMClient } from './client';\nexport type {\n StuffleIAMConfig,\n TokenResponse,\n User,\n AuthState,\n LoginOptions,\n LogoutOptions,\n CallbackResult,\n OIDCDiscovery,\n} from './types';\nexport {\n generateRandomString,\n generateCodeVerifier,\n generateCodeChallenge,\n decodeJwtPayload,\n isTokenExpired,\n} from './utils';\nexport { getStorage, type TokenStorage } from './storage';\n","/**\n * Utility functions for PKCE and crypto operations\n */\n\n/**\n * Generate a random string for state/nonce\n */\nexport function generateRandomString(length: number = 32): string {\n const array = new Uint8Array(length);\n crypto.getRandomValues(array);\n return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Generate PKCE code verifier (43-128 characters)\n */\nexport function generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\n/**\n * Generate PKCE code challenge from verifier\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', data);\n return base64UrlEncode(new Uint8Array(digest));\n}\n\n/**\n * Base64 URL encode (no padding, URL-safe characters)\n */\nexport function base64UrlEncode(buffer: Uint8Array): string {\n let binary = '';\n for (let i = 0; i < buffer.length; i++) {\n binary += String.fromCharCode(buffer[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\n/**\n * Decode JWT payload (without verification)\n */\nexport function decodeJwtPayload<T = Record<string, unknown>>(token: string): T | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n \n const payload = parts[1];\n const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));\n return JSON.parse(decoded) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Check if token is expired\n */\nexport function isTokenExpired(token: string, thresholdSeconds: number = 0): boolean {\n const payload = decodeJwtPayload<{ exp?: number }>(token);\n if (!payload?.exp) return true;\n \n const now = Math.floor(Date.now() / 1000);\n return payload.exp - thresholdSeconds <= now;\n}\n\n/**\n * Parse URL hash or query parameters\n */\nexport function parseUrlParams(url: string): Record<string, string> {\n const params: Record<string, string> = {};\n \n // Check hash first (implicit flow), then query (code flow)\n const hashIndex = url.indexOf('#');\n const queryIndex = url.indexOf('?');\n \n let paramString = '';\n if (hashIndex !== -1) {\n paramString = url.substring(hashIndex + 1);\n } else if (queryIndex !== -1) {\n paramString = url.substring(queryIndex + 1);\n }\n \n if (!paramString) return params;\n \n const searchParams = new URLSearchParams(paramString);\n searchParams.forEach((value, key) => {\n params[key] = value;\n });\n \n return params;\n}\n\n/**\n * Build URL with query parameters\n */\nexport function buildUrl(base: string, params: Record<string, string | undefined>): string {\n const url = new URL(base);\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.append(key, value);\n }\n });\n return url.toString();\n}\n","/**\n * Token storage abstraction\n */\n\nexport interface TokenStorage {\n get(key: string): string | null;\n set(key: string, value: string): void;\n remove(key: string): void;\n clear(): void;\n}\n\nconst STORAGE_PREFIX = 'stuffle_iam_';\n\n/**\n * LocalStorage implementation\n */\nexport class LocalStorageAdapter implements TokenStorage {\n get(key: string): string | null {\n try {\n return localStorage.getItem(STORAGE_PREFIX + key);\n } catch {\n return null;\n }\n }\n\n set(key: string, value: string): void {\n try {\n localStorage.setItem(STORAGE_PREFIX + key, value);\n } catch {\n console.warn('LocalStorage not available');\n }\n }\n\n remove(key: string): void {\n try {\n localStorage.removeItem(STORAGE_PREFIX + key);\n } catch {\n // Ignore\n }\n }\n\n clear(): void {\n try {\n Object.keys(localStorage)\n .filter(k => k.startsWith(STORAGE_PREFIX))\n .forEach(k => localStorage.removeItem(k));\n } catch {\n // Ignore\n }\n }\n}\n\n/**\n * SessionStorage implementation\n */\nexport class SessionStorageAdapter implements TokenStorage {\n get(key: string): string | null {\n try {\n return sessionStorage.getItem(STORAGE_PREFIX + key);\n } catch {\n return null;\n }\n }\n\n set(key: string, value: string): void {\n try {\n sessionStorage.setItem(STORAGE_PREFIX + key, value);\n } catch {\n console.warn('SessionStorage not available');\n }\n }\n\n remove(key: string): void {\n try {\n sessionStorage.removeItem(STORAGE_PREFIX + key);\n } catch {\n // Ignore\n }\n }\n\n clear(): void {\n try {\n Object.keys(sessionStorage)\n .filter(k => k.startsWith(STORAGE_PREFIX))\n .forEach(k => sessionStorage.removeItem(k));\n } catch {\n // Ignore\n }\n }\n}\n\n/**\n * In-memory storage (for SSR or when storage is unavailable)\n */\nexport class MemoryStorageAdapter implements TokenStorage {\n private store = new Map<string, string>();\n\n get(key: string): string | null {\n return this.store.get(key) ?? null;\n }\n\n set(key: string, value: string): void {\n this.store.set(key, value);\n }\n\n remove(key: string): void {\n this.store.delete(key);\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n\n/**\n * Get storage adapter based on type\n */\nexport function getStorage(type: 'localStorage' | 'sessionStorage' | 'memory'): TokenStorage {\n switch (type) {\n case 'localStorage':\n return new LocalStorageAdapter();\n case 'sessionStorage':\n return new SessionStorageAdapter();\n case 'memory':\n return new MemoryStorageAdapter();\n default:\n return new SessionStorageAdapter();\n }\n}\n","/**\n * Stuffle IAM Client\n * \n * OIDC/OAuth2 client for browser-based applications.\n * Supports authorization code flow with PKCE.\n */\n\nimport type {\n StuffleIAMConfig,\n TokenResponse,\n User,\n AuthState,\n LoginOptions,\n LogoutOptions,\n CallbackResult,\n OIDCDiscovery,\n} from './types';\nimport {\n generateRandomString,\n generateCodeVerifier,\n generateCodeChallenge,\n decodeJwtPayload,\n isTokenExpired,\n parseUrlParams,\n buildUrl,\n} from './utils';\nimport { getStorage, type TokenStorage } from './storage';\n\nconst STORAGE_KEYS = {\n ACCESS_TOKEN: 'access_token',\n REFRESH_TOKEN: 'refresh_token',\n ID_TOKEN: 'id_token',\n CODE_VERIFIER: 'code_verifier',\n STATE: 'state',\n NONCE: 'nonce',\n USER: 'user',\n EXPIRES_AT: 'expires_at',\n};\n\nexport class StuffleIAMClient {\n private config: Required<StuffleIAMConfig>;\n private storage: TokenStorage;\n private discovery: OIDCDiscovery | null = null;\n private refreshTimer: ReturnType<typeof setTimeout> | null = null;\n private listeners: Set<(state: AuthState) => void> = new Set();\n private _initialized = false;\n private _initPromise: Promise<boolean> | null = null;\n\n constructor(config: StuffleIAMConfig) {\n this.config = {\n issuer: config.issuer.replace(/\\/$/, ''), // Remove trailing slash\n clientId: config.clientId,\n redirectUri: config.redirectUri,\n scopes: config.scopes ?? ['openid', 'profile', 'email'],\n postLogoutRedirectUri: config.postLogoutRedirectUri ?? config.redirectUri,\n usePkce: config.usePkce ?? true,\n storage: config.storage ?? 'sessionStorage',\n autoRefresh: config.autoRefresh ?? true,\n refreshThreshold: config.refreshThreshold ?? 60,\n };\n\n this.storage = getStorage(this.config.storage);\n }\n\n /**\n * Initialize / restore session from storage.\n * Call this on app startup (page load) to silently restore a session.\n *\n * - If access token is valid → re-setup auto-refresh timer, return true\n * - If access token is expired but refresh token exists → silent refresh, return true/false\n * - If no tokens → return false (not authenticated)\n *\n * Safe to call multiple times — subsequent calls return the same promise.\n */\n async init(): Promise<boolean> {\n if (this._initialized) return this.isAuthenticated();\n if (this._initPromise) return this._initPromise;\n\n this._initPromise = this._doInit();\n return this._initPromise;\n }\n\n private async _doInit(): Promise<boolean> {\n try {\n const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);\n const refreshTokenValue = this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);\n\n if (!accessToken && !refreshTokenValue) {\n // No session to restore\n this._initialized = true;\n return false;\n }\n\n if (accessToken && !isTokenExpired(accessToken)) {\n // Access token still valid — just re-setup auto-refresh\n if (this.config.autoRefresh && refreshTokenValue) {\n this.setupAutoRefresh();\n }\n this._initialized = true;\n this.notifyListeners();\n return true;\n }\n\n // Access token expired (or missing) but refresh token exists — silent refresh\n if (refreshTokenValue) {\n const tokens = await this.refreshToken();\n if (tokens) {\n if (this.config.autoRefresh) {\n this.setupAutoRefresh();\n }\n this._initialized = true;\n return true;\n }\n }\n\n // Refresh failed or no refresh token\n this._initialized = true;\n return false;\n } catch (err) {\n console.error('[StuffleIAMClient] init() failed:', err);\n this._initialized = true;\n return false;\n }\n }\n\n /**\n * Fetch OIDC discovery document\n */\n async getDiscovery(): Promise<OIDCDiscovery> {\n if (this.discovery) return this.discovery;\n\n const response = await fetch(\n `${this.config.issuer}/.well-known/openid-configuration`\n );\n\n if (!response.ok) {\n throw new Error(`Failed to fetch OIDC discovery: ${response.status}`);\n }\n\n this.discovery = await response.json();\n return this.discovery!;\n }\n\n /**\n * Start login flow - redirects to authorization endpoint\n */\n async login(options: LoginOptions = {}): Promise<void> {\n const discovery = await this.getDiscovery();\n\n const state = options.state ?? generateRandomString();\n const nonce = options.nonce ?? generateRandomString();\n const scopes = [...this.config.scopes, ...(options.scopes ?? [])];\n\n // Store state and nonce for callback validation\n this.storage.set(STORAGE_KEYS.STATE, state);\n this.storage.set(STORAGE_KEYS.NONCE, nonce);\n\n const params: Record<string, string | undefined> = {\n client_id: this.config.clientId,\n redirect_uri: this.config.redirectUri,\n response_type: 'code',\n scope: scopes.join(' '),\n state,\n nonce,\n prompt: options.prompt,\n login_hint: options.loginHint,\n };\n\n // Add PKCE if enabled\n if (this.config.usePkce) {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n \n this.storage.set(STORAGE_KEYS.CODE_VERIFIER, codeVerifier);\n params.code_challenge = codeChallenge;\n params.code_challenge_method = 'S256';\n }\n\n // Use signup endpoint if requested\n const endpoint = options.signup\n ? discovery.authorization_endpoint.replace('/authorize', '/authorize/signup')\n : discovery.authorization_endpoint;\n\n const authUrl = buildUrl(endpoint, params);\n window.location.href = authUrl;\n }\n\n /**\n * Alias for login({ signup: true })\n */\n async signup(options: Omit<LoginOptions, 'signup'> = {}): Promise<void> {\n return this.login({ ...options, signup: true });\n }\n\n /**\n * Handle callback from authorization server\n */\n async handleCallback(url?: string): Promise<CallbackResult> {\n const callbackUrl = url ?? window.location.href;\n const params = parseUrlParams(callbackUrl);\n\n // Check for errors\n if (params.error) {\n return {\n success: false,\n error: params.error,\n errorDescription: params.error_description,\n };\n }\n\n // Validate state\n const storedState = this.storage.get(STORAGE_KEYS.STATE);\n if (!storedState || storedState !== params.state) {\n return {\n success: false,\n error: 'invalid_state',\n errorDescription: 'State mismatch - possible CSRF attack',\n };\n }\n\n // Exchange code for tokens\n if (!params.code) {\n return {\n success: false,\n error: 'missing_code',\n errorDescription: 'No authorization code received',\n };\n }\n\n try {\n const tokens = await this.exchangeCode(params.code);\n \n // Validate nonce in ID token\n if (tokens.id_token) {\n const payload = decodeJwtPayload<{ nonce?: string }>(tokens.id_token);\n const storedNonce = this.storage.get(STORAGE_KEYS.NONCE);\n if (payload?.nonce !== storedNonce) {\n return {\n success: false,\n error: 'invalid_nonce',\n errorDescription: 'Nonce mismatch - possible replay attack',\n };\n }\n }\n\n // Store tokens\n this.storeTokens(tokens);\n\n // Clear temporary storage\n this.storage.remove(STORAGE_KEYS.STATE);\n this.storage.remove(STORAGE_KEYS.NONCE);\n this.storage.remove(STORAGE_KEYS.CODE_VERIFIER);\n\n // Get user info\n const user = await this.fetchUserInfo(tokens.access_token);\n\n // Setup auto-refresh\n if (this.config.autoRefresh && tokens.refresh_token) {\n this.setupAutoRefresh();\n }\n\n // Notify listeners\n this.notifyListeners();\n\n return {\n success: true,\n user: user || undefined,\n accessToken: tokens.access_token,\n idToken: tokens.id_token,\n refreshToken: tokens.refresh_token,\n };\n } catch (error) {\n return {\n success: false,\n error: 'token_exchange_failed',\n errorDescription: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n }\n\n /**\n * Exchange authorization code for tokens\n */\n private async exchangeCode(code: string): Promise<TokenResponse> {\n const discovery = await this.getDiscovery();\n\n const body: Record<string, string> = {\n grant_type: 'authorization_code',\n client_id: this.config.clientId,\n code,\n redirect_uri: this.config.redirectUri,\n };\n\n // Add PKCE code verifier\n if (this.config.usePkce) {\n const codeVerifier = this.storage.get(STORAGE_KEYS.CODE_VERIFIER);\n if (codeVerifier) {\n body.code_verifier = codeVerifier;\n }\n }\n\n const response = await fetch(discovery.token_endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams(body),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(error.error_description || error.error || 'Token exchange failed');\n }\n\n return response.json();\n }\n\n /**\n * Refresh access token using refresh token\n */\n async refreshToken(): Promise<TokenResponse | null> {\n const refreshToken = this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);\n if (!refreshToken) return null;\n\n const discovery = await this.getDiscovery();\n\n const response = await fetch(discovery.token_endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: this.config.clientId,\n refresh_token: refreshToken,\n }),\n });\n\n if (!response.ok) {\n // Refresh failed - clear tokens\n this.clearTokens();\n this.notifyListeners();\n return null;\n }\n\n const tokens: TokenResponse = await response.json();\n this.storeTokens(tokens);\n this.notifyListeners();\n\n return tokens;\n }\n\n /**\n * Logout - end session\n */\n async logout(options: LogoutOptions = {}): Promise<void> {\n const discovery = await this.getDiscovery();\n const idToken = this.storage.get(STORAGE_KEYS.ID_TOKEN);\n\n // Clear local tokens first\n this.clearTokens();\n this.notifyListeners();\n\n // Redirect to end session endpoint if available\n if (discovery.end_session_endpoint) {\n const params: Record<string, string | undefined> = {\n post_logout_redirect_uri: options.returnTo ?? this.config.postLogoutRedirectUri,\n id_token_hint: options.idTokenHint ?? idToken ?? undefined,\n client_id: this.config.clientId,\n };\n\n const logoutUrl = buildUrl(discovery.end_session_endpoint, params);\n window.location.href = logoutUrl;\n }\n }\n\n /**\n * Get current access token (refreshes if needed)\n */\n async getAccessToken(): Promise<string | null> {\n const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);\n \n if (!accessToken) return null;\n\n // Check if token needs refresh\n if (isTokenExpired(accessToken, this.config.refreshThreshold)) {\n const tokens = await this.refreshToken();\n return tokens?.access_token ?? null;\n }\n\n return accessToken;\n }\n\n /**\n * Get current user from stored ID token\n */\n getUser(): User | null {\n const idToken = this.storage.get(STORAGE_KEYS.ID_TOKEN);\n if (!idToken) return null;\n\n return decodeJwtPayload<User>(idToken);\n }\n\n /**\n * Fetch user info from userinfo endpoint\n */\n async fetchUserInfo(accessToken?: string): Promise<User | null> {\n const token = accessToken ?? await this.getAccessToken();\n if (!token) return null;\n\n const discovery = await this.getDiscovery();\n\n const response = await fetch(discovery.userinfo_endpoint, {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!response.ok) return null;\n\n const user: User = await response.json();\n this.storage.set(STORAGE_KEYS.USER, JSON.stringify(user));\n return user;\n }\n\n /**\n * Check if user is authenticated (synchronous).\n * Returns true if access token exists and is not expired.\n *\n * NOTE: If the access token is expired but a refresh token exists,\n * this returns false. Call init() or getAccessToken() first to\n * attempt a silent refresh.\n */\n isAuthenticated(): boolean {\n const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);\n if (!accessToken) return false;\n\n return !isTokenExpired(accessToken);\n }\n\n /**\n * Check if a refresh token exists in storage.\n * Useful for determining if a silent refresh is possible.\n */\n hasRefreshToken(): boolean {\n return !!this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);\n }\n\n /**\n * Get current auth state\n */\n getAuthState(): AuthState {\n const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);\n const idToken = this.storage.get(STORAGE_KEYS.ID_TOKEN);\n const user = this.getUser();\n\n return {\n isAuthenticated: this.isAuthenticated(),\n isLoading: false,\n user,\n accessToken,\n idToken,\n error: null,\n };\n }\n\n /**\n * Subscribe to auth state changes\n */\n subscribe(listener: (state: AuthState) => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Store tokens in storage\n */\n private storeTokens(tokens: TokenResponse): void {\n this.storage.set(STORAGE_KEYS.ACCESS_TOKEN, tokens.access_token);\n \n if (tokens.refresh_token) {\n this.storage.set(STORAGE_KEYS.REFRESH_TOKEN, tokens.refresh_token);\n }\n \n if (tokens.id_token) {\n this.storage.set(STORAGE_KEYS.ID_TOKEN, tokens.id_token);\n }\n\n // Store expiry time\n const expiresAt = Date.now() + (tokens.expires_in * 1000);\n this.storage.set(STORAGE_KEYS.EXPIRES_AT, expiresAt.toString());\n }\n\n /**\n * Clear all stored tokens\n */\n private clearTokens(): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n\n this.storage.clear();\n }\n\n /**\n * Setup auto-refresh timer\n */\n private setupAutoRefresh(): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n }\n\n const expiresAt = this.storage.get(STORAGE_KEYS.EXPIRES_AT);\n if (!expiresAt) return;\n\n const expiresAtMs = parseInt(expiresAt, 10);\n const refreshAt = expiresAtMs - (this.config.refreshThreshold * 1000);\n const delay = refreshAt - Date.now();\n\n if (delay > 0) {\n this.refreshTimer = setTimeout(async () => {\n await this.refreshToken();\n this.setupAutoRefresh();\n }, delay);\n }\n }\n\n /**\n * Notify all listeners of state change\n */\n private notifyListeners(): void {\n const state = this.getAuthState();\n this.listeners.forEach(listener => listener(state));\n }\n}\n\n/**\n * Create a new Stuffle IAM client instance\n */\nexport function createStuffleIAMClient(config: StuffleIAMConfig): StuffleIAMClient {\n return new StuffleIAMClient(config);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACOO,SAAS,qBAAqB,SAAiB,IAAY;AAChE,QAAM,QAAQ,IAAI,WAAW,MAAM;AACnC,SAAO,gBAAgB,KAAK;AAC5B,SAAO,MAAM,KAAK,OAAO,UAAQ,KAAK,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC9E;AAKO,SAAS,uBAA+B;AAC7C,QAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,SAAO,gBAAgB,KAAK;AAC5B,SAAO,gBAAgB,KAAK;AAC9B;AAKA,eAAsB,sBAAsB,UAAmC;AAC7E,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,OAAO,QAAQ,OAAO,QAAQ;AACpC,QAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI;AACzD,SAAO,gBAAgB,IAAI,WAAW,MAAM,CAAC;AAC/C;AAKO,SAAS,gBAAgB,QAA4B;AAC1D,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,cAAU,OAAO,aAAa,OAAO,CAAC,CAAC;AAAA,EACzC;AACA,SAAO,KAAK,MAAM,EACf,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AACtB;AAKO,SAAS,iBAA8C,OAAyB;AACrF,MAAI;AACF,UAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,UAAM,UAAU,MAAM,CAAC;AACvB,UAAM,UAAU,KAAK,QAAQ,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,CAAC;AAClE,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,eAAe,OAAe,mBAA2B,GAAY;AACnF,QAAM,UAAU,iBAAmC,KAAK;AACxD,MAAI,CAAC,SAAS,IAAK,QAAO;AAE1B,QAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AACxC,SAAO,QAAQ,MAAM,oBAAoB;AAC3C;AAKO,SAAS,eAAe,KAAqC;AAClE,QAAM,SAAiC,CAAC;AAGxC,QAAM,YAAY,IAAI,QAAQ,GAAG;AACjC,QAAM,aAAa,IAAI,QAAQ,GAAG;AAElC,MAAI,cAAc;AAClB,MAAI,cAAc,IAAI;AACpB,kBAAc,IAAI,UAAU,YAAY,CAAC;AAAA,EAC3C,WAAW,eAAe,IAAI;AAC5B,kBAAc,IAAI,UAAU,aAAa,CAAC;AAAA,EAC5C;AAEA,MAAI,CAAC,YAAa,QAAO;AAEzB,QAAM,eAAe,IAAI,gBAAgB,WAAW;AACpD,eAAa,QAAQ,CAAC,OAAO,QAAQ;AACnC,WAAO,GAAG,IAAI;AAAA,EAChB,CAAC;AAED,SAAO;AACT;AAKO,SAAS,SAAS,MAAc,QAAoD;AACzF,QAAM,MAAM,IAAI,IAAI,IAAI;AACxB,SAAO,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAC/C,QAAI,UAAU,UAAa,UAAU,MAAM;AACzC,UAAI,aAAa,OAAO,KAAK,KAAK;AAAA,IACpC;AAAA,EACF,CAAC;AACD,SAAO,IAAI,SAAS;AACtB;;;ACpGA,IAAM,iBAAiB;AAKhB,IAAM,sBAAN,MAAkD;AAAA,EACvD,IAAI,KAA4B;AAC9B,QAAI;AACF,aAAO,aAAa,QAAQ,iBAAiB,GAAG;AAAA,IAClD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,IAAI,KAAa,OAAqB;AACpC,QAAI;AACF,mBAAa,QAAQ,iBAAiB,KAAK,KAAK;AAAA,IAClD,QAAQ;AACN,cAAQ,KAAK,4BAA4B;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,OAAO,KAAmB;AACxB,QAAI;AACF,mBAAa,WAAW,iBAAiB,GAAG;AAAA,IAC9C,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,QAAI;AACF,aAAO,KAAK,YAAY,EACrB,OAAO,OAAK,EAAE,WAAW,cAAc,CAAC,EACxC,QAAQ,OAAK,aAAa,WAAW,CAAC,CAAC;AAAA,IAC5C,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAKO,IAAM,wBAAN,MAAoD;AAAA,EACzD,IAAI,KAA4B;AAC9B,QAAI;AACF,aAAO,eAAe,QAAQ,iBAAiB,GAAG;AAAA,IACpD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,IAAI,KAAa,OAAqB;AACpC,QAAI;AACF,qBAAe,QAAQ,iBAAiB,KAAK,KAAK;AAAA,IACpD,QAAQ;AACN,cAAQ,KAAK,8BAA8B;AAAA,IAC7C;AAAA,EACF;AAAA,EAEA,OAAO,KAAmB;AACxB,QAAI;AACF,qBAAe,WAAW,iBAAiB,GAAG;AAAA,IAChD,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,QAAI;AACF,aAAO,KAAK,cAAc,EACvB,OAAO,OAAK,EAAE,WAAW,cAAc,CAAC,EACxC,QAAQ,OAAK,eAAe,WAAW,CAAC,CAAC;AAAA,IAC9C,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAKO,IAAM,uBAAN,MAAmD;AAAA,EAAnD;AACL,SAAQ,QAAQ,oBAAI,IAAoB;AAAA;AAAA,EAExC,IAAI,KAA4B;AAC9B,WAAO,KAAK,MAAM,IAAI,GAAG,KAAK;AAAA,EAChC;AAAA,EAEA,IAAI,KAAa,OAAqB;AACpC,SAAK,MAAM,IAAI,KAAK,KAAK;AAAA,EAC3B;AAAA,EAEA,OAAO,KAAmB;AACxB,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AAAA,EAEA,QAAc;AACZ,SAAK,MAAM,MAAM;AAAA,EACnB;AACF;AAKO,SAAS,WAAW,MAAkE;AAC3F,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,IAAI,oBAAoB;AAAA,IACjC,KAAK;AACH,aAAO,IAAI,sBAAsB;AAAA,IACnC,KAAK;AACH,aAAO,IAAI,qBAAqB;AAAA,IAClC;AACE,aAAO,IAAI,sBAAsB;AAAA,EACrC;AACF;;;ACpGA,IAAM,eAAe;AAAA,EACnB,cAAc;AAAA,EACd,eAAe;AAAA,EACf,UAAU;AAAA,EACV,eAAe;AAAA,EACf,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAM;AAAA,EACN,YAAY;AACd;AAEO,IAAM,mBAAN,MAAuB;AAAA,EAS5B,YAAY,QAA0B;AANtC,SAAQ,YAAkC;AAC1C,SAAQ,eAAqD;AAC7D,SAAQ,YAA6C,oBAAI,IAAI;AAC7D,SAAQ,eAAe;AACvB,SAAQ,eAAwC;AAG9C,SAAK,SAAS;AAAA,MACZ,QAAQ,OAAO,OAAO,QAAQ,OAAO,EAAE;AAAA;AAAA,MACvC,UAAU,OAAO;AAAA,MACjB,aAAa,OAAO;AAAA,MACpB,QAAQ,OAAO,UAAU,CAAC,UAAU,WAAW,OAAO;AAAA,MACtD,uBAAuB,OAAO,yBAAyB,OAAO;AAAA,MAC9D,SAAS,OAAO,WAAW;AAAA,MAC3B,SAAS,OAAO,WAAW;AAAA,MAC3B,aAAa,OAAO,eAAe;AAAA,MACnC,kBAAkB,OAAO,oBAAoB;AAAA,IAC/C;AAEA,SAAK,UAAU,WAAW,KAAK,OAAO,OAAO;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,OAAyB;AAC7B,QAAI,KAAK,aAAc,QAAO,KAAK,gBAAgB;AACnD,QAAI,KAAK,aAAc,QAAO,KAAK;AAEnC,SAAK,eAAe,KAAK,QAAQ;AACjC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,UAA4B;AACxC,QAAI;AACF,YAAM,cAAc,KAAK,QAAQ,IAAI,aAAa,YAAY;AAC9D,YAAM,oBAAoB,KAAK,QAAQ,IAAI,aAAa,aAAa;AAErE,UAAI,CAAC,eAAe,CAAC,mBAAmB;AAEtC,aAAK,eAAe;AACpB,eAAO;AAAA,MACT;AAEA,UAAI,eAAe,CAAC,eAAe,WAAW,GAAG;AAE/C,YAAI,KAAK,OAAO,eAAe,mBAAmB;AAChD,eAAK,iBAAiB;AAAA,QACxB;AACA,aAAK,eAAe;AACpB,aAAK,gBAAgB;AACrB,eAAO;AAAA,MACT;AAGA,UAAI,mBAAmB;AACrB,cAAM,SAAS,MAAM,KAAK,aAAa;AACvC,YAAI,QAAQ;AACV,cAAI,KAAK,OAAO,aAAa;AAC3B,iBAAK,iBAAiB;AAAA,UACxB;AACA,eAAK,eAAe;AACpB,iBAAO;AAAA,QACT;AAAA,MACF;AAGA,WAAK,eAAe;AACpB,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,cAAQ,MAAM,qCAAqC,GAAG;AACtD,WAAK,eAAe;AACpB,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAuC;AAC3C,QAAI,KAAK,UAAW,QAAO,KAAK;AAEhC,UAAM,WAAW,MAAM;AAAA,MACrB,GAAG,KAAK,OAAO,MAAM;AAAA,IACvB;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,mCAAmC,SAAS,MAAM,EAAE;AAAA,IACtE;AAEA,SAAK,YAAY,MAAM,SAAS,KAAK;AACrC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM,UAAwB,CAAC,GAAkB;AACrD,UAAM,YAAY,MAAM,KAAK,aAAa;AAE1C,UAAM,QAAQ,QAAQ,SAAS,qBAAqB;AACpD,UAAM,QAAQ,QAAQ,SAAS,qBAAqB;AACpD,UAAM,SAAS,CAAC,GAAG,KAAK,OAAO,QAAQ,GAAI,QAAQ,UAAU,CAAC,CAAE;AAGhE,SAAK,QAAQ,IAAI,aAAa,OAAO,KAAK;AAC1C,SAAK,QAAQ,IAAI,aAAa,OAAO,KAAK;AAE1C,UAAM,SAA6C;AAAA,MACjD,WAAW,KAAK,OAAO;AAAA,MACvB,cAAc,KAAK,OAAO;AAAA,MAC1B,eAAe;AAAA,MACf,OAAO,OAAO,KAAK,GAAG;AAAA,MACtB;AAAA,MACA;AAAA,MACA,QAAQ,QAAQ;AAAA,MAChB,YAAY,QAAQ;AAAA,IACtB;AAGA,QAAI,KAAK,OAAO,SAAS;AACvB,YAAM,eAAe,qBAAqB;AAC1C,YAAM,gBAAgB,MAAM,sBAAsB,YAAY;AAE9D,WAAK,QAAQ,IAAI,aAAa,eAAe,YAAY;AACzD,aAAO,iBAAiB;AACxB,aAAO,wBAAwB;AAAA,IACjC;AAGA,UAAM,WAAW,QAAQ,SACrB,UAAU,uBAAuB,QAAQ,cAAc,mBAAmB,IAC1E,UAAU;AAEd,UAAM,UAAU,SAAS,UAAU,MAAM;AACzC,WAAO,SAAS,OAAO;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,UAAwC,CAAC,GAAkB;AACtE,WAAO,KAAK,MAAM,EAAE,GAAG,SAAS,QAAQ,KAAK,CAAC;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAAe,KAAuC;AAC1D,UAAM,cAAc,OAAO,OAAO,SAAS;AAC3C,UAAM,SAAS,eAAe,WAAW;AAGzC,QAAI,OAAO,OAAO;AAChB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,OAAO;AAAA,QACd,kBAAkB,OAAO;AAAA,MAC3B;AAAA,IACF;AAGA,UAAM,cAAc,KAAK,QAAQ,IAAI,aAAa,KAAK;AACvD,QAAI,CAAC,eAAe,gBAAgB,OAAO,OAAO;AAChD,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,QACP,kBAAkB;AAAA,MACpB;AAAA,IACF;AAGA,QAAI,CAAC,OAAO,MAAM;AAChB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,QACP,kBAAkB;AAAA,MACpB;AAAA,IACF;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,aAAa,OAAO,IAAI;AAGlD,UAAI,OAAO,UAAU;AACnB,cAAM,UAAU,iBAAqC,OAAO,QAAQ;AACpE,cAAM,cAAc,KAAK,QAAQ,IAAI,aAAa,KAAK;AACvD,YAAI,SAAS,UAAU,aAAa;AAClC,iBAAO;AAAA,YACL,SAAS;AAAA,YACT,OAAO;AAAA,YACP,kBAAkB;AAAA,UACpB;AAAA,QACF;AAAA,MACF;AAGA,WAAK,YAAY,MAAM;AAGvB,WAAK,QAAQ,OAAO,aAAa,KAAK;AACtC,WAAK,QAAQ,OAAO,aAAa,KAAK;AACtC,WAAK,QAAQ,OAAO,aAAa,aAAa;AAG9C,YAAM,OAAO,MAAM,KAAK,cAAc,OAAO,YAAY;AAGzD,UAAI,KAAK,OAAO,eAAe,OAAO,eAAe;AACnD,aAAK,iBAAiB;AAAA,MACxB;AAGA,WAAK,gBAAgB;AAErB,aAAO;AAAA,QACL,SAAS;AAAA,QACT,MAAM,QAAQ;AAAA,QACd,aAAa,OAAO;AAAA,QACpB,SAAS,OAAO;AAAA,QAChB,cAAc,OAAO;AAAA,MACvB;AAAA,IACF,SAAS,OAAO;AACd,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO;AAAA,QACP,kBAAkB,iBAAiB,QAAQ,MAAM,UAAU;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aAAa,MAAsC;AAC/D,UAAM,YAAY,MAAM,KAAK,aAAa;AAE1C,UAAM,OAA+B;AAAA,MACnC,YAAY;AAAA,MACZ,WAAW,KAAK,OAAO;AAAA,MACvB;AAAA,MACA,cAAc,KAAK,OAAO;AAAA,IAC5B;AAGA,QAAI,KAAK,OAAO,SAAS;AACvB,YAAM,eAAe,KAAK,QAAQ,IAAI,aAAa,aAAa;AAChE,UAAI,cAAc;AAChB,aAAK,gBAAgB;AAAA,MACvB;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,MAAM,UAAU,gBAAgB;AAAA,MACrD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,IAAI,gBAAgB,IAAI;AAAA,IAChC,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,QAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACpD,YAAM,IAAI,MAAM,MAAM,qBAAqB,MAAM,SAAS,uBAAuB;AAAA,IACnF;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,eAA8C;AAClD,UAAM,eAAe,KAAK,QAAQ,IAAI,aAAa,aAAa;AAChE,QAAI,CAAC,aAAc,QAAO;AAE1B,UAAM,YAAY,MAAM,KAAK,aAAa;AAE1C,UAAM,WAAW,MAAM,MAAM,UAAU,gBAAgB;AAAA,MACrD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,IAAI,gBAAgB;AAAA,QACxB,YAAY;AAAA,QACZ,WAAW,KAAK,OAAO;AAAA,QACvB,eAAe;AAAA,MACjB,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAEhB,WAAK,YAAY;AACjB,WAAK,gBAAgB;AACrB,aAAO;AAAA,IACT;AAEA,UAAM,SAAwB,MAAM,SAAS,KAAK;AAClD,SAAK,YAAY,MAAM;AACvB,SAAK,gBAAgB;AAErB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,UAAyB,CAAC,GAAkB;AACvD,UAAM,YAAY,MAAM,KAAK,aAAa;AAC1C,UAAM,UAAU,KAAK,QAAQ,IAAI,aAAa,QAAQ;AAGtD,SAAK,YAAY;AACjB,SAAK,gBAAgB;AAGrB,QAAI,UAAU,sBAAsB;AAClC,YAAM,SAA6C;AAAA,QACjD,0BAA0B,QAAQ,YAAY,KAAK,OAAO;AAAA,QAC1D,eAAe,QAAQ,eAAe,WAAW;AAAA,QACjD,WAAW,KAAK,OAAO;AAAA,MACzB;AAEA,YAAM,YAAY,SAAS,UAAU,sBAAsB,MAAM;AACjE,aAAO,SAAS,OAAO;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAyC;AAC7C,UAAM,cAAc,KAAK,QAAQ,IAAI,aAAa,YAAY;AAE9D,QAAI,CAAC,YAAa,QAAO;AAGzB,QAAI,eAAe,aAAa,KAAK,OAAO,gBAAgB,GAAG;AAC7D,YAAM,SAAS,MAAM,KAAK,aAAa;AACvC,aAAO,QAAQ,gBAAgB;AAAA,IACjC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,UAAuB;AACrB,UAAM,UAAU,KAAK,QAAQ,IAAI,aAAa,QAAQ;AACtD,QAAI,CAAC,QAAS,QAAO;AAErB,WAAO,iBAAuB,OAAO;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cAAc,aAA4C;AAC9D,UAAM,QAAQ,eAAe,MAAM,KAAK,eAAe;AACvD,QAAI,CAAC,MAAO,QAAO;AAEnB,UAAM,YAAY,MAAM,KAAK,aAAa;AAE1C,UAAM,WAAW,MAAM,MAAM,UAAU,mBAAmB;AAAA,MACxD,SAAS;AAAA,QACP,eAAe,UAAU,KAAK;AAAA,MAChC;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,GAAI,QAAO;AAEzB,UAAM,OAAa,MAAM,SAAS,KAAK;AACvC,SAAK,QAAQ,IAAI,aAAa,MAAM,KAAK,UAAU,IAAI,CAAC;AACxD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,kBAA2B;AACzB,UAAM,cAAc,KAAK,QAAQ,IAAI,aAAa,YAAY;AAC9D,QAAI,CAAC,YAAa,QAAO;AAEzB,WAAO,CAAC,eAAe,WAAW;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,kBAA2B;AACzB,WAAO,CAAC,CAAC,KAAK,QAAQ,IAAI,aAAa,aAAa;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKA,eAA0B;AACxB,UAAM,cAAc,KAAK,QAAQ,IAAI,aAAa,YAAY;AAC9D,UAAM,UAAU,KAAK,QAAQ,IAAI,aAAa,QAAQ;AACtD,UAAM,OAAO,KAAK,QAAQ;AAE1B,WAAO;AAAA,MACL,iBAAiB,KAAK,gBAAgB;AAAA,MACtC,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,UAAkD;AAC1D,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM,KAAK,UAAU,OAAO,QAAQ;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY,QAA6B;AAC/C,SAAK,QAAQ,IAAI,aAAa,cAAc,OAAO,YAAY;AAE/D,QAAI,OAAO,eAAe;AACxB,WAAK,QAAQ,IAAI,aAAa,eAAe,OAAO,aAAa;AAAA,IACnE;AAEA,QAAI,OAAO,UAAU;AACnB,WAAK,QAAQ,IAAI,aAAa,UAAU,OAAO,QAAQ;AAAA,IACzD;AAGA,UAAM,YAAY,KAAK,IAAI,IAAK,OAAO,aAAa;AACpD,SAAK,QAAQ,IAAI,aAAa,YAAY,UAAU,SAAS,CAAC;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAoB;AAC1B,QAAI,KAAK,cAAc;AACrB,mBAAa,KAAK,YAAY;AAC9B,WAAK,eAAe;AAAA,IACtB;AAEA,SAAK,QAAQ,MAAM;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAyB;AAC/B,QAAI,KAAK,cAAc;AACrB,mBAAa,KAAK,YAAY;AAAA,IAChC;AAEA,UAAM,YAAY,KAAK,QAAQ,IAAI,aAAa,UAAU;AAC1D,QAAI,CAAC,UAAW;AAEhB,UAAM,cAAc,SAAS,WAAW,EAAE;AAC1C,UAAM,YAAY,cAAe,KAAK,OAAO,mBAAmB;AAChE,UAAM,QAAQ,YAAY,KAAK,IAAI;AAEnC,QAAI,QAAQ,GAAG;AACb,WAAK,eAAe,WAAW,YAAY;AACzC,cAAM,KAAK,aAAa;AACxB,aAAK,iBAAiB;AAAA,MACxB,GAAG,KAAK;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAwB;AAC9B,UAAM,QAAQ,KAAK,aAAa;AAChC,SAAK,UAAU,QAAQ,cAAY,SAAS,KAAK,CAAC;AAAA,EACpD;AACF;AAKO,SAAS,uBAAuB,QAA4C;AACjF,SAAO,IAAI,iBAAiB,MAAM;AACpC;","names":[]}
package/dist/index.mjs CHANGED
@@ -170,6 +170,8 @@ var StuffleIAMClient = class {
170
170
  this.discovery = null;
171
171
  this.refreshTimer = null;
172
172
  this.listeners = /* @__PURE__ */ new Set();
173
+ this._initialized = false;
174
+ this._initPromise = null;
173
175
  this.config = {
174
176
  issuer: config.issuer.replace(/\/$/, ""),
175
177
  // Remove trailing slash
@@ -184,6 +186,56 @@ var StuffleIAMClient = class {
184
186
  };
185
187
  this.storage = getStorage(this.config.storage);
186
188
  }
189
+ /**
190
+ * Initialize / restore session from storage.
191
+ * Call this on app startup (page load) to silently restore a session.
192
+ *
193
+ * - If access token is valid → re-setup auto-refresh timer, return true
194
+ * - If access token is expired but refresh token exists → silent refresh, return true/false
195
+ * - If no tokens → return false (not authenticated)
196
+ *
197
+ * Safe to call multiple times — subsequent calls return the same promise.
198
+ */
199
+ async init() {
200
+ if (this._initialized) return this.isAuthenticated();
201
+ if (this._initPromise) return this._initPromise;
202
+ this._initPromise = this._doInit();
203
+ return this._initPromise;
204
+ }
205
+ async _doInit() {
206
+ try {
207
+ const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);
208
+ const refreshTokenValue = this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);
209
+ if (!accessToken && !refreshTokenValue) {
210
+ this._initialized = true;
211
+ return false;
212
+ }
213
+ if (accessToken && !isTokenExpired(accessToken)) {
214
+ if (this.config.autoRefresh && refreshTokenValue) {
215
+ this.setupAutoRefresh();
216
+ }
217
+ this._initialized = true;
218
+ this.notifyListeners();
219
+ return true;
220
+ }
221
+ if (refreshTokenValue) {
222
+ const tokens = await this.refreshToken();
223
+ if (tokens) {
224
+ if (this.config.autoRefresh) {
225
+ this.setupAutoRefresh();
226
+ }
227
+ this._initialized = true;
228
+ return true;
229
+ }
230
+ }
231
+ this._initialized = true;
232
+ return false;
233
+ } catch (err) {
234
+ console.error("[StuffleIAMClient] init() failed:", err);
235
+ this._initialized = true;
236
+ return false;
237
+ }
238
+ }
187
239
  /**
188
240
  * Fetch OIDC discovery document
189
241
  */
@@ -414,13 +466,25 @@ var StuffleIAMClient = class {
414
466
  return user;
415
467
  }
416
468
  /**
417
- * Check if user is authenticated
469
+ * Check if user is authenticated (synchronous).
470
+ * Returns true if access token exists and is not expired.
471
+ *
472
+ * NOTE: If the access token is expired but a refresh token exists,
473
+ * this returns false. Call init() or getAccessToken() first to
474
+ * attempt a silent refresh.
418
475
  */
419
476
  isAuthenticated() {
420
477
  const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);
421
478
  if (!accessToken) return false;
422
479
  return !isTokenExpired(accessToken);
423
480
  }
481
+ /**
482
+ * Check if a refresh token exists in storage.
483
+ * Useful for determining if a silent refresh is possible.
484
+ */
485
+ hasRefreshToken() {
486
+ return !!this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);
487
+ }
424
488
  /**
425
489
  * Get current auth state
426
490
  */
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["/**\n * Utility functions for PKCE and crypto operations\n */\n\n/**\n * Generate a random string for state/nonce\n */\nexport function generateRandomString(length: number = 32): string {\n const array = new Uint8Array(length);\n crypto.getRandomValues(array);\n return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Generate PKCE code verifier (43-128 characters)\n */\nexport function generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\n/**\n * Generate PKCE code challenge from verifier\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', data);\n return base64UrlEncode(new Uint8Array(digest));\n}\n\n/**\n * Base64 URL encode (no padding, URL-safe characters)\n */\nexport function base64UrlEncode(buffer: Uint8Array): string {\n let binary = '';\n for (let i = 0; i < buffer.length; i++) {\n binary += String.fromCharCode(buffer[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\n/**\n * Decode JWT payload (without verification)\n */\nexport function decodeJwtPayload<T = Record<string, unknown>>(token: string): T | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n \n const payload = parts[1];\n const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));\n return JSON.parse(decoded) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Check if token is expired\n */\nexport function isTokenExpired(token: string, thresholdSeconds: number = 0): boolean {\n const payload = decodeJwtPayload<{ exp?: number }>(token);\n if (!payload?.exp) return true;\n \n const now = Math.floor(Date.now() / 1000);\n return payload.exp - thresholdSeconds <= now;\n}\n\n/**\n * Parse URL hash or query parameters\n */\nexport function parseUrlParams(url: string): Record<string, string> {\n const params: Record<string, string> = {};\n \n // Check hash first (implicit flow), then query (code flow)\n const hashIndex = url.indexOf('#');\n const queryIndex = url.indexOf('?');\n \n let paramString = '';\n if (hashIndex !== -1) {\n paramString = url.substring(hashIndex + 1);\n } else if (queryIndex !== -1) {\n paramString = url.substring(queryIndex + 1);\n }\n \n if (!paramString) return params;\n \n const searchParams = new URLSearchParams(paramString);\n searchParams.forEach((value, key) => {\n params[key] = value;\n });\n \n return params;\n}\n\n/**\n * Build URL with query parameters\n */\nexport function buildUrl(base: string, params: Record<string, string | undefined>): string {\n const url = new URL(base);\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.append(key, value);\n }\n });\n return url.toString();\n}\n","/**\n * Token storage abstraction\n */\n\nexport interface TokenStorage {\n get(key: string): string | null;\n set(key: string, value: string): void;\n remove(key: string): void;\n clear(): void;\n}\n\nconst STORAGE_PREFIX = 'stuffle_iam_';\n\n/**\n * LocalStorage implementation\n */\nexport class LocalStorageAdapter implements TokenStorage {\n get(key: string): string | null {\n try {\n return localStorage.getItem(STORAGE_PREFIX + key);\n } catch {\n return null;\n }\n }\n\n set(key: string, value: string): void {\n try {\n localStorage.setItem(STORAGE_PREFIX + key, value);\n } catch {\n console.warn('LocalStorage not available');\n }\n }\n\n remove(key: string): void {\n try {\n localStorage.removeItem(STORAGE_PREFIX + key);\n } catch {\n // Ignore\n }\n }\n\n clear(): void {\n try {\n Object.keys(localStorage)\n .filter(k => k.startsWith(STORAGE_PREFIX))\n .forEach(k => localStorage.removeItem(k));\n } catch {\n // Ignore\n }\n }\n}\n\n/**\n * SessionStorage implementation\n */\nexport class SessionStorageAdapter implements TokenStorage {\n get(key: string): string | null {\n try {\n return sessionStorage.getItem(STORAGE_PREFIX + key);\n } catch {\n return null;\n }\n }\n\n set(key: string, value: string): void {\n try {\n sessionStorage.setItem(STORAGE_PREFIX + key, value);\n } catch {\n console.warn('SessionStorage not available');\n }\n }\n\n remove(key: string): void {\n try {\n sessionStorage.removeItem(STORAGE_PREFIX + key);\n } catch {\n // Ignore\n }\n }\n\n clear(): void {\n try {\n Object.keys(sessionStorage)\n .filter(k => k.startsWith(STORAGE_PREFIX))\n .forEach(k => sessionStorage.removeItem(k));\n } catch {\n // Ignore\n }\n }\n}\n\n/**\n * In-memory storage (for SSR or when storage is unavailable)\n */\nexport class MemoryStorageAdapter implements TokenStorage {\n private store = new Map<string, string>();\n\n get(key: string): string | null {\n return this.store.get(key) ?? null;\n }\n\n set(key: string, value: string): void {\n this.store.set(key, value);\n }\n\n remove(key: string): void {\n this.store.delete(key);\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n\n/**\n * Get storage adapter based on type\n */\nexport function getStorage(type: 'localStorage' | 'sessionStorage' | 'memory'): TokenStorage {\n switch (type) {\n case 'localStorage':\n return new LocalStorageAdapter();\n case 'sessionStorage':\n return new SessionStorageAdapter();\n case 'memory':\n return new MemoryStorageAdapter();\n default:\n return new SessionStorageAdapter();\n }\n}\n","/**\n * Stuffle IAM Client\n * \n * OIDC/OAuth2 client for browser-based applications.\n * Supports authorization code flow with PKCE.\n */\n\nimport type {\n StuffleIAMConfig,\n TokenResponse,\n User,\n AuthState,\n LoginOptions,\n LogoutOptions,\n CallbackResult,\n OIDCDiscovery,\n} from './types';\nimport {\n generateRandomString,\n generateCodeVerifier,\n generateCodeChallenge,\n decodeJwtPayload,\n isTokenExpired,\n parseUrlParams,\n buildUrl,\n} from './utils';\nimport { getStorage, type TokenStorage } from './storage';\n\nconst STORAGE_KEYS = {\n ACCESS_TOKEN: 'access_token',\n REFRESH_TOKEN: 'refresh_token',\n ID_TOKEN: 'id_token',\n CODE_VERIFIER: 'code_verifier',\n STATE: 'state',\n NONCE: 'nonce',\n USER: 'user',\n EXPIRES_AT: 'expires_at',\n};\n\nexport class StuffleIAMClient {\n private config: Required<StuffleIAMConfig>;\n private storage: TokenStorage;\n private discovery: OIDCDiscovery | null = null;\n private refreshTimer: ReturnType<typeof setTimeout> | null = null;\n private listeners: Set<(state: AuthState) => void> = new Set();\n\n constructor(config: StuffleIAMConfig) {\n this.config = {\n issuer: config.issuer.replace(/\\/$/, ''), // Remove trailing slash\n clientId: config.clientId,\n redirectUri: config.redirectUri,\n scopes: config.scopes ?? ['openid', 'profile', 'email'],\n postLogoutRedirectUri: config.postLogoutRedirectUri ?? config.redirectUri,\n usePkce: config.usePkce ?? true,\n storage: config.storage ?? 'sessionStorage',\n autoRefresh: config.autoRefresh ?? true,\n refreshThreshold: config.refreshThreshold ?? 60,\n };\n\n this.storage = getStorage(this.config.storage);\n }\n\n /**\n * Fetch OIDC discovery document\n */\n async getDiscovery(): Promise<OIDCDiscovery> {\n if (this.discovery) return this.discovery;\n\n const response = await fetch(\n `${this.config.issuer}/.well-known/openid-configuration`\n );\n\n if (!response.ok) {\n throw new Error(`Failed to fetch OIDC discovery: ${response.status}`);\n }\n\n this.discovery = await response.json();\n return this.discovery!;\n }\n\n /**\n * Start login flow - redirects to authorization endpoint\n */\n async login(options: LoginOptions = {}): Promise<void> {\n const discovery = await this.getDiscovery();\n\n const state = options.state ?? generateRandomString();\n const nonce = options.nonce ?? generateRandomString();\n const scopes = [...this.config.scopes, ...(options.scopes ?? [])];\n\n // Store state and nonce for callback validation\n this.storage.set(STORAGE_KEYS.STATE, state);\n this.storage.set(STORAGE_KEYS.NONCE, nonce);\n\n const params: Record<string, string | undefined> = {\n client_id: this.config.clientId,\n redirect_uri: this.config.redirectUri,\n response_type: 'code',\n scope: scopes.join(' '),\n state,\n nonce,\n prompt: options.prompt,\n login_hint: options.loginHint,\n };\n\n // Add PKCE if enabled\n if (this.config.usePkce) {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n \n this.storage.set(STORAGE_KEYS.CODE_VERIFIER, codeVerifier);\n params.code_challenge = codeChallenge;\n params.code_challenge_method = 'S256';\n }\n\n // Use signup endpoint if requested\n const endpoint = options.signup\n ? discovery.authorization_endpoint.replace('/authorize', '/authorize/signup')\n : discovery.authorization_endpoint;\n\n const authUrl = buildUrl(endpoint, params);\n window.location.href = authUrl;\n }\n\n /**\n * Alias for login({ signup: true })\n */\n async signup(options: Omit<LoginOptions, 'signup'> = {}): Promise<void> {\n return this.login({ ...options, signup: true });\n }\n\n /**\n * Handle callback from authorization server\n */\n async handleCallback(url?: string): Promise<CallbackResult> {\n const callbackUrl = url ?? window.location.href;\n const params = parseUrlParams(callbackUrl);\n\n // Check for errors\n if (params.error) {\n return {\n success: false,\n error: params.error,\n errorDescription: params.error_description,\n };\n }\n\n // Validate state\n const storedState = this.storage.get(STORAGE_KEYS.STATE);\n if (!storedState || storedState !== params.state) {\n return {\n success: false,\n error: 'invalid_state',\n errorDescription: 'State mismatch - possible CSRF attack',\n };\n }\n\n // Exchange code for tokens\n if (!params.code) {\n return {\n success: false,\n error: 'missing_code',\n errorDescription: 'No authorization code received',\n };\n }\n\n try {\n const tokens = await this.exchangeCode(params.code);\n \n // Validate nonce in ID token\n if (tokens.id_token) {\n const payload = decodeJwtPayload<{ nonce?: string }>(tokens.id_token);\n const storedNonce = this.storage.get(STORAGE_KEYS.NONCE);\n if (payload?.nonce !== storedNonce) {\n return {\n success: false,\n error: 'invalid_nonce',\n errorDescription: 'Nonce mismatch - possible replay attack',\n };\n }\n }\n\n // Store tokens\n this.storeTokens(tokens);\n\n // Clear temporary storage\n this.storage.remove(STORAGE_KEYS.STATE);\n this.storage.remove(STORAGE_KEYS.NONCE);\n this.storage.remove(STORAGE_KEYS.CODE_VERIFIER);\n\n // Get user info\n const user = await this.fetchUserInfo(tokens.access_token);\n\n // Setup auto-refresh\n if (this.config.autoRefresh && tokens.refresh_token) {\n this.setupAutoRefresh();\n }\n\n // Notify listeners\n this.notifyListeners();\n\n return {\n success: true,\n user: user || undefined,\n accessToken: tokens.access_token,\n idToken: tokens.id_token,\n refreshToken: tokens.refresh_token,\n };\n } catch (error) {\n return {\n success: false,\n error: 'token_exchange_failed',\n errorDescription: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n }\n\n /**\n * Exchange authorization code for tokens\n */\n private async exchangeCode(code: string): Promise<TokenResponse> {\n const discovery = await this.getDiscovery();\n\n const body: Record<string, string> = {\n grant_type: 'authorization_code',\n client_id: this.config.clientId,\n code,\n redirect_uri: this.config.redirectUri,\n };\n\n // Add PKCE code verifier\n if (this.config.usePkce) {\n const codeVerifier = this.storage.get(STORAGE_KEYS.CODE_VERIFIER);\n if (codeVerifier) {\n body.code_verifier = codeVerifier;\n }\n }\n\n const response = await fetch(discovery.token_endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams(body),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(error.error_description || error.error || 'Token exchange failed');\n }\n\n return response.json();\n }\n\n /**\n * Refresh access token using refresh token\n */\n async refreshToken(): Promise<TokenResponse | null> {\n const refreshToken = this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);\n if (!refreshToken) return null;\n\n const discovery = await this.getDiscovery();\n\n const response = await fetch(discovery.token_endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: this.config.clientId,\n refresh_token: refreshToken,\n }),\n });\n\n if (!response.ok) {\n // Refresh failed - clear tokens\n this.clearTokens();\n this.notifyListeners();\n return null;\n }\n\n const tokens: TokenResponse = await response.json();\n this.storeTokens(tokens);\n this.notifyListeners();\n\n return tokens;\n }\n\n /**\n * Logout - end session\n */\n async logout(options: LogoutOptions = {}): Promise<void> {\n const discovery = await this.getDiscovery();\n const idToken = this.storage.get(STORAGE_KEYS.ID_TOKEN);\n\n // Clear local tokens first\n this.clearTokens();\n this.notifyListeners();\n\n // Redirect to end session endpoint if available\n if (discovery.end_session_endpoint) {\n const params: Record<string, string | undefined> = {\n post_logout_redirect_uri: options.returnTo ?? this.config.postLogoutRedirectUri,\n id_token_hint: options.idTokenHint ?? idToken ?? undefined,\n client_id: this.config.clientId,\n };\n\n const logoutUrl = buildUrl(discovery.end_session_endpoint, params);\n window.location.href = logoutUrl;\n }\n }\n\n /**\n * Get current access token (refreshes if needed)\n */\n async getAccessToken(): Promise<string | null> {\n const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);\n \n if (!accessToken) return null;\n\n // Check if token needs refresh\n if (isTokenExpired(accessToken, this.config.refreshThreshold)) {\n const tokens = await this.refreshToken();\n return tokens?.access_token ?? null;\n }\n\n return accessToken;\n }\n\n /**\n * Get current user from stored ID token\n */\n getUser(): User | null {\n const idToken = this.storage.get(STORAGE_KEYS.ID_TOKEN);\n if (!idToken) return null;\n\n return decodeJwtPayload<User>(idToken);\n }\n\n /**\n * Fetch user info from userinfo endpoint\n */\n async fetchUserInfo(accessToken?: string): Promise<User | null> {\n const token = accessToken ?? await this.getAccessToken();\n if (!token) return null;\n\n const discovery = await this.getDiscovery();\n\n const response = await fetch(discovery.userinfo_endpoint, {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!response.ok) return null;\n\n const user: User = await response.json();\n this.storage.set(STORAGE_KEYS.USER, JSON.stringify(user));\n return user;\n }\n\n /**\n * Check if user is authenticated\n */\n isAuthenticated(): boolean {\n const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);\n if (!accessToken) return false;\n\n // Consider authenticated if token exists and not expired\n return !isTokenExpired(accessToken);\n }\n\n /**\n * Get current auth state\n */\n getAuthState(): AuthState {\n const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);\n const idToken = this.storage.get(STORAGE_KEYS.ID_TOKEN);\n const user = this.getUser();\n\n return {\n isAuthenticated: this.isAuthenticated(),\n isLoading: false,\n user,\n accessToken,\n idToken,\n error: null,\n };\n }\n\n /**\n * Subscribe to auth state changes\n */\n subscribe(listener: (state: AuthState) => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Store tokens in storage\n */\n private storeTokens(tokens: TokenResponse): void {\n this.storage.set(STORAGE_KEYS.ACCESS_TOKEN, tokens.access_token);\n \n if (tokens.refresh_token) {\n this.storage.set(STORAGE_KEYS.REFRESH_TOKEN, tokens.refresh_token);\n }\n \n if (tokens.id_token) {\n this.storage.set(STORAGE_KEYS.ID_TOKEN, tokens.id_token);\n }\n\n // Store expiry time\n const expiresAt = Date.now() + (tokens.expires_in * 1000);\n this.storage.set(STORAGE_KEYS.EXPIRES_AT, expiresAt.toString());\n }\n\n /**\n * Clear all stored tokens\n */\n private clearTokens(): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n\n this.storage.clear();\n }\n\n /**\n * Setup auto-refresh timer\n */\n private setupAutoRefresh(): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n }\n\n const expiresAt = this.storage.get(STORAGE_KEYS.EXPIRES_AT);\n if (!expiresAt) return;\n\n const expiresAtMs = parseInt(expiresAt, 10);\n const refreshAt = expiresAtMs - (this.config.refreshThreshold * 1000);\n const delay = refreshAt - Date.now();\n\n if (delay > 0) {\n this.refreshTimer = setTimeout(async () => {\n await this.refreshToken();\n this.setupAutoRefresh();\n }, delay);\n }\n }\n\n /**\n * Notify all listeners of state change\n */\n private notifyListeners(): void {\n const state = this.getAuthState();\n this.listeners.forEach(listener => listener(state));\n }\n}\n\n/**\n * Create a new Stuffle IAM client instance\n */\nexport function createStuffleIAMClient(config: StuffleIAMConfig): StuffleIAMClient {\n return new StuffleIAMClient(config);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/utils.ts","../src/storage.ts","../src/client.ts"],"sourcesContent":["/**\n * Utility functions for PKCE and crypto operations\n */\n\n/**\n * Generate a random string for state/nonce\n */\nexport function generateRandomString(length: number = 32): string {\n const array = new Uint8Array(length);\n crypto.getRandomValues(array);\n return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Generate PKCE code verifier (43-128 characters)\n */\nexport function generateCodeVerifier(): string {\n const array = new Uint8Array(32);\n crypto.getRandomValues(array);\n return base64UrlEncode(array);\n}\n\n/**\n * Generate PKCE code challenge from verifier\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', data);\n return base64UrlEncode(new Uint8Array(digest));\n}\n\n/**\n * Base64 URL encode (no padding, URL-safe characters)\n */\nexport function base64UrlEncode(buffer: Uint8Array): string {\n let binary = '';\n for (let i = 0; i < buffer.length; i++) {\n binary += String.fromCharCode(buffer[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\n/**\n * Decode JWT payload (without verification)\n */\nexport function decodeJwtPayload<T = Record<string, unknown>>(token: string): T | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n \n const payload = parts[1];\n const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));\n return JSON.parse(decoded) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Check if token is expired\n */\nexport function isTokenExpired(token: string, thresholdSeconds: number = 0): boolean {\n const payload = decodeJwtPayload<{ exp?: number }>(token);\n if (!payload?.exp) return true;\n \n const now = Math.floor(Date.now() / 1000);\n return payload.exp - thresholdSeconds <= now;\n}\n\n/**\n * Parse URL hash or query parameters\n */\nexport function parseUrlParams(url: string): Record<string, string> {\n const params: Record<string, string> = {};\n \n // Check hash first (implicit flow), then query (code flow)\n const hashIndex = url.indexOf('#');\n const queryIndex = url.indexOf('?');\n \n let paramString = '';\n if (hashIndex !== -1) {\n paramString = url.substring(hashIndex + 1);\n } else if (queryIndex !== -1) {\n paramString = url.substring(queryIndex + 1);\n }\n \n if (!paramString) return params;\n \n const searchParams = new URLSearchParams(paramString);\n searchParams.forEach((value, key) => {\n params[key] = value;\n });\n \n return params;\n}\n\n/**\n * Build URL with query parameters\n */\nexport function buildUrl(base: string, params: Record<string, string | undefined>): string {\n const url = new URL(base);\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n url.searchParams.append(key, value);\n }\n });\n return url.toString();\n}\n","/**\n * Token storage abstraction\n */\n\nexport interface TokenStorage {\n get(key: string): string | null;\n set(key: string, value: string): void;\n remove(key: string): void;\n clear(): void;\n}\n\nconst STORAGE_PREFIX = 'stuffle_iam_';\n\n/**\n * LocalStorage implementation\n */\nexport class LocalStorageAdapter implements TokenStorage {\n get(key: string): string | null {\n try {\n return localStorage.getItem(STORAGE_PREFIX + key);\n } catch {\n return null;\n }\n }\n\n set(key: string, value: string): void {\n try {\n localStorage.setItem(STORAGE_PREFIX + key, value);\n } catch {\n console.warn('LocalStorage not available');\n }\n }\n\n remove(key: string): void {\n try {\n localStorage.removeItem(STORAGE_PREFIX + key);\n } catch {\n // Ignore\n }\n }\n\n clear(): void {\n try {\n Object.keys(localStorage)\n .filter(k => k.startsWith(STORAGE_PREFIX))\n .forEach(k => localStorage.removeItem(k));\n } catch {\n // Ignore\n }\n }\n}\n\n/**\n * SessionStorage implementation\n */\nexport class SessionStorageAdapter implements TokenStorage {\n get(key: string): string | null {\n try {\n return sessionStorage.getItem(STORAGE_PREFIX + key);\n } catch {\n return null;\n }\n }\n\n set(key: string, value: string): void {\n try {\n sessionStorage.setItem(STORAGE_PREFIX + key, value);\n } catch {\n console.warn('SessionStorage not available');\n }\n }\n\n remove(key: string): void {\n try {\n sessionStorage.removeItem(STORAGE_PREFIX + key);\n } catch {\n // Ignore\n }\n }\n\n clear(): void {\n try {\n Object.keys(sessionStorage)\n .filter(k => k.startsWith(STORAGE_PREFIX))\n .forEach(k => sessionStorage.removeItem(k));\n } catch {\n // Ignore\n }\n }\n}\n\n/**\n * In-memory storage (for SSR or when storage is unavailable)\n */\nexport class MemoryStorageAdapter implements TokenStorage {\n private store = new Map<string, string>();\n\n get(key: string): string | null {\n return this.store.get(key) ?? null;\n }\n\n set(key: string, value: string): void {\n this.store.set(key, value);\n }\n\n remove(key: string): void {\n this.store.delete(key);\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n\n/**\n * Get storage adapter based on type\n */\nexport function getStorage(type: 'localStorage' | 'sessionStorage' | 'memory'): TokenStorage {\n switch (type) {\n case 'localStorage':\n return new LocalStorageAdapter();\n case 'sessionStorage':\n return new SessionStorageAdapter();\n case 'memory':\n return new MemoryStorageAdapter();\n default:\n return new SessionStorageAdapter();\n }\n}\n","/**\n * Stuffle IAM Client\n * \n * OIDC/OAuth2 client for browser-based applications.\n * Supports authorization code flow with PKCE.\n */\n\nimport type {\n StuffleIAMConfig,\n TokenResponse,\n User,\n AuthState,\n LoginOptions,\n LogoutOptions,\n CallbackResult,\n OIDCDiscovery,\n} from './types';\nimport {\n generateRandomString,\n generateCodeVerifier,\n generateCodeChallenge,\n decodeJwtPayload,\n isTokenExpired,\n parseUrlParams,\n buildUrl,\n} from './utils';\nimport { getStorage, type TokenStorage } from './storage';\n\nconst STORAGE_KEYS = {\n ACCESS_TOKEN: 'access_token',\n REFRESH_TOKEN: 'refresh_token',\n ID_TOKEN: 'id_token',\n CODE_VERIFIER: 'code_verifier',\n STATE: 'state',\n NONCE: 'nonce',\n USER: 'user',\n EXPIRES_AT: 'expires_at',\n};\n\nexport class StuffleIAMClient {\n private config: Required<StuffleIAMConfig>;\n private storage: TokenStorage;\n private discovery: OIDCDiscovery | null = null;\n private refreshTimer: ReturnType<typeof setTimeout> | null = null;\n private listeners: Set<(state: AuthState) => void> = new Set();\n private _initialized = false;\n private _initPromise: Promise<boolean> | null = null;\n\n constructor(config: StuffleIAMConfig) {\n this.config = {\n issuer: config.issuer.replace(/\\/$/, ''), // Remove trailing slash\n clientId: config.clientId,\n redirectUri: config.redirectUri,\n scopes: config.scopes ?? ['openid', 'profile', 'email'],\n postLogoutRedirectUri: config.postLogoutRedirectUri ?? config.redirectUri,\n usePkce: config.usePkce ?? true,\n storage: config.storage ?? 'sessionStorage',\n autoRefresh: config.autoRefresh ?? true,\n refreshThreshold: config.refreshThreshold ?? 60,\n };\n\n this.storage = getStorage(this.config.storage);\n }\n\n /**\n * Initialize / restore session from storage.\n * Call this on app startup (page load) to silently restore a session.\n *\n * - If access token is valid → re-setup auto-refresh timer, return true\n * - If access token is expired but refresh token exists → silent refresh, return true/false\n * - If no tokens → return false (not authenticated)\n *\n * Safe to call multiple times — subsequent calls return the same promise.\n */\n async init(): Promise<boolean> {\n if (this._initialized) return this.isAuthenticated();\n if (this._initPromise) return this._initPromise;\n\n this._initPromise = this._doInit();\n return this._initPromise;\n }\n\n private async _doInit(): Promise<boolean> {\n try {\n const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);\n const refreshTokenValue = this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);\n\n if (!accessToken && !refreshTokenValue) {\n // No session to restore\n this._initialized = true;\n return false;\n }\n\n if (accessToken && !isTokenExpired(accessToken)) {\n // Access token still valid — just re-setup auto-refresh\n if (this.config.autoRefresh && refreshTokenValue) {\n this.setupAutoRefresh();\n }\n this._initialized = true;\n this.notifyListeners();\n return true;\n }\n\n // Access token expired (or missing) but refresh token exists — silent refresh\n if (refreshTokenValue) {\n const tokens = await this.refreshToken();\n if (tokens) {\n if (this.config.autoRefresh) {\n this.setupAutoRefresh();\n }\n this._initialized = true;\n return true;\n }\n }\n\n // Refresh failed or no refresh token\n this._initialized = true;\n return false;\n } catch (err) {\n console.error('[StuffleIAMClient] init() failed:', err);\n this._initialized = true;\n return false;\n }\n }\n\n /**\n * Fetch OIDC discovery document\n */\n async getDiscovery(): Promise<OIDCDiscovery> {\n if (this.discovery) return this.discovery;\n\n const response = await fetch(\n `${this.config.issuer}/.well-known/openid-configuration`\n );\n\n if (!response.ok) {\n throw new Error(`Failed to fetch OIDC discovery: ${response.status}`);\n }\n\n this.discovery = await response.json();\n return this.discovery!;\n }\n\n /**\n * Start login flow - redirects to authorization endpoint\n */\n async login(options: LoginOptions = {}): Promise<void> {\n const discovery = await this.getDiscovery();\n\n const state = options.state ?? generateRandomString();\n const nonce = options.nonce ?? generateRandomString();\n const scopes = [...this.config.scopes, ...(options.scopes ?? [])];\n\n // Store state and nonce for callback validation\n this.storage.set(STORAGE_KEYS.STATE, state);\n this.storage.set(STORAGE_KEYS.NONCE, nonce);\n\n const params: Record<string, string | undefined> = {\n client_id: this.config.clientId,\n redirect_uri: this.config.redirectUri,\n response_type: 'code',\n scope: scopes.join(' '),\n state,\n nonce,\n prompt: options.prompt,\n login_hint: options.loginHint,\n };\n\n // Add PKCE if enabled\n if (this.config.usePkce) {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = await generateCodeChallenge(codeVerifier);\n \n this.storage.set(STORAGE_KEYS.CODE_VERIFIER, codeVerifier);\n params.code_challenge = codeChallenge;\n params.code_challenge_method = 'S256';\n }\n\n // Use signup endpoint if requested\n const endpoint = options.signup\n ? discovery.authorization_endpoint.replace('/authorize', '/authorize/signup')\n : discovery.authorization_endpoint;\n\n const authUrl = buildUrl(endpoint, params);\n window.location.href = authUrl;\n }\n\n /**\n * Alias for login({ signup: true })\n */\n async signup(options: Omit<LoginOptions, 'signup'> = {}): Promise<void> {\n return this.login({ ...options, signup: true });\n }\n\n /**\n * Handle callback from authorization server\n */\n async handleCallback(url?: string): Promise<CallbackResult> {\n const callbackUrl = url ?? window.location.href;\n const params = parseUrlParams(callbackUrl);\n\n // Check for errors\n if (params.error) {\n return {\n success: false,\n error: params.error,\n errorDescription: params.error_description,\n };\n }\n\n // Validate state\n const storedState = this.storage.get(STORAGE_KEYS.STATE);\n if (!storedState || storedState !== params.state) {\n return {\n success: false,\n error: 'invalid_state',\n errorDescription: 'State mismatch - possible CSRF attack',\n };\n }\n\n // Exchange code for tokens\n if (!params.code) {\n return {\n success: false,\n error: 'missing_code',\n errorDescription: 'No authorization code received',\n };\n }\n\n try {\n const tokens = await this.exchangeCode(params.code);\n \n // Validate nonce in ID token\n if (tokens.id_token) {\n const payload = decodeJwtPayload<{ nonce?: string }>(tokens.id_token);\n const storedNonce = this.storage.get(STORAGE_KEYS.NONCE);\n if (payload?.nonce !== storedNonce) {\n return {\n success: false,\n error: 'invalid_nonce',\n errorDescription: 'Nonce mismatch - possible replay attack',\n };\n }\n }\n\n // Store tokens\n this.storeTokens(tokens);\n\n // Clear temporary storage\n this.storage.remove(STORAGE_KEYS.STATE);\n this.storage.remove(STORAGE_KEYS.NONCE);\n this.storage.remove(STORAGE_KEYS.CODE_VERIFIER);\n\n // Get user info\n const user = await this.fetchUserInfo(tokens.access_token);\n\n // Setup auto-refresh\n if (this.config.autoRefresh && tokens.refresh_token) {\n this.setupAutoRefresh();\n }\n\n // Notify listeners\n this.notifyListeners();\n\n return {\n success: true,\n user: user || undefined,\n accessToken: tokens.access_token,\n idToken: tokens.id_token,\n refreshToken: tokens.refresh_token,\n };\n } catch (error) {\n return {\n success: false,\n error: 'token_exchange_failed',\n errorDescription: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n }\n\n /**\n * Exchange authorization code for tokens\n */\n private async exchangeCode(code: string): Promise<TokenResponse> {\n const discovery = await this.getDiscovery();\n\n const body: Record<string, string> = {\n grant_type: 'authorization_code',\n client_id: this.config.clientId,\n code,\n redirect_uri: this.config.redirectUri,\n };\n\n // Add PKCE code verifier\n if (this.config.usePkce) {\n const codeVerifier = this.storage.get(STORAGE_KEYS.CODE_VERIFIER);\n if (codeVerifier) {\n body.code_verifier = codeVerifier;\n }\n }\n\n const response = await fetch(discovery.token_endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams(body),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(error.error_description || error.error || 'Token exchange failed');\n }\n\n return response.json();\n }\n\n /**\n * Refresh access token using refresh token\n */\n async refreshToken(): Promise<TokenResponse | null> {\n const refreshToken = this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);\n if (!refreshToken) return null;\n\n const discovery = await this.getDiscovery();\n\n const response = await fetch(discovery.token_endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: this.config.clientId,\n refresh_token: refreshToken,\n }),\n });\n\n if (!response.ok) {\n // Refresh failed - clear tokens\n this.clearTokens();\n this.notifyListeners();\n return null;\n }\n\n const tokens: TokenResponse = await response.json();\n this.storeTokens(tokens);\n this.notifyListeners();\n\n return tokens;\n }\n\n /**\n * Logout - end session\n */\n async logout(options: LogoutOptions = {}): Promise<void> {\n const discovery = await this.getDiscovery();\n const idToken = this.storage.get(STORAGE_KEYS.ID_TOKEN);\n\n // Clear local tokens first\n this.clearTokens();\n this.notifyListeners();\n\n // Redirect to end session endpoint if available\n if (discovery.end_session_endpoint) {\n const params: Record<string, string | undefined> = {\n post_logout_redirect_uri: options.returnTo ?? this.config.postLogoutRedirectUri,\n id_token_hint: options.idTokenHint ?? idToken ?? undefined,\n client_id: this.config.clientId,\n };\n\n const logoutUrl = buildUrl(discovery.end_session_endpoint, params);\n window.location.href = logoutUrl;\n }\n }\n\n /**\n * Get current access token (refreshes if needed)\n */\n async getAccessToken(): Promise<string | null> {\n const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);\n \n if (!accessToken) return null;\n\n // Check if token needs refresh\n if (isTokenExpired(accessToken, this.config.refreshThreshold)) {\n const tokens = await this.refreshToken();\n return tokens?.access_token ?? null;\n }\n\n return accessToken;\n }\n\n /**\n * Get current user from stored ID token\n */\n getUser(): User | null {\n const idToken = this.storage.get(STORAGE_KEYS.ID_TOKEN);\n if (!idToken) return null;\n\n return decodeJwtPayload<User>(idToken);\n }\n\n /**\n * Fetch user info from userinfo endpoint\n */\n async fetchUserInfo(accessToken?: string): Promise<User | null> {\n const token = accessToken ?? await this.getAccessToken();\n if (!token) return null;\n\n const discovery = await this.getDiscovery();\n\n const response = await fetch(discovery.userinfo_endpoint, {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!response.ok) return null;\n\n const user: User = await response.json();\n this.storage.set(STORAGE_KEYS.USER, JSON.stringify(user));\n return user;\n }\n\n /**\n * Check if user is authenticated (synchronous).\n * Returns true if access token exists and is not expired.\n *\n * NOTE: If the access token is expired but a refresh token exists,\n * this returns false. Call init() or getAccessToken() first to\n * attempt a silent refresh.\n */\n isAuthenticated(): boolean {\n const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);\n if (!accessToken) return false;\n\n return !isTokenExpired(accessToken);\n }\n\n /**\n * Check if a refresh token exists in storage.\n * Useful for determining if a silent refresh is possible.\n */\n hasRefreshToken(): boolean {\n return !!this.storage.get(STORAGE_KEYS.REFRESH_TOKEN);\n }\n\n /**\n * Get current auth state\n */\n getAuthState(): AuthState {\n const accessToken = this.storage.get(STORAGE_KEYS.ACCESS_TOKEN);\n const idToken = this.storage.get(STORAGE_KEYS.ID_TOKEN);\n const user = this.getUser();\n\n return {\n isAuthenticated: this.isAuthenticated(),\n isLoading: false,\n user,\n accessToken,\n idToken,\n error: null,\n };\n }\n\n /**\n * Subscribe to auth state changes\n */\n subscribe(listener: (state: AuthState) => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /**\n * Store tokens in storage\n */\n private storeTokens(tokens: TokenResponse): void {\n this.storage.set(STORAGE_KEYS.ACCESS_TOKEN, tokens.access_token);\n \n if (tokens.refresh_token) {\n this.storage.set(STORAGE_KEYS.REFRESH_TOKEN, tokens.refresh_token);\n }\n \n if (tokens.id_token) {\n this.storage.set(STORAGE_KEYS.ID_TOKEN, tokens.id_token);\n }\n\n // Store expiry time\n const expiresAt = Date.now() + (tokens.expires_in * 1000);\n this.storage.set(STORAGE_KEYS.EXPIRES_AT, expiresAt.toString());\n }\n\n /**\n * Clear all stored tokens\n */\n private clearTokens(): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n\n this.storage.clear();\n }\n\n /**\n * Setup auto-refresh timer\n */\n private setupAutoRefresh(): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n }\n\n const expiresAt = this.storage.get(STORAGE_KEYS.EXPIRES_AT);\n if (!expiresAt) return;\n\n const expiresAtMs = parseInt(expiresAt, 10);\n const refreshAt = expiresAtMs - (this.config.refreshThreshold * 1000);\n const delay = refreshAt - Date.now();\n\n if (delay > 0) {\n this.refreshTimer = setTimeout(async () => {\n await this.refreshToken();\n this.setupAutoRefresh();\n }, delay);\n }\n }\n\n /**\n * Notify all listeners of state change\n */\n private notifyListeners(): void {\n const state = this.getAuthState();\n this.listeners.forEach(listener => listener(state));\n }\n}\n\n/**\n * Create a new Stuffle IAM client instance\n */\nexport function createStuffleIAMClient(config: StuffleIAMConfig): StuffleIAMClient {\n return new StuffleIAMClient(config);\n}\n"],"mappings":"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","names":[]}
package/dist/react.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { a as StuffleIAMConfig, C as CallbackResult, S as StuffleIAMClient, U as User, L as LoginOptions, b as LogoutOptions } from './client-CTKWBZ26.mjs';
4
- export { A as AuthState } from './client-CTKWBZ26.mjs';
3
+ import { a as StuffleIAMConfig, C as CallbackResult, S as StuffleIAMClient, U as User, L as LoginOptions, b as LogoutOptions } from './client-CrQnMix2.mjs';
4
+ export { A as AuthState } from './client-CrQnMix2.mjs';
5
5
 
6
6
  interface StuffleIAMContextValue {
7
7
  client: StuffleIAMClient;
package/dist/react.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { a as StuffleIAMConfig, C as CallbackResult, S as StuffleIAMClient, U as User, L as LoginOptions, b as LogoutOptions } from './client-CTKWBZ26.js';
4
- export { A as AuthState } from './client-CTKWBZ26.js';
3
+ import { a as StuffleIAMConfig, C as CallbackResult, S as StuffleIAMClient, U as User, L as LoginOptions, b as LogoutOptions } from './client-CrQnMix2.js';
4
+ export { A as AuthState } from './client-CrQnMix2.js';
5
5
 
6
6
  interface StuffleIAMContextValue {
7
7
  client: StuffleIAMClient;