UNPKG

sdk-simple-auth

Version:

Universal JavaScript/TypeScript authentication SDK with multi-backend support, automatic token refresh, and React integration

1 lines β€’ 65.9 kB
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).AuthSDK={},e.React)}(this,function(e,t){"use strict";class AxiosInterceptorManager{constructor(e,t){this.requestInterceptorId=null,this.responseInterceptorId=null,this.isRefreshing=!1,this.failedQueue=[],this.axiosInstance=e,this.getAccessToken=t.getAccessToken,this.onSessionInvalid=t.onSessionInvalid,this.onTokenRefresh=t.onTokenRefresh}setup(e={}){const{autoInjectToken:t=!0,handleAuthErrors:s=!0}=e;this.isAxiosInstance(this.axiosInstance)?(t&&(this.requestInterceptorId=this.axiosInstance.interceptors.request.use(async e=>{try{const t=await this.getAccessToken();t&&(e.headers.Authorization||(e.headers.Authorization=`Bearer ${t}`,console.debug("AxiosInterceptor: Token injected automatically")))}catch(e){console.error("AxiosInterceptor: Error getting access token:",e)}return e},e=>Promise.reject(e)),console.debug("AxiosInterceptor: Request interceptor configured")),s&&(this.responseInterceptorId=this.axiosInstance.interceptors.response.use(e=>e,async e=>{const t=e.config,s=e?.response?.status;if(401===s){if(!this.onTokenRefresh)return this.handleSessionInvalid(s),Promise.reject(e);if(this.isRefreshing)return console.debug("AxiosInterceptor: Refresh in progress, queuing request"),new Promise((e,t)=>{this.failedQueue.push({resolve:e,reject:t})}).then(e=>(t.headers.Authorization=`Bearer ${e}`,this.axiosInstance.request(t))).catch(e=>Promise.reject(e));this.isRefreshing=!0,console.debug("AxiosInterceptor: Authentication error (401), starting refresh...");try{await this.onTokenRefresh();const e=await this.getAccessToken();if(!e)throw new Error("No token available after refresh");return console.debug("AxiosInterceptor: Token refreshed successfully"),this.processQueue(null,e),t.headers.Authorization=`Bearer ${e}`,this.axiosInstance.request(t)}catch(e){return console.error("AxiosInterceptor: Token refresh failed:",e),this.processQueue(e,null),this.handleSessionInvalid(s),Promise.reject(e)}finally{this.isRefreshing=!1}}return 422!==s&&403!==s||console.warn(`AxiosInterceptor: Auth error (${s}), checking session...`),Promise.reject(e)}),console.debug("AxiosInterceptor: Response interceptor configured")),console.log("βœ… Axios interceptors configured successfully")):console.warn("AxiosInterceptorManager: Invalid Axios instance provided")}processQueue(e,t=null){console.debug(`AxiosInterceptor: Processing queue (${this.failedQueue.length} requests)`),this.failedQueue.forEach(s=>{e?s.reject(e):s.resolve(t)}),this.failedQueue=[]}handleSessionInvalid(e){console.warn(`AxiosInterceptor: Session invalid (HTTP ${e}), triggering logout`),this.onSessionInvalid()}remove(){this.axiosInstance?.interceptors&&(null!==this.requestInterceptorId&&(this.axiosInstance.interceptors.request.eject(this.requestInterceptorId),this.requestInterceptorId=null,console.debug("AxiosInterceptor: Request interceptor removed")),null!==this.responseInterceptorId&&(this.axiosInstance.interceptors.response.eject(this.responseInterceptorId),this.responseInterceptorId=null,console.debug("AxiosInterceptor: Response interceptor removed")),console.log("Axios interceptors removed"))}isAxiosInstance(e){return e&&e.interceptors&&"function"==typeof e.interceptors.request?.use&&"function"==typeof e.interceptors.response?.use&&"function"==typeof e.request}isActive(){return null!==this.requestInterceptorId||null!==this.responseInterceptorId}getStatus(){return{isActive:this.isActive(),hasRequestInterceptor:null!==this.requestInterceptorId,hasResponseInterceptor:null!==this.responseInterceptorId}}}class TokenHandler{static detectTokenType(e){if(!e||"string"!=typeof e)return"opaque";if(e.includes("|"))return"sanctum";const t=e.split(".");if(3===t.length)try{const e=t[1],s=e.padEnd(e.length+(4-e.length%4)%4,"=");return atob(s),"jwt"}catch{return"opaque"}return"opaque"}static parseToken(e){switch(this.detectTokenType(e)){case"jwt":return this.parseJWTToken(e);case"sanctum":return this.parseSanctumToken(e);default:return this.parseOpaqueToken(e)}}static parseJWTToken(e){try{const t=e.split(".")[1],s=t.padEnd(t.length+(4-t.length%4)%4,"="),r=atob(s),o=JSON.parse(r);return{type:"jwt",payload:o,exp:o.exp,isValid:!o.exp||o.exp>Math.floor(Date.now()/1e3)}}catch(e){return console.error("Error parsing JWT token:",e),{type:"jwt",isValid:!1}}}static parseSanctumToken(e){const t=e.split("|");return{type:"sanctum",tokenId:t[0],payload:{tokenId:t[0],hash:t[1]},isValid:!0}}static parseOpaqueToken(e){return{type:"opaque",isValid:!0}}}class ExpirationHandler{static normalizeExpiration(e){if(e){if("number"==typeof e)return e;if("string"==typeof e){if(e.includes("T")||e.includes("-")){const t=new Date(e);if(!isNaN(t.getTime())){const e=Date.now(),s=t.getTime(),r=Math.floor((s-e)/1e3);return Math.max(0,r)}}const t=parseInt(e);if(!isNaN(t)&&t>1e9){const e=Math.floor(Date.now()/1e3);return Math.max(0,t-e)}const s=parseInt(e);if(!isNaN(s))return s}console.warn("Could not parse expiration time:",e)}}static calculateExpiration(e,t,s,r){if(s)return this.normalizeExpiration(s);if(t)return this.normalizeExpiration(t);const o=TokenHandler.parseToken(e);if("jwt"===o.type&&o.exp){const e=Math.floor(Date.now()/1e3);return Math.max(0,o.exp-e)}if(r&&t){const e=Math.floor(Date.now()/1e3)-r;return Math.max(0,t-e)}}}class Logger{static setDebugMode(e){this.debugMode=e}static debug(e,...t){this.debugMode&&console.debug(`${this.prefix} [Debug] ${e}`,...t)}static log(e,...t){this.debugMode&&console.log(`${this.prefix} ${e}`,...t)}static warn(e,...t){console.warn(`${this.prefix} [Warn] ${e}`,...t)}static error(e,...t){console.error(`${this.prefix} [Error] ${e}`,...t)}}Logger.debugMode=!1,Logger.prefix="[AuthSDK]";class TokenExtractor$1{static deepSearch(e,t){if(!e||"object"!=typeof e)return null;for(const s of t)if(e.hasOwnProperty(s)&&null!==e[s]&&void 0!==e[s])return e[s];for(const s of Object.values(e))if(s&&"object"==typeof s){const e=this.deepSearch(s,t);if(e)return e}return null}static extractTokens(e){const t=this.deepSearch(e,this.TOKEN_KEYS),s=this.deepSearch(e,this.REFRESH_TOKEN_KEYS),r=this.deepSearch(e,this.TOKEN_TYPE_KEYS),o=this.deepSearch(e,this.EXPIRES_KEYS),n=this.deepSearch(e,this.EXPIRES_AT_KEYS);if(!t)throw new Error("No access token found in response");return{accessToken:t,refreshToken:s,expiresIn:ExpirationHandler.calculateExpiration(t,o,n),expiresAt:n,tokenType:r||"Bearer"}}static extractUser(e){const t=this.deepSearch(e,this.USER_KEYS);if(t&&"object"==typeof t)return{id:t.id||t.user_id||t.userId||"unknown",name:t.name||t.username||t.full_name||t.email||"User",email:t.email||t.userEmail,...t};const s=this.deepSearch(e,["name","username","full_name","email"]);return s?{id:this.deepSearch(e,["id","user_id","userId"])||"unknown",name:s,email:this.deepSearch(e,["email","user_email","userEmail"]),sucursales:this.deepSearch(e,["sucursales","branches","offices"])}:null}}TokenExtractor$1.TOKEN_KEYS=["accessToken","access_token","token","authToken","auth_token","bearerToken","bearer_token","jwt","jwtToken","jwt_token"],TokenExtractor$1.REFRESH_TOKEN_KEYS=["refreshToken","refresh_token","renewToken","renew_token"],TokenExtractor$1.EXPIRES_KEYS=["expiresIn","expires_in","exp","expiration","expires_at","expiresAt","expiry","expiry_time","expiryTime","valid_until","validUntil"],TokenExtractor$1.EXPIRES_AT_KEYS=["expires_at","expiresAt","expiration","expiry_time","expiryTime","valid_until","validUntil","rt_expires_at","rtExpiresAt"],TokenExtractor$1.TOKEN_TYPE_KEYS=["tokenType","token_type","type","authType","auth_type"],TokenExtractor$1.USER_KEYS=["user","userData","user_data","profile","userProfile","user_profile","data","userInfo","user_info","account","accountData","account_data","name","username","userName","email","userEmail","user_email","userId","id","full_name","fullName"];class RefreshManager{constructor(e,t,s,r){this.refreshTimer=null,this.isRefreshing=!1,this.refreshPromise=null,this.refreshAttempts=0,this.lastRefreshTime=0,this.config=e,this.storageManager=t,this.httpClient=s,this.onTokenRefresh=r?.onTokenRefresh,this.onRefreshError=r?.onRefreshError,this.onSessionRenewed=r?.onSessionRenewed}scheduleTokenRefresh(e){if(!this.config.tokenRefresh.enabled||!e.refreshToken)return void console.debug("Token refresh disabled or no refresh token available");this.clearRefreshTimer();const t=ExpirationHandler.calculateExpiration(e.accessToken,e.expiresIn,e.expiresAt);if(!t){console.warn("No expiration info available, using default scheduling");const t=1e3*("sanctum"===TokenHandler.parseToken(e.accessToken).type?86400:3600)-1e3*this.config.tokenRefresh.bufferTime;return void(t>0&&this.scheduleRefreshTimer(t))}const s=this.config.tokenRefresh.minimumTokenLifetime||300;if(t<s){console.warn(`Token expires in ${t}s (less than minimum ${s}s), using grace period`);const e=1e3*(this.config.tokenRefresh.gracePeriod||60);return this.scheduleRefreshTimer(e),void console.debug(`Token refresh scheduled with grace period in ${e/1e3}s`)}const r=1e3*t-1e3*this.config.tokenRefresh.bufferTime,o=Math.max(r,3e4);o>0?this.scheduleRefreshTimer(o):console.warn("Token expires very soon, but skipping immediate refresh to avoid loops")}async refreshTokens(){if(!this.config.tokenRefresh.enabled)throw new Error("Token refresh is disabled");if(this.isRefreshing&&this.refreshPromise)return console.debug("Refresh already in progress, waiting for completion"),this.refreshPromise;const e=Date.now();if(e-this.lastRefreshTime<5e3)throw new Error("Refresh rate limit exceeded");if(e-this.lastRefreshTime>6e4&&(this.refreshAttempts=0),this.refreshAttempts>=this.config.tokenRefresh.maxRetries)throw console.error("Maximum refresh attempts exceeded, stopping automatic refresh"),this.refreshAttempts=0,new Error("Maximum refresh attempts exceeded");this.isRefreshing=!0,this.refreshAttempts++,this.refreshPromise=this.performRefresh();try{const t=await this.refreshPromise;return this.refreshAttempts=0,this.lastRefreshTime=e,t}catch(e){if(console.error(`Refresh attempt ${this.refreshAttempts} failed:`,e),this.refreshAttempts<this.config.tokenRefresh.maxRetries){const e=Math.min(2e3*this.refreshAttempts,3e4);console.log(`Scheduling retry ${this.refreshAttempts+1}/${this.config.tokenRefresh.maxRetries} in ${e}ms`),setTimeout(()=>{this.storageManager.getStoredTokens().then(e=>{e?.refreshToken?this.refreshTokens().catch(console.error):(console.warn("No refresh token available for retry, stopping attempts"),this.refreshAttempts=0)})},e)}else console.error("Max retries exceeded, stopping refresh attempts"),this.refreshAttempts=0;throw e}finally{this.isRefreshing=!1,this.refreshPromise=null}}shouldRefreshToken(e){if(!this.config.tokenRefresh.enabled)return!1;const t=TokenHandler.parseToken(e);if("jwt"===t.type&&t.exp){const e=Math.floor(Date.now()/1e3);return t.exp-e<this.config.tokenRefresh.bufferTime}return!1}async shouldRefreshTokenAsync(e){if(!this.config.tokenRefresh.enabled)return!1;const t=TokenHandler.parseToken(e);if("jwt"===t.type&&t.exp){const e=Math.floor(Date.now()/1e3);return t.exp-e<this.config.tokenRefresh.bufferTime}return this.shouldRefreshBasedOnMetadata()}clearRefreshTimer(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null,console.debug("Refresh timer cleared"))}getRefreshStatus(){return{isRefreshing:this.isRefreshing,refreshAttempts:this.refreshAttempts,lastRefreshTime:this.lastRefreshTime,nextRefreshScheduled:null!==this.refreshTimer}}async forceRefresh(){return this.clearRefreshTimer(),this.refreshAttempts=0,this.refreshTokens()}isAuthenticationError(e){if(e?.response?.status){const t=e.response.status;return 401===t||403===t}if(e?.message){const t=e.message.toLowerCase();return t.includes("401")||t.includes("403")||t.includes("unauthorized")||t.includes("unauthenticated")}return!1}async performRefresh(){const e=await this.storageManager.getStoredTokens(),t=e?.refreshToken;if(!t)throw new Error("No refresh token available");console.debug("Performing token refresh...");try{const e=`${this.config.authServiceUrl}${this.config.endpoints.refresh}`,s=TokenHandler.parseToken(t);let r;console.debug("Refresh token info:",{type:s.type,url:e,hasRefreshToken:!!t,refreshTokenLength:t.length}),"sanctum"===s.type?(console.debug("Using Sanctum refresh method (Authorization header)"),r=await this.httpClient.post(e,{refresh_token:t,refreshToken:t},{headers:{Authorization:`Bearer ${t}`}})):(console.debug("Using JWT refresh method (body only)"),r=await this.httpClient.post(e,{refresh_token:t,refreshToken:t}));const o=this.processRefreshResponse(r,t);return await this.storageManager.storeTokens(o),await this.storageManager.updateLastRefreshTime(),this.scheduleTokenRefresh(o),this.onTokenRefresh?.(o),this.onSessionRenewed?.(o),console.debug("Token refresh completed successfully"),o}catch(e){const t=e instanceof Error?e.message:"Token refresh failed";console.error("Token refresh failed:",t);throw(this.isAuthenticationError(e)||t.includes("invΓ‘lidos")||t.includes("invalid")||t.includes("expired")||t.includes("requerido")||t.includes("Unauthorized")||t.includes("Unauthenticated"))&&(console.warn("Refresh token invalid or expired, clearing authentication data"),await this.storageManager.clearAll(),this.refreshAttempts=this.config.tokenRefresh.maxRetries),this.onRefreshError?.(e instanceof Error?e:new Error(t)),e}}processRefreshResponse(e,t){try{const s=TokenExtractor$1.extractTokens(e);return s.refreshToken||(s.refreshToken=t,console.debug("Using original refresh token (no new token provided)")),s}catch(e){throw Logger.error("Error processing refresh response:",e),new Error("Invalid refresh response format")}}scheduleRefreshTimer(e){try{this.refreshTimer=setTimeout(()=>{console.debug("Automatic refresh triggered by timer"),this.refreshTokens().catch(e=>{console.error("Automatic refresh failed:",e),this.onRefreshError?.(e)})},e)}catch(e){console.error("Error scheduling refresh timer:",e)}}async shouldRefreshBasedOnMetadata(){try{const e=await this.storageManager.getTokenMetadata(),t=await this.storageManager.getStoredTokens();if(!e?.storedAt||!t?.expiresIn)return!1;const s=Math.floor(Date.now()/1e3)-e.storedAt;return t.expiresIn-s<this.config.tokenRefresh.bufferTime}catch(e){return console.error("Error checking refresh metadata:",e),!1}}reset(){this.clearRefreshTimer(),this.isRefreshing=!1,this.refreshPromise=null,this.refreshAttempts=0,this.lastRefreshTime=0,console.debug("Refresh manager reset")}async canRefresh(){try{const e=await this.storageManager.getStoredTokens();return!(!this.config.tokenRefresh.enabled||!e?.refreshToken||this.isRefreshing)}catch{return!1}}}class SessionValidator{constructor(e,t){this.lastActivityTime=Date.now(),this.isListening=!1,this.config={enabled:!0,validateOnFocus:!0,validateOnVisibility:!0,maxInactivityTime:300,autoLogoutOnInvalid:!0,...e},this.onValidationRequired=t}startListening(){!this.isListening&&this.config.enabled&&("undefined"!=typeof window&&"undefined"!=typeof document?(this.config.validateOnVisibility&&(this.visibilityListener=()=>this.handleVisibilityChange(),document.addEventListener("visibilitychange",this.visibilityListener),console.debug("SessionValidator: visibilitychange listener added")),this.config.validateOnFocus&&(this.focusListener=()=>this.handleWindowFocus(),window.addEventListener("focus",this.focusListener),console.debug("SessionValidator: focus listener added")),this.pageShowListener=e=>this.handlePageShow(e),window.addEventListener("pageshow",this.pageShowListener),console.debug("SessionValidator: pageshow listener added"),this.isListening=!0,console.debug("SessionValidator: Started listening for app lifecycle events")):console.debug("SessionValidator: Not a browser environment, skipping"))}stopListening(){this.isListening&&"undefined"!=typeof window&&"undefined"!=typeof document&&(this.visibilityListener&&document.removeEventListener("visibilitychange",this.visibilityListener),this.focusListener&&window.removeEventListener("focus",this.focusListener),this.pageShowListener&&window.removeEventListener("pageshow",this.pageShowListener),this.isListening=!1,console.debug("SessionValidator: Stopped listening for app lifecycle events"))}async handleVisibilityChange(){"visible"===document.visibilityState?(console.debug("SessionValidator: App became visible"),await this.validateIfNeeded("visibility")):(console.debug("SessionValidator: App became hidden"),this.lastActivityTime=Date.now())}async handleWindowFocus(){console.debug("SessionValidator: Window gained focus"),await this.validateIfNeeded("focus")}async handlePageShow(e){e.persisted?(console.debug("SessionValidator: Page restored from cache"),await this.validateIfNeeded("pageshow-cached")):(console.debug("SessionValidator: Page loaded normally"),this.lastActivityTime=Date.now())}async validateIfNeeded(e){const t=Date.now(),s=(t-this.lastActivityTime)/1e3;if(console.debug(`SessionValidator: Validation triggered by ${e}`),console.debug(`SessionValidator: Inactive for ${Math.floor(s)}s (max: ${this.config.maxInactivityTime}s)`),s<this.config.maxInactivityTime)return console.debug("SessionValidator: Inactivity time below threshold, skipping validation"),void(this.lastActivityTime=t);console.debug("SessionValidator: Performing session validation...");try{await this.onValidationRequired()?(console.debug("SessionValidator: Session is valid"),this.lastActivityTime=t):console.warn("SessionValidator: Session is invalid")}catch(e){console.error("SessionValidator: Validation error:",e)}}updateLastActivity(){this.lastActivityTime=Date.now()}getStatus(){const e=Date.now();return{isListening:this.isListening,lastActivityTime:this.lastActivityTime,inactiveSeconds:Math.floor((e-this.lastActivityTime)/1e3)}}async forceValidation(){console.debug("SessionValidator: Forcing immediate validation");try{const e=await this.onValidationRequired();return e&&(this.lastActivityTime=Date.now()),e}catch(e){return console.error("SessionValidator: Force validation error:",e),!1}}static isSupported(){return"undefined"!=typeof window&&"undefined"!=typeof document}}class IndexedDBAdapter{constructor(e="AuthSDK",t=1,s="auth_data"){this.db=null,this.dbName=e,this.dbVersion=t,this.storeName=s}async openDB(){return this.db?this.db:new Promise((e,t)=>{if("undefined"==typeof window||!window.indexedDB)return void t(new Error("IndexedDB not supported"));const s=window.indexedDB.open(this.dbName,this.dbVersion);s.onerror=()=>t(s.error),s.onsuccess=()=>{this.db=s.result,e(this.db)},s.onupgradeneeded=e=>{const t=e.target.result;t.objectStoreNames.contains(this.storeName)||t.createObjectStore(this.storeName,{keyPath:"key"})}})}async setItem(e,t){const s=await this.openDB();return new Promise((r,o)=>{const n=s.transaction([this.storeName],"readwrite").objectStore(this.storeName).put({key:e,value:t});n.onerror=()=>o(n.error),n.onsuccess=()=>r()})}async getItem(e){try{const t=await this.openDB();return new Promise((s,r)=>{const o=t.transaction([this.storeName],"readonly").objectStore(this.storeName).get(e);o.onerror=()=>r(o.error),o.onsuccess=()=>{const e=o.result;s(e?e.value:null)}})}catch(e){return console.error("Error getting item from IndexedDB:",e),null}}async removeItem(e){const t=await this.openDB();return new Promise((s,r)=>{const o=t.transaction([this.storeName],"readwrite").objectStore(this.storeName).delete(e);o.onerror=()=>r(o.error),o.onsuccess=()=>s()})}async clear(){const e=await this.openDB();return new Promise((t,s)=>{const r=e.transaction([this.storeName],"readwrite").objectStore(this.storeName).clear();r.onerror=()=>s(r.error),r.onsuccess=()=>t()})}}class LocalStorageAdapter{async setItem(e,t){"undefined"!=typeof window&&localStorage.setItem(e,t)}async getItem(e){return"undefined"!=typeof window?localStorage.getItem(e):null}async removeItem(e){"undefined"!=typeof window&&localStorage.removeItem(e)}async clear(){"undefined"!=typeof window&&localStorage.clear()}}class StorageManager{constructor(e){this.config=e,this.storageAdapter=this.createStorageAdapter()}createStorageAdapter(){return"localStorage"===(this.config.type||"indexedDB")?new LocalStorageAdapter:new IndexedDBAdapter(this.config.dbName,this.config.dbVersion,this.config.storeName)}async storeTokens(e){try{const t=Math.floor(Date.now()/1e3),s={...e,storedAt:t,lastRefreshed:t,version:"1.2.3",sessionId:this.generateSessionId()};await this.storageAdapter.setItem(this.config.tokenKey,JSON.stringify(s)),e.refreshToken&&await this.storageAdapter.setItem(this.config.refreshTokenKey,e.refreshToken),console.debug("Tokens stored successfully with session metadata")}catch(e){throw console.error("Error storing tokens:",e),new Error("Failed to store authentication tokens")}}async storeUser(e){try{const t={...e,lastUpdated:Math.floor(Date.now()/1e3),sessionId:await this.getCurrentSessionId()};await this.storageAdapter.setItem(this.config.userKey,JSON.stringify(t)),console.debug("User data stored successfully")}catch(e){throw console.error("Error storing user:",e),new Error("Failed to store user information")}}async getStoredTokens(){try{const e=await this.storageAdapter.getItem(this.config.tokenKey);if(!e)return null;let t;try{t=JSON.parse(e)}catch{console.warn("Legacy token format detected, migrating...");return{accessToken:e,refreshToken:await this.storageAdapter.getItem(this.config.refreshTokenKey)||void 0}}if(!t.accessToken)return console.warn("Invalid token data structure, clearing storage"),await this.clearTokens(),null;if(t.expiresIn&&t.storedAt){const e=Math.floor(Date.now()/1e3)-t.storedAt,s=Math.max(0,t.expiresIn-e);t.expiresIn=s}const s=await this.storageAdapter.getItem(this.config.refreshTokenKey);return{accessToken:t.accessToken,refreshToken:s||t.refreshToken,expiresIn:t.expiresIn,expiresAt:t.expiresAt,tokenType:t.tokenType}}catch(e){return console.error("Error retrieving stored tokens:",e),await this.clearTokens(),null}}async getStoredUser(){try{const e=await this.storageAdapter.getItem(this.config.userKey);if(!e)return null;try{const t=JSON.parse(e);return t.id||t.email?t:(console.warn("Invalid user data structure, clearing storage"),await this.clearUser(),null)}catch{return console.warn("Corrupted user data, clearing storage"),await this.clearUser(),null}}catch(e){return console.error("Error retrieving stored user:",e),null}}async getTokenMetadata(){try{const e=await this.storageAdapter.getItem(this.config.tokenKey);if(!e)return null;const t=JSON.parse(e);return{storedAt:t.storedAt,lastRefreshed:t.lastRefreshed,sessionId:t.sessionId,version:t.version}}catch(e){return console.error("Error getting token metadata:",e),null}}async updateLastRefreshTime(){try{const e=await this.storageAdapter.getItem(this.config.tokenKey);if(e){const t=JSON.parse(e);t.lastRefreshed=Math.floor(Date.now()/1e3),await this.storageAdapter.setItem(this.config.tokenKey,JSON.stringify(t))}}catch(e){console.error("Error updating last refresh time:",e)}}async clearTokens(){try{await Promise.all([this.storageAdapter.removeItem(this.config.tokenKey),this.storageAdapter.removeItem(this.config.refreshTokenKey)]),console.debug("Tokens cleared successfully")}catch(e){console.error("Error clearing tokens:",e)}}async clearUser(){try{await this.storageAdapter.removeItem(this.config.userKey),console.debug("User data cleared successfully")}catch(e){console.error("Error clearing user data:",e)}}async clearAll(){try{await Promise.all([this.clearTokens(),this.clearUser()]),console.debug("All authentication data cleared successfully")}catch(e){console.error("Error clearing all storage:",e)}}async hasValidSession(){try{const e=await this.getStoredTokens(),t=await this.getStoredUser();return!(!e?.accessToken||!t?.id)}catch{return!1}}generateSessionId(){return`session_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}async getCurrentSessionId(){try{const e=await this.getTokenMetadata();return e?.sessionId||null}catch{return null}}async migrateStorage(){try{const e=await this.getTokenMetadata();if(!e?.version){console.log("Migrating legacy storage format...");const e=await this.getStoredTokens(),t=await this.getStoredUser();e&&t&&(await this.storeTokens(e),await this.storeUser(t),console.log("Storage migration completed successfully"))}}catch(e){console.error("Error during storage migration:",e)}}getAdapter(){return this.storageAdapter}getConfig(){return this.config}}class TokenExtractor{static deepSearchByPaths(e,t){if(!e||"object"!=typeof e)return null;const s=`paths_${JSON.stringify(t)}_${this.generateObjectSignature(e)}`,r=this.searchCache.get(s);if(r&&Date.now()-r.timestamp<this.CACHE_TTL)return r.value;for(const r of t){if(""===r){const t=this.extractFromRoot(e);if(t)return this.searchCache.set(s,{value:t,timestamp:Date.now()}),t;continue}const t=this.getNestedValue(e,r);if(t&&"object"==typeof t)return this.searchCache.set(s,{value:t,timestamp:Date.now()}),t}return this.searchCache.set(s,{value:null,timestamp:Date.now()}),null}static extractFromRoot(e){return["id","_id","email","name","firstName","usuario"].some(t=>void 0!==e[t])?e:null}static getNestedValue(e,t){return t.split(".").reduce((e,t)=>e&&void 0!==e[t]?e[t]:null,e)}static generateObjectSignature(e){try{return Object.keys(e).sort().slice(0,10).join(",")}catch{return"unknown"}}static enhancedDeepSearch(e,t,s=5){if(!e||"object"!=typeof e||s<=0)return null;for(const s of t)if(e.hasOwnProperty(s)&&null!==e[s]&&void 0!==e[s])return e[s];const r=[];for(const[t,s]of Object.entries(e))s&&"object"==typeof s&&!Array.isArray(s)&&r.push({obj:s,depth:1});for(;r.length>0;){const{obj:e,depth:o}=r.shift();if(!(o>=s)){for(const s of t)if(e.hasOwnProperty(s)&&null!==e[s]&&void 0!==e[s])return e[s];for(const[t,s]of Object.entries(e))s&&"object"==typeof s&&!Array.isArray(s)&&r.push({obj:s,depth:o+1})}}return null}static normalizeExpirationTime(e){if(e){if("number"==typeof e){if(e>1609459200){const t=Math.floor(Date.now()/1e3);return Math.max(0,e-t)}return Math.max(0,e)}if("string"==typeof e){if(e.includes("T")||e.includes("-")||e.includes("/")){const t=new Date(e);if(!isNaN(t.getTime())){const e=Date.now(),s=t.getTime(),r=Math.floor((s-e)/1e3);return Math.max(0,r)}}const t=parseInt(e);if(!isNaN(t)){if(t>1609459200){const e=Math.floor(Date.now()/1e3);return Math.max(0,t-e)}return Math.max(0,t)}}console.warn("Could not parse expiration time:",e,typeof e)}}static extractTokens(e){const t=this.enhancedDeepSearch(e,this.TOKEN_KEYS),s=this.enhancedDeepSearch(e,this.REFRESH_TOKEN_KEYS),r=this.enhancedDeepSearch(e,this.TOKEN_TYPE_KEYS);if(!t)throw new Error("No access token found in response");return{accessToken:t,refreshToken:s,expiresIn:this.extractExpirationTime(e),expiresAt:this.enhancedDeepSearch(e,["expires_at","expiresAt","rt_expires_at"]),tokenType:r||"Bearer",_originalTokenResponse:e}}static extractExpirationTime(e){const t=this.enhancedDeepSearch(e,this.EXPIRES_KEYS);return this.normalizeExpirationTime(t)}static extractUser(e){const t=this.deepSearchByPaths(e,this.USER_SEARCH_PATHS);if(!t||"object"!=typeof t)return this.buildUserFromScatteredFields(e);const s=this.detectBackendType(e,t),r=this.mapToStandardUser(t);return{...r,...this.preserveUnmappedFields(t,r),_originalUserResponse:e,_backendType:s}}static mapToStandardUser(e){return{id:this.findUserField(e,["_id","id","user_id","userId"])||"unknown",email:this.findUserField(e,["email","user_email","userEmail","correo"]),name:this.buildUserName(e),firstName:this.findUserField(e,["firstName","first_name","nombre"]),lastName:this.findUserField(e,["lastName","last_name","apellido"]),role:this.findUserField(e,["role","rol","roles","authorities"]),permissions:this.extractPermissions(e),isActive:this.findUserField(e,["isActive","is_active","active","activo"]),profile:this.findUserField(e,["profile","perfil","profileData"])}}static buildUserName(e){const t=this.findUserField(e,["firstName","first_name","nombre"]),s=this.findUserField(e,["lastName","last_name","apellido"]),r=this.findUserField(e,["name","fullName","full_name","nombreCompleto"]),o=this.findUserField(e,["email","correo"]);return r||(t&&s?`${t} ${s}`:t||(o||"Usuario"))}static findUserField(e,t){for(const s of t)if(void 0!==e[s]&&null!==e[s])return e[s]}static extractPermissions(e){const t=this.findUserField(e,["permissions","permisos","authorities","roles"]);return Array.isArray(t)?t:"string"==typeof t?[t]:[]}static preserveUnmappedFields(e,t){const s={},r=new Set(Object.keys(t));for(const[t,o]of Object.entries(e))r.has(t)||t.startsWith("_")||(s[t]=o);return s}static detectBackendType(e,t){return e.resultado?.data||e.token?.includes?.("|")?"laravel-sanctum":e.data?.user||void 0!==e.success?"node-express":e.accessToken&&!e.data?"jwt-standard":"unknown"}static buildUserFromScatteredFields(e){const t=this.enhancedDeepSearch(e,["id","_id","user_id","userId"]),s=this.enhancedDeepSearch(e,["email","user_email","userEmail"]),r=this.enhancedDeepSearch(e,["name","username","user_name","fullName"]);if(!t&&!s&&!r){const t=this.enhancedDeepSearch(e,this.TOKEN_KEYS);if(t){const s=this.parseUserFromToken(t);if(s)return{...s,_originalUserResponse:e,_backendType:"jwt-parsed"}}return null}return{id:t||"unknown",email:s,name:r||s||"Usuario",_originalUserResponse:e,_backendType:"scattered-fields"}}static parseUserFromToken(e){try{if(e.includes("|"))return null;const t=e.split(".");if(3!==t.length)return null;const s=t[1],r=s.padEnd(s.length+(4-s.length%4)%4,"="),o=JSON.parse(atob(r));return{id:o.sub||o.user_id||o.id||o.userId||"unknown",name:o.name||o.username||o.firstName||o.email||"Usuario",email:o.email,firstName:o.firstName||o.first_name,lastName:o.lastName||o.last_name,role:o.role||o.roles?.[0],permissions:o.permissions||o.authorities||[],...o}}catch(e){return console.warn("Error parsing user from JWT:",e),null}}static debugResponse(e,t=0){const s=" ".repeat(t);if(console.group(`${s}πŸ” Enhanced Response Analysis (depth: ${t})`),e&&"object"==typeof e){console.log(`${s}πŸ“Š Object keys:`,Object.keys(e));const r=this.detectBackendType(e,e);console.log(`${s}πŸ”§ Detected backend type:`,r);const o=Object.keys(e).filter(e=>this.TOKEN_KEYS.some(t=>e.toLowerCase().includes(t.toLowerCase())));o.length>0&&console.log(`${s}πŸ”‘ Possible token fields:`,o);const n=Object.keys(e).filter(e=>["user","data","profile","account"].some(t=>e.toLowerCase().includes(t.toLowerCase())));if(n.length>0&&console.log(`${s}πŸ‘€ Possible user fields:`,n),console.log(`${s}πŸ” Searching in paths:`,this.USER_SEARCH_PATHS),t<2)for(const[r,o]of Object.entries(e))o&&"object"==typeof o&&!Array.isArray(o)&&(console.log(`${s}πŸ“‚ Analyzing nested object: ${r}`),this.debugResponse(o,t+1))}else console.log(`${s}πŸ“ Primitive value:`,typeof e,e);console.groupEnd()}static clearCache(){this.searchCache.clear()}static testExtraction(e){console.group("πŸ§ͺ Testing Token and User Extraction");try{console.log("πŸ“₯ Original response:",e),console.log("πŸ”‘ Testing token extraction...");const t=this.extractTokens(e);console.log("βœ… Extracted tokens:",t),console.log("πŸ‘€ Testing user extraction...");const s=this.extractUser(e);console.log("βœ… Extracted user:",s),console.log("πŸŽ‰ Extraction test completed successfully!")}catch(e){console.error("❌ Extraction test failed:",e)}console.groupEnd()}}TokenExtractor.TOKEN_KEYS=["accessToken","access_token","token","authToken","auth_token","bearerToken","bearer_token","jwt","jwtToken","jwt_token"],TokenExtractor.REFRESH_TOKEN_KEYS=["refreshToken","refresh_token","renewToken","renew_token","rt_expires_at"],TokenExtractor.EXPIRES_KEYS=["expiresIn","expires_in","exp","expiration","expires_at","expiresAt","expiry","expiry_time","expiryTime","valid_until","validUntil","rt_expires_at"],TokenExtractor.TOKEN_TYPE_KEYS=["tokenType","token_type","type","authType","auth_type"],TokenExtractor.USER_SEARCH_PATHS=["data.user","resultado.data","user","data","profile","userInfo","account",""],TokenExtractor.searchCache=new Map,TokenExtractor.CACHE_TTL=5e3;class AuthSDK{constructor(e,t){this.sessionValidator=null,this.axiosInterceptorManager=null,this.expirationTimer=null,this.stateChangeListeners=[],this.isInitialized=!1,this.config=this.buildConfig(e),this.callbacks=t||{},Logger.setDebugMode(this.config.debug||!1),this.storageManager=new StorageManager(this.config.storage),this.refreshManager=new RefreshManager(this.config,this.storageManager,this.config.httpClient,{onTokenRefresh:e=>{this.handleTokenRefresh(e)},onRefreshError:e=>{this.callbacks.onError?.(e.message)},onSessionRenewed:e=>{this.callbacks.onTokenRefresh?.(e)}}),this.state={isAuthenticated:!1,user:null,tokens:null,loading:!1,error:null,backendType:"unknown",capabilities:{canRefresh:Boolean(this.config.tokenRefresh?.enabled),hasProfile:!0,supportsOTP:!1,supportsBiometric:!1}},this.config.sessionValidation.enabled&&SessionValidator.isSupported()&&(this.sessionValidator=new SessionValidator(this.config.sessionValidation,()=>this.validateSession())),this.config.interceptors.enabled&&this.config.interceptors.axiosInstance&&(this.axiosInterceptorManager=new AxiosInterceptorManager(this.config.interceptors.axiosInstance,{getAccessToken:()=>this.getValidAccessToken(),onSessionInvalid:async()=>{console.warn("Axios interceptor detected invalid session"),await this.clearSession(),this.callbacks.onSessionInvalid?.()},onTokenRefresh:async()=>{await this.refreshTokens()}}),this.axiosInterceptorManager.setup({autoInjectToken:this.config.interceptors.autoInjectToken,handleAuthErrors:this.config.interceptors.handleAuthErrors}),console.log("βœ… Axios interceptors initialized")),this.initializeFromStorage()}buildConfig(e){return{authServiceUrl:e.authServiceUrl,endpoints:{login:"/auth/login",register:"/auth/register",refresh:"/auth/refreshToken",logout:"/auth/logout",profile:"/auth/profile",...e.endpoints},storage:{type:"indexedDB",dbName:"AuthSDK",dbVersion:1,storeName:"auth_data",tokenKey:"auth_access_token",refreshTokenKey:"auth_refresh_token",userKey:"auth_user",...e.storage},tokenRefresh:{enabled:!0,bufferTime:900,maxRetries:3,minimumTokenLifetime:300,gracePeriod:60,...e.tokenRefresh},httpClient:e.httpClient||this.createDefaultHttpClient(),debug:e.debug||!1,backend:{type:e.backend?.type||"jwt-standard",userSearchPaths:e.backend?.userSearchPaths||["user","data.user"],fieldMappings:e.backend?.fieldMappings||{},preserveOriginalData:e.backend?.preserveOriginalData??!1},sessionValidation:{enabled:!0,validateOnFocus:!0,validateOnVisibility:!0,maxInactivityTime:300,autoLogoutOnInvalid:!0,validateOnStartup:!0,...e.sessionValidation},interceptors:{enabled:!1,autoInjectToken:!0,handleAuthErrors:!0,axiosInstance:void 0,...e.interceptors}}}async login(e){this.setLoading(!0),this.setError(null);try{const t=`${this.config.authServiceUrl}${this.config.endpoints.login}`,s=await this.config.httpClient.post(t,e),r=TokenExtractor.extractTokens(s),o=TokenExtractor.extractUser(s);if(!o)throw new Error("No user information found in login response");return await this.establishSession(r,o),this.callbacks.onLogin?.(o,r),console.debug("Login successful, session established"),o}catch(e){const t=e instanceof Error?e.message:"Login failed";throw this.setError(t),this.callbacks.onError?.(t),e}finally{this.setLoading(!1)}}async register(e){this.setLoading(!0),this.setError(null);try{const t=`${this.config.authServiceUrl}${this.config.endpoints.register}`,s=await this.config.httpClient.post(t,e);try{const e=TokenExtractor.extractTokens(s),t=TokenExtractor.extractUser(s);if(!t)throw new Error("No user information found in register response");return await this.establishSession(e,t),this.callbacks.onLogin?.(t,e),console.debug("Registration successful with automatic login"),t}catch(t){console.debug("Registration successful, manual login required");const r=TokenExtractor.extractUser(s);return r||{id:"unknown",name:e.name||e.email||"User",email:e.email}}}catch(e){const t=e instanceof Error?e.message:"Registration failed";throw this.setError(t),this.callbacks.onError?.(t),e}finally{this.setLoading(!1)}}async logout(){try{if(this.state.tokens?.accessToken)try{const e=`${this.config.authServiceUrl}${this.config.endpoints.logout}`;await this.config.httpClient.post(e,{},{headers:{Authorization:`Bearer ${this.state.tokens.accessToken}`}}),console.debug("Server-side logout successful")}catch(e){console.warn("Server-side logout failed, continuing with client-side cleanup:",e)}}finally{await this.clearSession(),this.callbacks.onLogout?.(),console.debug("Logout completed, session cleared")}}async clearLocalSession(){console.debug("Clearing local session only (no backend call)"),await this.clearSession(),this.callbacks.onLogout?.(),console.debug("Local session cleared")}async refreshTokens(){return this.refreshManager.refreshTokens()}onAuthStateChanged(e){return this.stateChangeListeners.push(e),this.isInitialized&&e(this.getState()),()=>{const t=this.stateChangeListeners.indexOf(e);t>-1&&this.stateChangeListeners.splice(t,1)}}getState(){return{...this.state}}getCurrentUser(){return this.state.user}getAccessToken(){return this.state.tokens?.accessToken||null}getRefreshToken(){return this.state.tokens?.refreshToken||null}async isAuthenticated(){return!(!this.state.isAuthenticated||!this.state.tokens?.accessToken)&&this.isTokenValid(this.state.tokens.accessToken)}async validateSession(){if(Logger.debug("Validating session with server..."),!this.state.isAuthenticated||!this.state.tokens)return console.debug("No active session to validate"),!1;if(!this.config.tokenRefresh.enabled||!this.state.tokens.refreshToken)return console.debug("Cannot validate session: refresh token not available"),!0;try{return await this.refreshTokens(),Logger.debug("Session validated successfully"),this.callbacks.onSessionValidated?.(),!0}catch(e){return Logger.warn("Session validation failed:",e),this.config.sessionValidation.autoLogoutOnInvalid&&(Logger.debug("Auto-logout due to invalid session"),await this.clearSession(),this.callbacks.onSessionInvalid?.()),!1}}async getValidAccessToken(){if(!this.state.tokens?.accessToken)return null;if(!this.config.tokenRefresh.enabled){return await this.isTokenValid(this.state.tokens.accessToken)?this.state.tokens.accessToken:null}if(await this.refreshManager.shouldRefreshTokenAsync(this.state.tokens.accessToken)&&await this.refreshManager.canRefresh())try{return(await this.refreshManager.refreshTokens()).accessToken}catch(e){return console.error("Failed to refresh token:",e),null}return await this.isTokenValid(this.state.tokens.accessToken)?this.state.tokens.accessToken:null}async getAuthHeaders(){const e=await this.getValidAccessToken();if(!e)throw new Error("No valid authentication token available");return{Authorization:`Bearer ${e}`}}debugToken(e){const t=e||this.state.tokens?.accessToken;if(!t)return void console.log("No token to debug");console.group("πŸ” Token Debug Information");const s=TokenHandler.parseToken(t);if(console.log("Token type:",s.type),console.log("Token info:",s),"jwt"===s.type&&s.payload&&(console.log("JWT Payload:",s.payload),s.exp)){const e=new Date(1e3*s.exp),t=new Date,r=Math.max(0,Math.floor((e.getTime()-t.getTime())/1e3));console.log("Expires at:",e.toISOString()),console.log("Time left:",`${Math.floor(r/60)}m ${r%60}s`)}console.log("Refresh status:",this.refreshManager.getRefreshStatus()),console.groupEnd()}debugResponse(e){console.group("πŸ” API Response Debug"),TokenExtractor.debugResponse(e);try{const t=TokenExtractor.extractTokens(e);console.log("βœ… Extracted tokens:",t)}catch(e){console.log("❌ Token extraction failed:",e)}try{const t=TokenExtractor.extractUser(e);console.log("βœ… Extracted user:",t)}catch(e){console.log("❌ User extraction failed:",e)}console.groupEnd()}async forceRefreshTokens(){return this.refreshManager.forceRefresh()}async getExtendedSessionInfo(){const e=await this.storageManager.getStoredTokens(),t=await this.storageManager.getTokenMetadata();return{isValid:!!e?.accessToken&&await this.isTokenValid(e.accessToken),user:this.state.user,tokens:this.state.tokens,tokenType:e?.tokenType||null,tokenFormat:this.detectTokenFormat(e?.accessToken),expiresIn:e?.expiresIn||null,refreshAvailable:await this.refreshManager.canRefresh(),canRefresh:Boolean(this.config.tokenRefresh?.enabled)&&await this.refreshManager.canRefresh(),sessionId:t?.sessionId||null,backendType:this.state.backendType||null,storedAt:t?.storedAt||null,lastRefreshed:t?.lastRefreshed||null,originalResponse:this.state.user?._originalUserResponse||null}}async getSessionInfo(){const e=await this.getExtendedSessionInfo();return{isValid:e.isValid,user:e.user,tokenType:e.tokenType,expiresIn:e.expiresIn,refreshAvailable:e.refreshAvailable,sessionId:e.sessionId}}testExtraction(e){console.group("πŸ§ͺ Testing Token and User Extraction");try{console.log("πŸ“₯ Original response:",e),console.log("πŸ”‘ Testing token extraction...");const t=TokenExtractor.extractTokens(e);console.log("βœ… Extracted tokens:",t),console.log("πŸ‘€ Testing user extraction...");const s=TokenExtractor.extractUser(e);console.log("βœ… Extracted user:",s),console.log("πŸŽ‰ Extraction test completed successfully!")}catch(e){console.error("❌ Extraction test failed:",e)}console.groupEnd()}detectTokenFormat(e){return e?e.includes("|")?"sanctum":3===e.split(".").length?"jwt":"opaque":null}async initializeFromStorage(){try{await this.storageManager.migrateStorage();const[e,t]=await Promise.all([this.storageManager.getStoredTokens(),this.storageManager.getStoredUser()]);if(e?.accessToken&&t){await this.isTokenValid(e.accessToken)?(this.state={isAuthenticated:!0,user:t,tokens:e,loading:!1,error:null},this.config.tokenRefresh.enabled&&e.refreshToken&&this.refreshManager.scheduleTokenRefresh(e),this.scheduleTokenExpiration(e),this.sessionValidator&&(this.sessionValidator.startListening(),console.debug("Session validation listeners started")),this.config.sessionValidation.validateOnStartup&&(Logger.debug("Performing startup session validation..."),this.validateSession().then(e=>{e?Logger.debug("Startup session validation successful"):(Logger.warn("Startup session validation failed, logging out"),this.config.sessionValidation.autoLogoutOnInvalid||this.clearSession())}).catch(e=>{Logger.error("Error during startup session validation:",e)}))):(console.debug("Stored token is invalid, clearing storage"),await this.storageManager.clearAll())}else console.debug("No valid session found in storage"),await this.storageManager.clearAll()}catch(e){console.error("Error initializing from storage:",e),await this.storageManager.clearAll()}finally{this.isInitialized=!0,this.notifyStateChange()}}async establishSession(e,t){await Promise.all([this.storageManager.storeTokens(e),this.storageManager.storeUser(t)]),this.state={isAuthenticated:!0,user:t,tokens:e,loading:!1,error:null},this.config.tokenRefresh.enabled&&e.refreshToken&&this.refreshManager.scheduleTokenRefresh(e),this.scheduleTokenExpiration(e),this.sessionValidator&&(this.sessionValidator.startListening(),console.debug("Session validation listeners started")),this.notifyStateChange()}async clearSession(){this.sessionValidator&&(this.sessionValidator.stopListening(),console.debug("Session validation listeners stopped")),await this.storageManager.clearAll(),this.clearExpirationTimer(),this.refreshManager.clearRefreshTimer(),this.refreshManager.reset(),this.state={isAuthenticated:!1,user:null,tokens:null,loading:!1,error:null,isRefreshing:!1,lastActivity:Date.now(),backendType:"unknown",capabilities:{canRefresh:Boolean(this.config.tokenRefresh?.enabled),hasProfile:!0,supportsOTP:!1,supportsBiometric:!1}},this.notifyStateChange()}handleTokenRefresh(e){this.state.tokens=e,this.scheduleTokenExpiration(e),this.notifyStateChange()}scheduleTokenExpiration(e){this.clearExpirationTimer();const t=ExpirationHandler.calculateExpiration(e.accessToken,e.expiresIn,e.expiresAt);t&&t>0&&(this.expirationTimer=setTimeout(()=>{this.handleTokenExpiration()},1e3*t))}async handleTokenExpiration(){if(console.debug("Token expired, handling expiration..."),this.config.tokenRefresh.enabled&&await this.refreshManager.canRefresh())try{return await this.refreshManager.refreshTokens(),void console.debug("Token refreshed successfully on expiration")}catch(e){console.error("Failed to refresh expired token:",e)}console.debug("Performing automatic logout due to token expiration"),await this.logout(),this.callbacks.onTokenExpired?.()}clearExpirationTimer(){this.expirationTimer&&(clearTimeout(this.expirationTimer),this.expirationTimer=null)}async isTokenValid(e){if(!e)return!1;const t=TokenHandler.parseToken(e);switch(t.type){case"jwt":return t.isValid;case"sanctum":case"opaque":return this.validateStoredToken(e);default:return!1}}async validateStoredToken(e){try{const e=await this.storageManager.getTokenMetadata(),t=await this.storageManager.getStoredTokens();if(!e?.storedAt||!t?.expiresIn)return!0;const s=Math.floor(Date.now()/1e3);return s-e.storedAt<t.expiresIn}catch{return!1}}createDefaultHttpClient(){const makeRequest=async(e,t)=>{const s=await fetch(e,{headers:{"Content-Type":"application/json",...t.headers},...t});if(!s.ok){const e=await s.json().catch(()=>({message:`HTTP ${s.status}: ${s.statusText}`}));throw new Error(e.message||`Request failed with status ${s.status}`)}return s.json()};return{post:async(e,t,s)=>makeRequest(e,{method:"POST",body:t?JSON.stringify(t):void 0,...s}),get:async(e,t)=>makeRequest(e,{method:"GET",...t}),put:async(e,t,s)=>makeRequest(e,{method:"PUT",body:t?JSON.stringify(t):void 0,...s}),delete:async(e,t)=>makeRequest(e,{method:"DELETE",...t})}}setLoading(e){this.state.loading=e,this.notifyStateChange()}setError(e){this.state.error=e,this.notifyStateChange()}notifyStateChange(){const e=this.getState();this.callbacks.onAuthStateChanged?.(e),this.stateChangeListeners.forEach(t=>{try{t(e)}catch(e){console.error("Error in state change listener:",e)}})}debugSession(){console.group("πŸ” Enhanced Session Debug"),console.log("πŸ“Š Current State:",this.getState()),this.state.tokens?.accessToken&&this.debugToken(this.state.tokens.accessToken),this.state.user?._originalUserResponse&&console.log("πŸ“₯ Original User Response:",this.state.user._originalUserResponse),console.log("πŸ”„ Refresh Status:",this.refreshManager.getRefreshStatus()),console.groupEnd()}}const s={"node-express":{userSearchPaths:["data.user","user","data",""],fieldMappings:{userId:["_id","id","userId"],email:["email"],name:["name","username"],firstName:["firstName"],lastName:["lastName"],role:["role"],permissions:["permissions"],token:["accessToken","access_token","token"],refreshToken:["refreshToken","refresh_token"],expires:["expiresIn","expires_in","exp"]},preserveOriginalData:!0,tokenFormat:"jwt"},"laravel-sanctum":{userSearchPaths:["resultado.data","data",""],fieldMappings:{userId:["id","user_id"],email:["email"],name:["name","full_name"],firstName:["first_name","name"],lastName:["last_name"],role:["rol","role"],permissions:["permissions","abilities"],token:["token","access_token"],refreshToken:["refresh_token"],expires:["expires_at","rt_expires_at","expires_in"]},preserveOriginalData:!0,tokenFormat:"opaque"},"jwt-standard":{userSearchPaths:["","user","data"],fieldMappings:{userId:["sub","id","user_id"],email:["email"],name:["name","username"],firstName:["given_name","firstName"],lastName:["family_name","lastName"],role:["role","roles"],permissions:["permissions","scope"],token:["access_token","token"],refreshToken:["refresh_token"],expires:["exp","expires_in"]},preserveOriginalData:!0,tokenFormat:"jwt"},custom:{userSearchPaths:[""],fieldMappings:{userId:["id"],email:["email"],name:["name"],firstName:["firstName"],lastName:["lastName"],role:["role"],permissions:["permissions"],token:["token"],refreshToken:["refreshToken"],expires:["expiresIn"]},preserveOriginalData:!0,tokenFormat:"mixed"}};class AuthSDKFactory{static create(e,t={}){const r=s[e];if(!r)throw new Error(`Backend preset '${e}' not found. Available: ${Object.keys(s).join(", ")}`);const o={authServiceUrl:t.authServiceUrl||"http://localhost:3000",...t,backend:{type:e,userSearchPaths:t.backend?.userSearchPaths||r.userSearchPaths,fieldMappings:(e=>{const t={};for(const[s,r]of Object.entries(e||{}))Array.isArray(r)&&(t[s]=r);return t})(t.backend?.fieldMappings||r.fieldMappings),preserveOriginalData:t.backend?.preserveOriginalData??r.preserveOriginalData}};return console.log(`🏭 Creating AuthSDK for backend: ${e}`),console.log("πŸ”§ Configuration:",o),new AuthSDK(o)}static createCustom(e){return console.log("🏭 Creating custom AuthSDK"),console.log("πŸ”§ Custom configuration:",e),new AuthSDK(e)}static analyzeResponse(e){console.log("πŸ” Analyzing API response structure...");const t={backendType:"unknown",structure:{hasUser:!1,hasTokens:!1,userPath:null,tokenFields:[],userFields:[]},extraction:{tokensExtracted:!1,userExtracted:!1,missingFields:[]},recommendations:[]};e.resultado?.data?t.backendType="laravel-sanctum":e.data?.user||void 0!==e.success?t.backendType="node-express":(e.access_token||e.token)&&(t.backendType="jwt-standard");const s=this.getAllKeys(e);t.structure.tokenFields=s.filter(e=>["token","access","bearer","jwt","auth"].some(t=>e.toLowerCase().includes(t))),t.structure.userFields=s.filter(e=>["user","data","profile","account","me"].some(t=>e.toLowerCase().includes(t))),t.structure.hasTokens=t.structure.tokenFields.length>0,t.structure.hasUser=t.structure.userFields.length>0,t.structure.userFields.length>0&&(t.structure.userPath=t.structure.userFields[0]);try{const{TokenExtractor:s}=require("../core/TokenManager");s.extractTokens(e),t.extraction.tokensExtracted=!0}catch(e){t.extraction.missingFields.push("accessToken")}try{const{TokenExtractor:s}=require("../core/TokenManager"),r=s.extractUser(e);t.extraction.userExtracted=!!r}catch(e){t.extraction.missingFields.push("user")}return t.recommendations=this.generateRecommendations(t),console.log("πŸ“Š Analysis completed:",t),t}static testAllPresets(e){console.log("πŸ§ͺ Testing response with all available presets...");const t={};for(const[r,o]of Object.entries(s)){console.log(`\nπŸ”§ Testing with ${r}