@saasbase-io/core-elements 1.1.16 → 1.1.17
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.
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +15 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function B(e,t,r,i){function o(s){return s instanceof r?s:new r(function(a){a(s)})}return new(r||(r=Promise))(function(s,a){function u(y){try{g(i.next(y))}catch(w){a(w)}}function p(y){try{g(i.throw(y))}catch(w){a(w)}}function g(y){y.done?s(y.value):o(y.value).then(u,p)}g((i=i.apply(e,t||[])).next())})}class wr{constructor(){this.data=new Map}static get Instance(){return wr.instance||(wr.instance=new wr),wr.instance}get(t){return this.data.get(t)}set(t,r){this.data.set(t,r)}setConfig(t){this.data.set("config",t)}getConfig(){return this.data.get("config")}getApiBaseUrl(){const t=this.getConfig().domain;return t.startsWith("localhost")?`http://${t}`:`https://${t}`}remove(t){this.data.delete(t)}clear(){this.data.clear()}}const Lt=wr.Instance;class nt extends Error{constructor(t,r,i){super(r),this.name="LoginflowError",this.code=t,this.details=i}}class ye{constructor(){}static get Instance(){return ye.instance||(ye.instance=new ye),ye.instance}request(t){return B(this,arguments,void 0,function*(r,i={}){const o=Lt.getConfig(),s=`${Lt.getApiBaseUrl()}${r}`,a=5e4,u=new AbortController,p=setTimeout(()=>u.abort(),a);try{const g=yield fetch(s,Object.assign(Object.assign({},i),{signal:u.signal,headers:Object.assign(Object.assign(Object.assign({"Content-Type":"application/json"},o.apiKey&&{Authorization:`Bearer ${o.apiKey}`}),o.headers),i.headers)}));if(clearTimeout(p),!g.ok){const y=yield g.json().catch(()=>({}));throw new nt(y.code||"API_ERROR",y.message||`HTTP ${g.status}: ${g.statusText}`,y.details)}return yield g.json()}catch(g){throw clearTimeout(p),g instanceof nt?g:g instanceof Error?g.name==="AbortError"?new nt("TIMEOUT","Request timeout"):new nt("NETWORK_ERROR",g.message):new nt("UNKNOWN_ERROR","An unknown error occurred")}})}post(t,r){return B(this,void 0,void 0,function*(){return this.request(t,{credentials:"include",method:"POST",body:r?JSON.stringify(r):void 0})})}get(t){return B(this,void 0,void 0,function*(){return this.request(t,{credentials:"include",method:"GET"})})}}class yt{constructor(){this.AUTH_SESSION_STORAGE_KEY="saasbase_auth_session",this.refreshTimeoutId=null,this.refreshPromise=null}static get Instance(){return yt.instance||(yt.instance=new yt),yt.instance}setCurrentSession(t){localStorage.setItem(this.AUTH_SESSION_STORAGE_KEY,JSON.stringify(t))}getCurrentSession(){const t=this.getStoredSession();return t?this.isSessionExpired(t)?(this.clearSession(),null):(this.shouldRefreshToken(t)&&this.triggerBackgroundRefresh(t),t):null}isAuthenticated(){return this.getCurrentSession()!==null}getAuthenticatedUser(){const t=this.getCurrentSession();if(!t||!t.id_token)return null;const r=this.decodeJWT(t.id_token);return r||null}cleanup(){this.refreshTimeoutId&&(clearTimeout(this.refreshTimeoutId),this.refreshTimeoutId=null),this.refreshPromise=null}clearSession(){localStorage.removeItem(this.AUTH_SESSION_STORAGE_KEY)}isSessionExpired(t){return!t||!t.expires_at?!0:Date.now()>=t.expires_at}cleanExpiredSession(){const t=this.getStoredSession();t&&this.isSessionExpired(t)&&this.clearSession()}getStoredSession(){const t=localStorage.getItem(this.AUTH_SESSION_STORAGE_KEY);if(!t)return null;try{return JSON.parse(t)}catch{return this.clearSession(),null}}decodeJWT(t){try{const i=t.split(".")[1].replace(/-/g,"+").replace(/_/g,"/"),o=decodeURIComponent(atob(i).split("").map(s=>"%"+("00"+s.charCodeAt(0).toString(16)).slice(-2)).join(""));return JSON.parse(o)}catch{return null}}shouldRefreshToken(t){if(!t||!t.expires_at||!t.refresh_token)return!1;const r=Date.now(),i=t.expires_at,o=t.expires_in||3600,s=r+o*1e3*.2;return i<=s}scheduleTokenRefresh(){this.refreshTimeoutId&&(clearTimeout(this.refreshTimeoutId),this.refreshTimeoutId=null);const t=this.getCurrentSession();if(!t||!t.expires_at||!t.refresh_token)return;const r=Date.now(),i=t.expires_at,o=t.expires_in||3600,s=i-r-o*1e3*.2;s>0&&(this.refreshTimeoutId=setTimeout(()=>{this.triggerBackgroundRefresh(t)},s))}triggerBackgroundRefresh(t){this.refreshPromise||t.refresh_token&&(this.refreshPromise=this.refreshTokens(t.refresh_token).then(r=>(r&&(this.setCurrentSession(r),this.scheduleTokenRefresh()),r)).catch(r=>(console.error("Background token refresh failed:",r),null)).finally(()=>{this.refreshPromise=null}))}refreshTokens(t){return B(this,void 0,void 0,function*(){try{const r=yield ye.Instance.post("/login-flow/v1/refresh",{refresh_token:t});return{id_token:r.id_token,access_token:r.access_token,refresh_token:r.refresh_token,expires_in:r.expires_in,expires_at:Date.now()+r.expires_in*1e3}}catch(r){return r instanceof nt?console.error("Token refresh failed:",r.message,r.details):console.error("Token refresh failed:",r),this.clearSession(),null}})}}class Ml{constructor(t=sessionStorage){this.storage=t,this.FLOW_STATE_KEY="saasbase_loginflow_state",this.FLOW_EXPIRY_MS=540*1e3}getFlowState(){const t=this.storage.getItem(this.FLOW_STATE_KEY);if(!t)return null;try{const r=JSON.parse(t);return this.isExpired(r)?(this.clearFlowState(),null):r}catch{return this.clearFlowState(),null}}saveFlowState(t,r,i){const o=Date.now(),s=this.getFlowState(),a={flowType:t,flow_id:r,state:i.state,startedAt:(s==null?void 0:s.startedAt)||o,lastUpdatedAt:o,render_spec:i.render_spec};this.storage.setItem(this.FLOW_STATE_KEY,JSON.stringify(a))}restoreFromHistory(t){const r={flowType:t.flowType,flow_id:t.flow_id,state:t.state,startedAt:t.timestamp,lastUpdatedAt:t.timestamp,render_spec:void 0};this.storage.setItem(this.FLOW_STATE_KEY,JSON.stringify(r))}clearFlowState(){this.storage.removeItem(this.FLOW_STATE_KEY)}isExpired(t){return Date.now()-t.lastUpdatedAt>this.FLOW_EXPIRY_MS}get expiryMs(){return this.FLOW_EXPIRY_MS}}class Gi{static detect(t,r=window.location){const i=r.pathname.toLowerCase();return i.includes(t.signinUrl)?"signin":i.includes(t.signupUrl)?"signup":"custom"}}class ql{constructor(){this.listeners=new Map}on(t,r){this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(r)}off(t,r){var i;(i=this.listeners.get(t))===null||i===void 0||i.delete(r)}emit(t,r){const i=this.listeners.get(t);i&&i.forEach(o=>{try{o(r)}catch(s){console.error(`[FlowEventBus] Error in event handler for ${t}:`,s)}})}clear(){this.listeners.clear()}clearEvent(t){this.listeners.delete(t)}}class Vd{getNavigationType(){const t=performance.getEntriesByType("navigation");if(t.length>0)return t[0].type;const r=performance.navigation;if(r)switch(r.type){case 1:return"reload";case 2:return"back_forward";default:return"navigate"}return"navigate"}}class Is{constructor(t,r,i=window.location,o,s){this.httpClient=ye.Instance,this.redirectTimeoutId=null,this.eventHandlers=new Map([["switch_to_signin",this.handleSwitchToSignin.bind(this)],["switch_to_signup",this.handleSwitchToSignup.bind(this)]]);const a=Lt.getConfig();if(!a.domain)throw new nt("CONFIG_ERROR","Domain is required");if(!a.appId)throw new nt("CONFIG_ERROR","App ID is required");if(!a.clientSecret)throw new nt("CONFIG_ERROR","Client Secret is required");this.flowId=t,this.location=i,this.flowStateManager=o||new Ml,this.flowEventBus=r||new ql,this.navigationTypeDetector=s||new Vd,yt.Instance.cleanExpiredSession(),yt.Instance.scheduleTokenRefresh()}static init(t,r,i,o,s){return B(this,void 0,void 0,function*(){const a=new Is(t,r,i,o,s),u=yield a.initializeFlowInternal();return{sdk:a,flowType:u.flowType,isResumed:u.isResumed,state:u.state,render_spec:u.render_spec,auth_result:u.auth_result}})}startFlow(t,r){return B(this,void 0,void 0,function*(){if(!r.app_id)throw new nt("VALIDATION_ERROR","Client ID is required");const i=`/v1/auth-flow/${t}/start`;return this.executeFlowRequest(()=>this.httpClient.post(i,r),"START_FLOW_ERROR","Failed to start flow")})}resumeFlow(t){return B(this,void 0,void 0,function*(){const r=`/v1/auth-flow/${t}/resume`;return this.executeFlowRequest(()=>this.httpClient.post(r),"RESUME_FLOW_ERROR","Failed to resume flow")})}processEvent(t,r){return B(this,void 0,void 0,function*(){if(!t.event)throw new nt("VALIDATION_ERROR","Event type is required");const i=this.eventHandlers.get(t.event);return i?yield i():yield this.processServerEvent(t,r)})}executeFlowRequest(t,r,i,o){return B(this,void 0,void 0,function*(){try{const s=yield t();return o&&(yield o(s)),this.processAuthResult(s.auth_result),s.redirection&&this.redirect(s.redirection),s}catch(s){throw s instanceof nt?s:new nt(r,i,s instanceof Error?{originalError:s.message,stack:s.stack}:s)}})}processServerEvent(t,r){return B(this,void 0,void 0,function*(){let i=this.flowStateManager.getFlowState();if(!i&&(console.warn("Flow state expired, restarting flow before processing event"),yield this.initializeFlowInternal(),i=this.flowStateManager.getFlowState(),!i))throw new nt("FLOW_RESTART_ERROR","Failed to restart flow - no flow state after initialization");const o=i.state,s=`/v1/auth-flow/${i.flowType}/events`,a=yield this.executeFlowRequest(()=>this.httpClient.post(s,t),"PROCESS_EVENT_ERROR","Failed to process event",r);if(!a.auth_result){this.flowStateManager.saveFlowState(i.flowType,i.flow_id,a);const u=this.flowStateManager.getFlowState();if(u){const p=o!==a.state;this.flowEventBus.emit("flow:updated",{flowState:u,isNewStep:p})}}return a})}processAuthResult(t){if(t){const r={id_token:t.id_token,access_token:t.access_token,refresh_token:t.refresh_token,expires_in:t.expires_in,expires_at:Date.now()+t.expires_in*1e3};yt.Instance.setCurrentSession(r),yt.Instance.scheduleTokenRefresh(),this.flowStateManager.clearFlowState(),this.reloadPage()}}initializeFlowInternal(){return B(this,void 0,void 0,function*(){const t=Lt.getConfig(),r=Gi.detect(t,this.location);this.validateFlowType(r);const i=this.flowStateManager.getFlowState();return this.shouldResumeFlow(i,r)?yield this.resumeExistingFlow(i,r):yield this.startNewFlow(r)})}validateFlowType(t){if(t==="custom"&&!this.flowId)throw new nt("FLOW_ID_ERROR","Flow ID is required for custom flows")}shouldResumeFlow(t,r){if(!t)return!1;const o=new URLSearchParams(this.location.search).get("resume")==="true",s=this.navigationTypeDetector.getNavigationType();if(s==="navigate"&&!o)return this.flowStateManager.clearFlowState(),console.log(`Fresh navigation detected (type: ${s}), starting new flow`),!1;if(t.flowType!==r)return this.flowStateManager.clearFlowState(),console.log(`Flow type changed from ${t.flowType} to ${r}, starting new flow, flow_id: ${this.flowId}`),!1;if(r==="custom"&&t.flow_id!==this.flowId)return this.flowStateManager.clearFlowState(),console.log(`Custom flow ID changed, starting new flow, flow_id: ${this.flowId}`),!1;const a=o?"explicit resume=true":`navigation type: ${s}`;return console.log(`Resuming existing flow (${a})`),!0}resumeExistingFlow(t,r){return B(this,void 0,void 0,function*(){try{const i=yield this.resumeFlow(r);if(!i.auth_result){this.flowStateManager.saveFlowState(r,t.flow_id,i);const o=this.flowStateManager.getFlowState();o&&this.flowEventBus.emit("flow:resumed",{flowState:o})}return{flowType:r,isResumed:!0,state:i.state,render_spec:i.render_spec,auth_result:i.auth_result}}catch(i){return this.flowStateManager.clearFlowState(),console.warn("Failed to resume flow, starting fresh:",i),yield this.startNewFlow(r)}})}startNewFlow(t){return B(this,void 0,void 0,function*(){const r=Lt.getConfig(),i={app_id:r.appId,client_secret:r.clientSecret,origin_url:this.location.href,signin_url:r.signinUrl,signup_url:r.signupUrl,locale:r.locale||"en",flow_id:this.determineFlowId(t)},o=yield this.startFlow(t,i);if(!o.auth_result){this.flowStateManager.saveFlowState(t,i.flow_id,o);const s=this.flowStateManager.getFlowState();s&&this.flowEventBus.emit("flow:started",{flowState:s})}return{flowType:t,isResumed:!1,state:o.state,render_spec:o.render_spec,auth_result:o.auth_result}})}determineFlowId(t){switch(t){case"signin":return this.flowId||"signin-flow";case"signup":return this.flowId||"signup-flow";case"custom":if(!this.flowId)throw new nt("FLOW_ID_ERROR","Flow ID is required for custom flows");return this.flowId}}handleSwitchToSignin(){return B(this,void 0,void 0,function*(){const t=Lt.getConfig();return this.redirect({url:t.signinUrl,redirect_delay:0}),{state:"End"}})}handleSwitchToSignup(){return B(this,void 0,void 0,function*(){const t=Lt.getConfig();return this.redirect({url:t.signupUrl,redirect_delay:0}),{state:"End"}})}redirect(t){t.url&&(this.redirectTimeoutId!==null&&(console.log("Clearing existing redirect timeout before scheduling new redirect to:",t.url),clearTimeout(this.redirectTimeoutId),this.redirectTimeoutId=null),this.redirectTimeoutId=setTimeout(()=>{this.location.href=t.url},t.redirect_delay*1e3))}reloadPage(){setTimeout(()=>{this.location.reload()},0)}getEventBus(){return this.flowEventBus}cleanup(){this.redirectTimeoutId&&(clearTimeout(this.redirectTimeoutId),this.redirectTimeoutId=null),this.flowEventBus.clear()}}class Wd{constructor(t){this.delayMs=t,this.timerId=null}debounce(t){this.timerId&&clearTimeout(this.timerId),this.timerId=setTimeout(()=>{t(),this.timerId=null},this.delayMs)}cancel(){this.timerId&&(clearTimeout(this.timerId),this.timerId=null)}isPending(){return this.timerId!==null}}class Gd{pushState(t,r,i){history.pushState(t,r,i)}replaceState(t,r,i){history.replaceState(t,r,i)}get state(){return history.state}}class Kd{constructor(t,r,i=!1,o=window.location,s,a){this.popstateListener=null,this.pageshowListener=null,this.isProcessing=!1,this.isHandlingNavigationEvent=!1,this.onFlowRestartNeeded=null,this.onFlowRestartNeeded=r,this.skipHistoryTracking=i,this.debouncer=new Wd(200),this.location=o,this.flowStateManager=s||new Ml,this.historyAdapter=a||new Gd,this.subscribeToFlowEvents(t),this.initializeListener()}subscribeToFlowEvents(t){t.on("flow:started",this.handleFlowStart.bind(this)),t.on("flow:updated",this.handleFlowUpdate.bind(this)),t.on("flow:resumed",this.handleFlowResume.bind(this))}initializeListener(){this.popstateListener=t=>{console.log("[NavigationManager] popstate event received"),this.debouncer.debounce(()=>{console.log("[NavigationManager] Debounce timeout fired, handling popstate"),this.handlePopState(t)})},this.pageshowListener=t=>{t.persisted&&(console.log("[NavigationManager] Page restored from bfcache"),this.debouncer.debounce(()=>{console.log("[NavigationManager] Handling bfcache restoration"),this.handleBfcacheRestoration()}))},window.addEventListener("popstate",this.popstateListener),window.addEventListener("pageshow",this.pageshowListener)}handlePopState(t){return B(this,void 0,void 0,function*(){if(this.isProcessing){console.log("[NavigationManager] Already processing popstate, skipping");return}console.log("[NavigationManager] Starting popstate processing"),this.isProcessing=!0,this.isHandlingNavigationEvent=!0;try{const r=t.state,i=this.flowStateManager.getFlowState(),o=Lt.getConfig(),s=Gi.detect(o,this.location);if(r&&this.restoreHistoryStateToSession(r),this.shouldRestartFlow(r,i,s)){yield this.triggerFlowRestart();return}console.log("[NavigationManager] Valid state restored from history")}finally{this.isProcessing=!1,this.isHandlingNavigationEvent=!1}})}handleBfcacheRestoration(){return B(this,void 0,void 0,function*(){if(this.isProcessing){console.log("[NavigationManager] Already processing bfcache restoration, skipping");return}console.log("[NavigationManager] Starting bfcache restoration processing"),this.isProcessing=!0,this.isHandlingNavigationEvent=!0;try{const t=this.historyAdapter.state,r=this.flowStateManager.getFlowState(),i=Lt.getConfig(),o=Gi.detect(i,this.location);if(t&&this.restoreHistoryStateToSession(t),this.shouldRestartFlow(t,r,o)){yield this.triggerFlowRestart();return}console.log("[NavigationManager] Valid state restored from bfcache")}finally{this.isProcessing=!1,this.isHandlingNavigationEvent=!1}})}restoreHistoryStateToSession(t){console.log("[NavigationManager] Restoring history state to session storage:",t),this.flowStateManager.restoreFromHistory(t)}shouldRestartFlow(t,r,i){return t&&t.flowType!==i?(console.log("[NavigationManager] Flow type changed via navigation:",t.flowType,"→",i),this.flowStateManager.clearFlowState(),!0):t?Date.now()-t.timestamp>this.flowStateManager.expiryMs?(console.log("[NavigationManager] History state expired, clearing and restarting flow"),this.flowStateManager.clearFlowState(),!0):(console.log("[NavigationManager] Valid history state restored, triggering restart to resume flow"),!0):(console.log("[NavigationManager] No history state, skipping"),!1)}triggerFlowRestart(){return B(this,void 0,void 0,function*(){this.onFlowRestartNeeded&&(yield this.onFlowRestartNeeded())})}createHistoryState(t){return{flowType:t.flowType,flow_id:t.flow_id,state:t.state,timestamp:Date.now()}}updateBrowserHistory(t,r){if(this.skipHistoryTracking){console.log("[NavigationManager] Skipping history tracking (created from navigation)");return}const i=this.createHistoryState(t);try{r==="push"?(this.historyAdapter.pushState(i,"",this.location.href),console.log("[NavigationManager] Pushed state to history")):(this.historyAdapter.replaceState(i,"",this.location.href),console.log("[NavigationManager] Replaced state in history"))}catch(o){console.error("[NavigationManager] Failed to update history:",o)}}handleFlowStart(t){this.updateBrowserHistory(t.flowState,"replace")}handleFlowUpdate(t){const r=this.isHandlingNavigationEvent||!t.isNewStep?"replace":"push";this.updateBrowserHistory(t.flowState,r)}handleFlowResume(t){this.updateBrowserHistory(t.flowState,"replace")}destroy(){this.debouncer.cancel(),this.popstateListener&&(window.removeEventListener("popstate",this.popstateListener),this.popstateListener=null),this.pageshowListener&&(window.removeEventListener("pageshow",this.pageshowListener),this.pageshowListener=null),this.onFlowRestartNeeded=null}}let ke=new AbortController;class Yd{constructor(){this.httpClient=ye.Instance,this.capabilities={webauthn:!1},this.capabilitiesDetected=!1}detectCapabilities(){return B(this,void 0,void 0,function*(){if(!this.capabilitiesDetected)try{if(typeof window.PublicKeyCredential=="function"?this.capabilities.webauthn=!0:this.capabilities.webauthn=!1,typeof PublicKeyCredential.getClientCapabilities=="function"){const t=yield PublicKeyCredential.getClientCapabilities();this.capabilities=Object.assign(Object.assign({},this.capabilities),t);return}typeof PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable=="function"&&(this.capabilities.userVerifyingPlatformAuthenticator=yield PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()),(PublicKeyCredential==null?void 0:PublicKeyCredential.isConditionalMediationAvailable)!==void 0&&typeof PublicKeyCredential.isConditionalMediationAvailable=="function"?this.capabilities.conditionalGet=yield PublicKeyCredential.isConditionalMediationAvailable():this.capabilities.conditionalGet=!1}finally{this.capabilitiesDetected=!0}})}fetchPublicKeyCredentialRequestOptions(t,r){return B(this,void 0,void 0,function*(){try{return this.httpClient.post("/webauthn/login/options",{conditional:r,flow_type:t})}catch(i){return console.error("error",i),null}})}fetchPublicKeyCredentialCreationOptions(t,r){return B(this,void 0,void 0,function*(){try{return this.httpClient.post("/webauthn/register/options",{conditional:r,flow_type:t})}catch(i){return console.error("error",i),null}})}startRegistration(t,r){return B(this,void 0,void 0,function*(){if(yield this.detectCapabilities(),!t)return console.error("No PublicKeyCredentialCreationOptionsJSON provided"),null;ke.abort(),ke=new AbortController;const i=this.toPublicKeyCredentialCreationOptions(t),o={signal:ke.signal,publicKey:i};if(r){if(!this.isConditionalCreateAvailable())return console.error("Conditional create mediation not supported in this browser"),null;o.mediation="conditional"}try{const s=yield navigator.credentials.create(o);if(!s){console.error("no credential returned by authenticator");return}const{id:a,rawId:u,response:p,type:g}=s;let y;typeof p.getTransports=="function"&&(y=p.getTransports());let w;if(typeof p.getPublicKeyAlgorithm=="function")try{w=p.getPublicKeyAlgorithm()}catch(_){$o("getPublicKeyAlgorithm()",_)}let C;if(typeof p.getPublicKey=="function")try{const _=p.getPublicKey();_!==null&&(C=this.bufferToBase64URLString(_))}catch(_){$o("getPublicKey()",_)}let $;if(typeof p.getAuthenticatorData=="function")try{$=this.bufferToBase64URLString(p.getAuthenticatorData())}catch(_){$o("getAuthenticatorData()",_)}return{id:a,rawId:this.bufferToBase64URLString(u),response:{attestationObject:this.bufferToBase64URLString(p.attestationObject),clientDataJSON:this.bufferToBase64URLString(p.clientDataJSON),transports:y,publicKeyAlgorithm:w,publicKey:C,authenticatorData:$},type:g,clientExtensionResults:s.getClientExtensionResults(),authenticatorAttachment:this.toAuthenticatorAttachment(s.authenticatorAttachment)}}catch(s){const a=this.identifyAuthenticationError({error:s,options:o});return a instanceof Re?console.error(`WebAuthn authentication failed with code: ${a.code}, message: ${a.message}`):console.error("WebAuthn authentication failed with error:",s),null}})}startAuthentication(t,r){return B(this,void 0,void 0,function*(){if(yield this.detectCapabilities(),!t)return console.error("No PublicKeyCredentialRequestOptionsJSON provided"),null;if(!this.isWebAuthnAvailable())return console.error("WebAuthn not supported in this browser"),null;ke.abort(),ke=new AbortController;const i=this.toPublicKeyCredentialRequestOptions(t),o={signal:ke.signal,publicKey:i};if(r){if(!this.isConditionalGetAvailable())return console.error("Conditional mediation not supported in this browser"),null;o.mediation="conditional",o.publicKey&&(o.publicKey.allowCredentials=[])}try{const s=yield navigator.credentials.get(o);return this.toPublicKeyCredentialJSON(s)}catch(s){const a=this.identifyAuthenticationError({error:s,options:o});return a instanceof Re?console.error(`WebAuthn authentication failed with code: ${a.code}, message: ${a.message}`):console.error("WebAuthn authentication failed with error:",s),null}})}signalUnknownCredential(t,r){return B(this,void 0,void 0,function*(){if(yield this.detectCapabilities(),this.isSignalUnknownCredentialAvailable())try{yield PublicKeyCredential.signalUnknownCredential({rpId:t,credentialId:r})}catch(i){console.error("Failed to signal unknown credential:",i)}else console.warn("signalUnknownCredential not supported in this browser")})}signalCurrentUserDetails(t){return B(this,void 0,void 0,function*(){if(yield this.detectCapabilities(),this.isSignalCurrentUserDetailsAvailable())try{yield PublicKeyCredential.signalCurrentUserDetails(t)}catch(r){console.error("Failed to signal current user details:",r)}else console.warn("signalCurrentUserDetails not supported. Ask user to update manually.")})}toPublicKeyCredentialDescriptor(t){const{id:r}=t;return Object.assign(Object.assign({},t),{id:this.base64URLStringToBuffer(r),type:t.type,transports:t.transports})}toAuthenticatorAttachment(t){const r=["cross-platform","platform"];if(t&&!(r.indexOf(t)<0))return t}bufferToBase64URLString(t){const r=new Uint8Array(t);let i="";for(const s of r)i+=String.fromCharCode(s);return btoa(i).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}base64URLStringToBuffer(t){const r=t.replace(/-/g,"+").replace(/_/g,"/"),i=(4-r.length%4)%4,o=r.padEnd(r.length+i,"="),s=atob(o),a=new ArrayBuffer(s.length),u=new Uint8Array(a);for(let p=0;p<s.length;p++)u[p]=s.charCodeAt(p);return a}toPublicKeyCredentialJSON(t){if(typeof t.toJSON=="function")return t.toJSON();const r=t.response;return{id:t.id,type:t.type,rawId:this.bufferToBase64URLString(t.rawId),authenticatorAttachment:this.toAuthenticatorAttachment(t.authenticatorAttachment),clientExtensionResults:t==null?void 0:t.getClientExtensionResults(),response:{clientDataJSON:this.bufferToBase64URLString(r.clientDataJSON),authenticatorData:this.bufferToBase64URLString(r.authenticatorData),signature:this.bufferToBase64URLString(r.signature),userHandle:r.userHandle?this.bufferToBase64URLString(r.userHandle):void 0}}}toPublicKeyCredentialCreationOptions(t){var r;if(typeof PublicKeyCredential.parseCreationOptionsFromJSON=="function")return PublicKeyCredential.parseCreationOptionsFromJSON(t);const i={challenge:this.base64URLStringToBuffer(t.challenge),rp:{id:t.rp.id,name:t.rp.name},user:{displayName:t.user.displayName,id:this.base64URLStringToBuffer(t.user.id),name:t.user.name},pubKeyCredParams:t.pubKeyCredParams.map(o=>({alg:o.alg,type:o.type})),timeout:t.timeout};if(t.attestation&&(i.attestation=t.attestation),t.extensions!==void 0&&(i.extensions=t.extensions),t.authenticatorSelection){const o={};t.authenticatorSelection.authenticatorAttachment&&(o.authenticatorAttachment=t.authenticatorSelection.authenticatorAttachment),t.authenticatorSelection.requireResidentKey&&(o.requireResidentKey=t.authenticatorSelection.requireResidentKey),t.authenticatorSelection.residentKey&&(o.residentKey=t.authenticatorSelection.residentKey),t.authenticatorSelection.userVerification&&(o.userVerification=t.authenticatorSelection.userVerification),i.authenticatorSelection=o}return!((r=t.excludeCredentials)===null||r===void 0)&&r.length&&(i.excludeCredentials=t.excludeCredentials.map(o=>this.toPublicKeyCredentialDescriptor(o))),i}toPublicKeyCredentialRequestOptions(t){var r;if(typeof PublicKeyCredential.parseRequestOptionsFromJSON=="function")return PublicKeyCredential.parseRequestOptionsFromJSON(t);const i={challenge:this.base64URLStringToBuffer(t.challenge)};return t.rpId!==void 0&&(i.rpId=t.rpId),t.timeout!==void 0&&(i.timeout=t.timeout),t.userVerification!==void 0&&(i.userVerification=t.userVerification),t.extensions!==void 0&&(i.extensions=t.extensions),!((r=t.allowCredentials)===null||r===void 0)&&r.length&&(i.allowCredentials=t.allowCredentials.map(o=>this.toPublicKeyCredentialDescriptor(o))),i}identifyAuthenticationError({error:t,options:r}){const{publicKey:i}=r;if(!i)throw Error("options was missing required publicKey property");if(t.name==="AbortError"){if(r.signal instanceof AbortSignal)return new Re({message:"Authentication ceremony was sent an abort signal",code:"ERROR_CEREMONY_ABORTED",cause:t})}else{if(t.name==="NotAllowedError")return new Re({message:t.message,code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:t});if(t.name==="SecurityError"){const o=window.location.hostname;if(Xd(o)){if(i.rpId!==o)return new Re({message:`The RP ID "${i.rpId}" is invalid for this domain`,code:"ERROR_INVALID_RP_ID",cause:t})}else return new Re({message:`${window.location.hostname} is an invalid domain`,code:"ERROR_INVALID_DOMAIN",cause:t})}else if(t.name==="UnknownError")return new Re({message:"The authenticator was unable to process the specified options, or could not create a new assertion signature",code:"ERROR_AUTHENTICATOR_GENERAL_ERROR",cause:t})}return t}isPublicKeyCredentialRequestOptionsJSON(t){return typeof t=="object"&&t!==null&&"challenge"in t}isWebAuthnAvailable(){return this.capabilities.webauthn}isSignalAllAcceptedCredentialsAvailable(){return!!this.capabilities.signalAllAcceptedCredentials}isSignalCurrentUserDetailsAvailable(){return!!this.capabilities.signalCurrentUserDetails}isSignalUnknownCredentialAvailable(){return!!this.capabilities.signalUnknownCredential}isConditionalGetAvailable(){return!!this.capabilities.conditionalGet}isConditionalCreateAvailable(){return!!this.capabilities.conditionalCreate}isHybridTransportAvailable(){return!!this.capabilities.hybridTransport}destroy(){ke.abort()}}function $o(e,t){console.warn(`The browser extension that intercepted this WebAuthn API call incorrectly implemented ${e}. You should report this error to them.
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function B(e,t,r,i){function o(s){return s instanceof r?s:new r(function(a){a(s)})}return new(r||(r=Promise))(function(s,a){function u(y){try{g(i.next(y))}catch(w){a(w)}}function p(y){try{g(i.throw(y))}catch(w){a(w)}}function g(y){y.done?s(y.value):o(y.value).then(u,p)}g((i=i.apply(e,t||[])).next())})}class wr{constructor(){this.data=new Map}static get Instance(){return wr.instance||(wr.instance=new wr),wr.instance}get(t){return this.data.get(t)}set(t,r){this.data.set(t,r)}setConfig(t){this.data.set("config",t)}getConfig(){return this.data.get("config")}getApiBaseUrl(){const t=this.getConfig().domain;return t.startsWith("localhost")?`http://${t}`:`https://${t}`}remove(t){this.data.delete(t)}clear(){this.data.clear()}}const Lt=wr.Instance;class nt extends Error{constructor(t,r,i){super(r),this.name="LoginflowError",this.code=t,this.details=i}}class ye{constructor(){}static get Instance(){return ye.instance||(ye.instance=new ye),ye.instance}request(t){return B(this,arguments,void 0,function*(r,i={}){const o=Lt.getConfig(),s=`${Lt.getApiBaseUrl()}${r}`,a=5e4,u=new AbortController,p=setTimeout(()=>u.abort(),a);try{const g=yield fetch(s,Object.assign(Object.assign({},i),{signal:u.signal,headers:Object.assign(Object.assign(Object.assign({"Content-Type":"application/json"},o.apiKey&&{Authorization:`Bearer ${o.apiKey}`}),o.headers),i.headers)}));if(clearTimeout(p),!g.ok){const y=yield g.json().catch(()=>({}));throw new nt(y.code||"API_ERROR",y.message||`HTTP ${g.status}: ${g.statusText}`,y.details)}return yield g.json()}catch(g){throw clearTimeout(p),g instanceof nt?g:g instanceof Error?g.name==="AbortError"?new nt("TIMEOUT","Request timeout"):new nt("NETWORK_ERROR",g.message):new nt("UNKNOWN_ERROR","An unknown error occurred")}})}post(t,r){return B(this,void 0,void 0,function*(){return this.request(t,{credentials:"include",method:"POST",body:r?JSON.stringify(r):void 0})})}get(t){return B(this,void 0,void 0,function*(){return this.request(t,{credentials:"include",method:"GET"})})}}class yt{constructor(){this.AUTH_SESSION_STORAGE_KEY="saasbase_auth_session",this.refreshTimeoutId=null,this.refreshPromise=null}static get Instance(){return yt.instance||(yt.instance=new yt),yt.instance}setCurrentSession(t){localStorage.setItem(this.AUTH_SESSION_STORAGE_KEY,JSON.stringify(t))}getCurrentSession(){const t=this.getStoredSession();return t?this.isSessionExpired(t)?(this.clearSession(),null):(this.shouldRefreshToken(t)&&this.triggerBackgroundRefresh(t),t):null}isAuthenticated(){return this.getCurrentSession()!==null}getAuthenticatedUser(){const t=this.getCurrentSession();if(!t||!t.id_token)return null;const r=this.decodeJWT(t.id_token);return r||null}cleanup(){this.refreshTimeoutId&&(clearTimeout(this.refreshTimeoutId),this.refreshTimeoutId=null),this.refreshPromise=null}clearSession(){localStorage.removeItem(this.AUTH_SESSION_STORAGE_KEY)}isSessionExpired(t){return!t||!t.expires_at?!0:Date.now()>=t.expires_at}cleanExpiredSession(){const t=this.getStoredSession();t&&this.isSessionExpired(t)&&this.clearSession()}getStoredSession(){const t=localStorage.getItem(this.AUTH_SESSION_STORAGE_KEY);if(!t)return null;try{return JSON.parse(t)}catch{return this.clearSession(),null}}decodeJWT(t){try{const i=t.split(".")[1].replace(/-/g,"+").replace(/_/g,"/"),o=decodeURIComponent(atob(i).split("").map(s=>"%"+("00"+s.charCodeAt(0).toString(16)).slice(-2)).join(""));return JSON.parse(o)}catch{return null}}shouldRefreshToken(t){if(!t||!t.expires_at||!t.refresh_token)return!1;const r=Date.now(),i=t.expires_at,o=t.expires_in||3600,s=r+o*1e3*.2;return i<=s}scheduleTokenRefresh(){this.refreshTimeoutId&&(clearTimeout(this.refreshTimeoutId),this.refreshTimeoutId=null);const t=this.getCurrentSession();if(!t||!t.expires_at||!t.refresh_token)return;const r=Date.now(),i=t.expires_at,o=t.expires_in||3600,s=i-r-o*1e3*.2;s>0&&(this.refreshTimeoutId=setTimeout(()=>{this.triggerBackgroundRefresh(t)},s))}triggerBackgroundRefresh(t){this.refreshPromise||t.refresh_token&&(this.refreshPromise=this.refreshTokens(t.refresh_token).then(r=>(r&&(this.setCurrentSession(r),this.scheduleTokenRefresh()),r)).catch(r=>(console.error("Background token refresh failed:",r),null)).finally(()=>{this.refreshPromise=null}))}refreshTokens(t){return B(this,void 0,void 0,function*(){try{const r=yield ye.Instance.post("/login-flow/v1/refresh",{refresh_token:t});return{id_token:r.id_token,access_token:r.access_token,refresh_token:r.refresh_token,expires_in:r.expires_in,expires_at:Date.now()+r.expires_in*1e3}}catch(r){return r instanceof nt?console.error("Token refresh failed:",r.message,r.details):console.error("Token refresh failed:",r),this.clearSession(),null}})}}class Ml{constructor(t=sessionStorage){this.storage=t,this.FLOW_STATE_KEY="saasbase_loginflow_state",this.FLOW_EXPIRY_MS=540*1e3}getFlowState(){const t=this.storage.getItem(this.FLOW_STATE_KEY);if(!t)return null;try{const r=JSON.parse(t);return this.isExpired(r)?(this.clearFlowState(),null):r}catch{return this.clearFlowState(),null}}saveFlowState(t,r,i){const o=Date.now(),s=this.getFlowState(),a={flowType:t,flow_id:r,state:i.state,startedAt:(s==null?void 0:s.startedAt)||o,lastUpdatedAt:o,render_spec:i.render_spec};this.storage.setItem(this.FLOW_STATE_KEY,JSON.stringify(a))}restoreFromHistory(t){const r={flowType:t.flowType,flow_id:t.flow_id,state:t.state,startedAt:t.timestamp,lastUpdatedAt:t.timestamp,render_spec:void 0};this.storage.setItem(this.FLOW_STATE_KEY,JSON.stringify(r))}clearFlowState(){this.storage.removeItem(this.FLOW_STATE_KEY)}isExpired(t){return Date.now()-t.lastUpdatedAt>this.FLOW_EXPIRY_MS}get expiryMs(){return this.FLOW_EXPIRY_MS}}class Gi{static detect(t,r=window.location){const i=r.pathname.toLowerCase();return i.includes(t.signinUrl)?"signin":i.includes(t.signupUrl)?"signup":"custom"}}class ql{constructor(){this.listeners=new Map}on(t,r){this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(r)}off(t,r){var i;(i=this.listeners.get(t))===null||i===void 0||i.delete(r)}emit(t,r){const i=this.listeners.get(t);i&&i.forEach(o=>{try{o(r)}catch(s){console.error(`[FlowEventBus] Error in event handler for ${t}:`,s)}})}clear(){this.listeners.clear()}clearEvent(t){this.listeners.delete(t)}}class Vd{getNavigationType(){const t=performance.getEntriesByType("navigation");if(t.length>0)return t[0].type;const r=performance.navigation;if(r)switch(r.type){case 1:return"reload";case 2:return"back_forward";default:return"navigate"}return"navigate"}}class Is{constructor(t,r,i=window.location,o,s){this.httpClient=ye.Instance,this.redirectTimeoutId=null,this.eventHandlers=new Map([["switch_to_signin",this.handleSwitchToSignin.bind(this)],["switch_to_signup",this.handleSwitchToSignup.bind(this)]]);const a=Lt.getConfig();if(!a.domain)throw new nt("CONFIG_ERROR","Domain is required");if(!a.appId)throw new nt("CONFIG_ERROR","App ID is required");if(!a.clientSecret)throw new nt("CONFIG_ERROR","Client Secret is required");this.flowId=t,this.location=i,this.flowStateManager=o||new Ml,this.flowEventBus=r||new ql,this.navigationTypeDetector=s||new Vd,yt.Instance.cleanExpiredSession(),yt.Instance.scheduleTokenRefresh()}static init(t,r,i,o,s){return B(this,void 0,void 0,function*(){const a=new Is(t,r,i,o,s),u=yield a.initializeFlowInternal();return{sdk:a,flowType:u.flowType,isResumed:u.isResumed,state:u.state,render_spec:u.render_spec,auth_result:u.auth_result}})}startFlow(t,r){return B(this,void 0,void 0,function*(){if(!r.app_id)throw new nt("VALIDATION_ERROR","Client ID is required");const i=`/v1/auth-flow/${t}/start`;return this.executeFlowRequest(()=>this.httpClient.post(i,r),"START_FLOW_ERROR","Failed to start flow")})}resumeFlow(t){return B(this,void 0,void 0,function*(){const r=`/v1/auth-flow/${t}/resume`;return this.executeFlowRequest(()=>this.httpClient.post(r),"RESUME_FLOW_ERROR","Failed to resume flow")})}processEvent(t,r){return B(this,void 0,void 0,function*(){if(!t.event)throw new nt("VALIDATION_ERROR","Event type is required");const i=this.eventHandlers.get(t.event);return i?yield i():yield this.processServerEvent(t,r)})}executeFlowRequest(t,r,i,o){return B(this,void 0,void 0,function*(){try{const s=yield t();return o&&(yield o(s)),this.processAuthResult(s.auth_result),s.redirection&&this.redirect(s.redirection),s}catch(s){throw s instanceof nt?s:new nt(r,i,s instanceof Error?{originalError:s.message,stack:s.stack}:s)}})}processServerEvent(t,r){return B(this,void 0,void 0,function*(){let i=this.flowStateManager.getFlowState();if(!i&&(console.warn("Flow state expired, restarting flow before processing event"),yield this.initializeFlowInternal(),i=this.flowStateManager.getFlowState(),!i))throw new nt("FLOW_RESTART_ERROR","Failed to restart flow - no flow state after initialization");const o=i.state,s=`/v1/auth-flow/${i.flowType}/events`,a=yield this.executeFlowRequest(()=>this.httpClient.post(s,t),"PROCESS_EVENT_ERROR","Failed to process event",r);if(!a.auth_result){this.flowStateManager.saveFlowState(i.flowType,i.flow_id,a);const u=this.flowStateManager.getFlowState();if(u){const p=o!==a.state;this.flowEventBus.emit("flow:updated",{flowState:u,isNewStep:p})}}return a})}processAuthResult(t){if(t){const r={id_token:t.id_token,access_token:t.access_token,refresh_token:t.refresh_token,expires_in:t.expires_in,expires_at:Date.now()+t.expires_in*1e3};yt.Instance.setCurrentSession(r),yt.Instance.scheduleTokenRefresh(),this.flowStateManager.clearFlowState(),this.reloadPage()}}initializeFlowInternal(){return B(this,void 0,void 0,function*(){const t=Lt.getConfig(),r=Gi.detect(t,this.location);this.validateFlowType(r);const i=this.flowStateManager.getFlowState();return this.shouldResumeFlow(i,r)?yield this.resumeExistingFlow(i,r):yield this.startNewFlow(r)})}validateFlowType(t){if(t==="custom"&&!this.flowId)throw new nt("FLOW_ID_ERROR","Flow ID is required for custom flows")}shouldResumeFlow(t,r){if(!t)return!1;const o=new URLSearchParams(this.location.search).get("resume")==="true",s=this.navigationTypeDetector.getNavigationType();if(s==="navigate"&&!o)return this.flowStateManager.clearFlowState(),console.log(`Fresh navigation detected (type: ${s}), starting new flow`),!1;if(t.flowType!==r)return this.flowStateManager.clearFlowState(),console.log(`Flow type changed from ${t.flowType} to ${r}, starting new flow, flow_id: ${this.flowId}`),!1;if(r==="custom"&&t.flow_id!==this.flowId)return this.flowStateManager.clearFlowState(),console.log(`Custom flow ID changed, starting new flow, flow_id: ${this.flowId}`),!1;const a=o?"explicit resume=true":`navigation type: ${s}`;return console.log(`Resuming existing flow (${a})`),!0}resumeExistingFlow(t,r){return B(this,void 0,void 0,function*(){try{const i=yield this.resumeFlow(r);if(!i.auth_result){this.flowStateManager.saveFlowState(r,t.flow_id,i);const o=this.flowStateManager.getFlowState();o&&this.flowEventBus.emit("flow:resumed",{flowState:o})}return{flowType:r,isResumed:!0,state:i.state,render_spec:i.render_spec,auth_result:i.auth_result}}catch(i){return this.flowStateManager.clearFlowState(),console.warn("Failed to resume flow, starting fresh:",i),yield this.startNewFlow(r)}})}startNewFlow(t){return B(this,void 0,void 0,function*(){const r=Lt.getConfig(),i=new URLSearchParams(this.location.search),o={};i.forEach((u,p)=>{o[p]=u}),console.log("metadata",o);const s={app_id:r.appId,client_secret:r.clientSecret,origin_url:this.location.href,signin_url:r.signinUrl,signup_url:r.signupUrl,locale:r.locale||"en",flow_id:this.determineFlowId(t),metadata:o},a=yield this.startFlow(t,s);if(!a.auth_result){this.flowStateManager.saveFlowState(t,s.flow_id,a);const u=this.flowStateManager.getFlowState();u&&this.flowEventBus.emit("flow:started",{flowState:u})}return{flowType:t,isResumed:!1,state:a.state,render_spec:a.render_spec,auth_result:a.auth_result}})}determineFlowId(t){switch(t){case"signin":return this.flowId||"signin-flow";case"signup":return this.flowId||"signup-flow";case"custom":if(!this.flowId)throw new nt("FLOW_ID_ERROR","Flow ID is required for custom flows");return this.flowId}}handleSwitchToSignin(){return B(this,void 0,void 0,function*(){const t=Lt.getConfig();return this.redirect({url:t.signinUrl,redirect_delay:0}),{state:"End"}})}handleSwitchToSignup(){return B(this,void 0,void 0,function*(){const t=Lt.getConfig();return this.redirect({url:t.signupUrl,redirect_delay:0}),{state:"End"}})}redirect(t){t.url&&(this.redirectTimeoutId!==null&&(console.log("Clearing existing redirect timeout before scheduling new redirect to:",t.url),clearTimeout(this.redirectTimeoutId),this.redirectTimeoutId=null),this.redirectTimeoutId=setTimeout(()=>{this.location.href=t.url},t.redirect_delay*1e3))}reloadPage(){setTimeout(()=>{this.location.reload()},0)}getEventBus(){return this.flowEventBus}cleanup(){this.redirectTimeoutId&&(clearTimeout(this.redirectTimeoutId),this.redirectTimeoutId=null),this.flowEventBus.clear()}}class Wd{constructor(t){this.delayMs=t,this.timerId=null}debounce(t){this.timerId&&clearTimeout(this.timerId),this.timerId=setTimeout(()=>{t(),this.timerId=null},this.delayMs)}cancel(){this.timerId&&(clearTimeout(this.timerId),this.timerId=null)}isPending(){return this.timerId!==null}}class Gd{pushState(t,r,i){history.pushState(t,r,i)}replaceState(t,r,i){history.replaceState(t,r,i)}get state(){return history.state}}class Kd{constructor(t,r,i=!1,o=window.location,s,a){this.popstateListener=null,this.pageshowListener=null,this.isProcessing=!1,this.isHandlingNavigationEvent=!1,this.onFlowRestartNeeded=null,this.onFlowRestartNeeded=r,this.skipHistoryTracking=i,this.debouncer=new Wd(200),this.location=o,this.flowStateManager=s||new Ml,this.historyAdapter=a||new Gd,this.subscribeToFlowEvents(t),this.initializeListener()}subscribeToFlowEvents(t){t.on("flow:started",this.handleFlowStart.bind(this)),t.on("flow:updated",this.handleFlowUpdate.bind(this)),t.on("flow:resumed",this.handleFlowResume.bind(this))}initializeListener(){this.popstateListener=t=>{console.log("[NavigationManager] popstate event received"),this.debouncer.debounce(()=>{console.log("[NavigationManager] Debounce timeout fired, handling popstate"),this.handlePopState(t)})},this.pageshowListener=t=>{t.persisted&&(console.log("[NavigationManager] Page restored from bfcache"),this.debouncer.debounce(()=>{console.log("[NavigationManager] Handling bfcache restoration"),this.handleBfcacheRestoration()}))},window.addEventListener("popstate",this.popstateListener),window.addEventListener("pageshow",this.pageshowListener)}handlePopState(t){return B(this,void 0,void 0,function*(){if(this.isProcessing){console.log("[NavigationManager] Already processing popstate, skipping");return}console.log("[NavigationManager] Starting popstate processing"),this.isProcessing=!0,this.isHandlingNavigationEvent=!0;try{const r=t.state,i=this.flowStateManager.getFlowState(),o=Lt.getConfig(),s=Gi.detect(o,this.location);if(r&&this.restoreHistoryStateToSession(r),this.shouldRestartFlow(r,i,s)){yield this.triggerFlowRestart();return}console.log("[NavigationManager] Valid state restored from history")}finally{this.isProcessing=!1,this.isHandlingNavigationEvent=!1}})}handleBfcacheRestoration(){return B(this,void 0,void 0,function*(){if(this.isProcessing){console.log("[NavigationManager] Already processing bfcache restoration, skipping");return}console.log("[NavigationManager] Starting bfcache restoration processing"),this.isProcessing=!0,this.isHandlingNavigationEvent=!0;try{const t=this.historyAdapter.state,r=this.flowStateManager.getFlowState(),i=Lt.getConfig(),o=Gi.detect(i,this.location);if(t&&this.restoreHistoryStateToSession(t),this.shouldRestartFlow(t,r,o)){yield this.triggerFlowRestart();return}console.log("[NavigationManager] Valid state restored from bfcache")}finally{this.isProcessing=!1,this.isHandlingNavigationEvent=!1}})}restoreHistoryStateToSession(t){console.log("[NavigationManager] Restoring history state to session storage:",t),this.flowStateManager.restoreFromHistory(t)}shouldRestartFlow(t,r,i){return t&&t.flowType!==i?(console.log("[NavigationManager] Flow type changed via navigation:",t.flowType,"→",i),this.flowStateManager.clearFlowState(),!0):t?Date.now()-t.timestamp>this.flowStateManager.expiryMs?(console.log("[NavigationManager] History state expired, clearing and restarting flow"),this.flowStateManager.clearFlowState(),!0):(console.log("[NavigationManager] Valid history state restored, triggering restart to resume flow"),!0):(console.log("[NavigationManager] No history state, skipping"),!1)}triggerFlowRestart(){return B(this,void 0,void 0,function*(){this.onFlowRestartNeeded&&(yield this.onFlowRestartNeeded())})}createHistoryState(t){return{flowType:t.flowType,flow_id:t.flow_id,state:t.state,timestamp:Date.now()}}updateBrowserHistory(t,r){if(this.skipHistoryTracking){console.log("[NavigationManager] Skipping history tracking (created from navigation)");return}const i=this.createHistoryState(t);try{r==="push"?(this.historyAdapter.pushState(i,"",this.location.href),console.log("[NavigationManager] Pushed state to history")):(this.historyAdapter.replaceState(i,"",this.location.href),console.log("[NavigationManager] Replaced state in history"))}catch(o){console.error("[NavigationManager] Failed to update history:",o)}}handleFlowStart(t){this.updateBrowserHistory(t.flowState,"replace")}handleFlowUpdate(t){const r=this.isHandlingNavigationEvent||!t.isNewStep?"replace":"push";this.updateBrowserHistory(t.flowState,r)}handleFlowResume(t){this.updateBrowserHistory(t.flowState,"replace")}destroy(){this.debouncer.cancel(),this.popstateListener&&(window.removeEventListener("popstate",this.popstateListener),this.popstateListener=null),this.pageshowListener&&(window.removeEventListener("pageshow",this.pageshowListener),this.pageshowListener=null),this.onFlowRestartNeeded=null}}let ke=new AbortController;class Yd{constructor(){this.httpClient=ye.Instance,this.capabilities={webauthn:!1},this.capabilitiesDetected=!1}detectCapabilities(){return B(this,void 0,void 0,function*(){if(!this.capabilitiesDetected)try{if(typeof window.PublicKeyCredential=="function"?this.capabilities.webauthn=!0:this.capabilities.webauthn=!1,typeof PublicKeyCredential.getClientCapabilities=="function"){const t=yield PublicKeyCredential.getClientCapabilities();this.capabilities=Object.assign(Object.assign({},this.capabilities),t);return}typeof PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable=="function"&&(this.capabilities.userVerifyingPlatformAuthenticator=yield PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()),(PublicKeyCredential==null?void 0:PublicKeyCredential.isConditionalMediationAvailable)!==void 0&&typeof PublicKeyCredential.isConditionalMediationAvailable=="function"?this.capabilities.conditionalGet=yield PublicKeyCredential.isConditionalMediationAvailable():this.capabilities.conditionalGet=!1}finally{this.capabilitiesDetected=!0}})}fetchPublicKeyCredentialRequestOptions(t,r){return B(this,void 0,void 0,function*(){try{return this.httpClient.post("/webauthn/login/options",{conditional:r,flow_type:t})}catch(i){return console.error("error",i),null}})}fetchPublicKeyCredentialCreationOptions(t,r){return B(this,void 0,void 0,function*(){try{return this.httpClient.post("/webauthn/register/options",{conditional:r,flow_type:t})}catch(i){return console.error("error",i),null}})}startRegistration(t,r){return B(this,void 0,void 0,function*(){if(yield this.detectCapabilities(),!t)return console.error("No PublicKeyCredentialCreationOptionsJSON provided"),null;ke.abort(),ke=new AbortController;const i=this.toPublicKeyCredentialCreationOptions(t),o={signal:ke.signal,publicKey:i};if(r){if(!this.isConditionalCreateAvailable())return console.error("Conditional create mediation not supported in this browser"),null;o.mediation="conditional"}try{const s=yield navigator.credentials.create(o);if(!s){console.error("no credential returned by authenticator");return}const{id:a,rawId:u,response:p,type:g}=s;let y;typeof p.getTransports=="function"&&(y=p.getTransports());let w;if(typeof p.getPublicKeyAlgorithm=="function")try{w=p.getPublicKeyAlgorithm()}catch(_){$o("getPublicKeyAlgorithm()",_)}let C;if(typeof p.getPublicKey=="function")try{const _=p.getPublicKey();_!==null&&(C=this.bufferToBase64URLString(_))}catch(_){$o("getPublicKey()",_)}let $;if(typeof p.getAuthenticatorData=="function")try{$=this.bufferToBase64URLString(p.getAuthenticatorData())}catch(_){$o("getAuthenticatorData()",_)}return{id:a,rawId:this.bufferToBase64URLString(u),response:{attestationObject:this.bufferToBase64URLString(p.attestationObject),clientDataJSON:this.bufferToBase64URLString(p.clientDataJSON),transports:y,publicKeyAlgorithm:w,publicKey:C,authenticatorData:$},type:g,clientExtensionResults:s.getClientExtensionResults(),authenticatorAttachment:this.toAuthenticatorAttachment(s.authenticatorAttachment)}}catch(s){const a=this.identifyAuthenticationError({error:s,options:o});return a instanceof Re?console.error(`WebAuthn authentication failed with code: ${a.code}, message: ${a.message}`):console.error("WebAuthn authentication failed with error:",s),null}})}startAuthentication(t,r){return B(this,void 0,void 0,function*(){if(yield this.detectCapabilities(),!t)return console.error("No PublicKeyCredentialRequestOptionsJSON provided"),null;if(!this.isWebAuthnAvailable())return console.error("WebAuthn not supported in this browser"),null;ke.abort(),ke=new AbortController;const i=this.toPublicKeyCredentialRequestOptions(t),o={signal:ke.signal,publicKey:i};if(r){if(!this.isConditionalGetAvailable())return console.error("Conditional mediation not supported in this browser"),null;o.mediation="conditional",o.publicKey&&(o.publicKey.allowCredentials=[])}try{const s=yield navigator.credentials.get(o);return this.toPublicKeyCredentialJSON(s)}catch(s){const a=this.identifyAuthenticationError({error:s,options:o});return a instanceof Re?console.error(`WebAuthn authentication failed with code: ${a.code}, message: ${a.message}`):console.error("WebAuthn authentication failed with error:",s),null}})}signalUnknownCredential(t,r){return B(this,void 0,void 0,function*(){if(yield this.detectCapabilities(),this.isSignalUnknownCredentialAvailable())try{yield PublicKeyCredential.signalUnknownCredential({rpId:t,credentialId:r})}catch(i){console.error("Failed to signal unknown credential:",i)}else console.warn("signalUnknownCredential not supported in this browser")})}signalCurrentUserDetails(t){return B(this,void 0,void 0,function*(){if(yield this.detectCapabilities(),this.isSignalCurrentUserDetailsAvailable())try{yield PublicKeyCredential.signalCurrentUserDetails(t)}catch(r){console.error("Failed to signal current user details:",r)}else console.warn("signalCurrentUserDetails not supported. Ask user to update manually.")})}toPublicKeyCredentialDescriptor(t){const{id:r}=t;return Object.assign(Object.assign({},t),{id:this.base64URLStringToBuffer(r),type:t.type,transports:t.transports})}toAuthenticatorAttachment(t){const r=["cross-platform","platform"];if(t&&!(r.indexOf(t)<0))return t}bufferToBase64URLString(t){const r=new Uint8Array(t);let i="";for(const s of r)i+=String.fromCharCode(s);return btoa(i).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}base64URLStringToBuffer(t){const r=t.replace(/-/g,"+").replace(/_/g,"/"),i=(4-r.length%4)%4,o=r.padEnd(r.length+i,"="),s=atob(o),a=new ArrayBuffer(s.length),u=new Uint8Array(a);for(let p=0;p<s.length;p++)u[p]=s.charCodeAt(p);return a}toPublicKeyCredentialJSON(t){if(typeof t.toJSON=="function")return t.toJSON();const r=t.response;return{id:t.id,type:t.type,rawId:this.bufferToBase64URLString(t.rawId),authenticatorAttachment:this.toAuthenticatorAttachment(t.authenticatorAttachment),clientExtensionResults:t==null?void 0:t.getClientExtensionResults(),response:{clientDataJSON:this.bufferToBase64URLString(r.clientDataJSON),authenticatorData:this.bufferToBase64URLString(r.authenticatorData),signature:this.bufferToBase64URLString(r.signature),userHandle:r.userHandle?this.bufferToBase64URLString(r.userHandle):void 0}}}toPublicKeyCredentialCreationOptions(t){var r;if(typeof PublicKeyCredential.parseCreationOptionsFromJSON=="function")return PublicKeyCredential.parseCreationOptionsFromJSON(t);const i={challenge:this.base64URLStringToBuffer(t.challenge),rp:{id:t.rp.id,name:t.rp.name},user:{displayName:t.user.displayName,id:this.base64URLStringToBuffer(t.user.id),name:t.user.name},pubKeyCredParams:t.pubKeyCredParams.map(o=>({alg:o.alg,type:o.type})),timeout:t.timeout};if(t.attestation&&(i.attestation=t.attestation),t.extensions!==void 0&&(i.extensions=t.extensions),t.authenticatorSelection){const o={};t.authenticatorSelection.authenticatorAttachment&&(o.authenticatorAttachment=t.authenticatorSelection.authenticatorAttachment),t.authenticatorSelection.requireResidentKey&&(o.requireResidentKey=t.authenticatorSelection.requireResidentKey),t.authenticatorSelection.residentKey&&(o.residentKey=t.authenticatorSelection.residentKey),t.authenticatorSelection.userVerification&&(o.userVerification=t.authenticatorSelection.userVerification),i.authenticatorSelection=o}return!((r=t.excludeCredentials)===null||r===void 0)&&r.length&&(i.excludeCredentials=t.excludeCredentials.map(o=>this.toPublicKeyCredentialDescriptor(o))),i}toPublicKeyCredentialRequestOptions(t){var r;if(typeof PublicKeyCredential.parseRequestOptionsFromJSON=="function")return PublicKeyCredential.parseRequestOptionsFromJSON(t);const i={challenge:this.base64URLStringToBuffer(t.challenge)};return t.rpId!==void 0&&(i.rpId=t.rpId),t.timeout!==void 0&&(i.timeout=t.timeout),t.userVerification!==void 0&&(i.userVerification=t.userVerification),t.extensions!==void 0&&(i.extensions=t.extensions),!((r=t.allowCredentials)===null||r===void 0)&&r.length&&(i.allowCredentials=t.allowCredentials.map(o=>this.toPublicKeyCredentialDescriptor(o))),i}identifyAuthenticationError({error:t,options:r}){const{publicKey:i}=r;if(!i)throw Error("options was missing required publicKey property");if(t.name==="AbortError"){if(r.signal instanceof AbortSignal)return new Re({message:"Authentication ceremony was sent an abort signal",code:"ERROR_CEREMONY_ABORTED",cause:t})}else{if(t.name==="NotAllowedError")return new Re({message:t.message,code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:t});if(t.name==="SecurityError"){const o=window.location.hostname;if(Xd(o)){if(i.rpId!==o)return new Re({message:`The RP ID "${i.rpId}" is invalid for this domain`,code:"ERROR_INVALID_RP_ID",cause:t})}else return new Re({message:`${window.location.hostname} is an invalid domain`,code:"ERROR_INVALID_DOMAIN",cause:t})}else if(t.name==="UnknownError")return new Re({message:"The authenticator was unable to process the specified options, or could not create a new assertion signature",code:"ERROR_AUTHENTICATOR_GENERAL_ERROR",cause:t})}return t}isPublicKeyCredentialRequestOptionsJSON(t){return typeof t=="object"&&t!==null&&"challenge"in t}isWebAuthnAvailable(){return this.capabilities.webauthn}isSignalAllAcceptedCredentialsAvailable(){return!!this.capabilities.signalAllAcceptedCredentials}isSignalCurrentUserDetailsAvailable(){return!!this.capabilities.signalCurrentUserDetails}isSignalUnknownCredentialAvailable(){return!!this.capabilities.signalUnknownCredential}isConditionalGetAvailable(){return!!this.capabilities.conditionalGet}isConditionalCreateAvailable(){return!!this.capabilities.conditionalCreate}isHybridTransportAvailable(){return!!this.capabilities.hybridTransport}destroy(){ke.abort()}}function $o(e,t){console.warn(`The browser extension that intercepted this WebAuthn API call incorrectly implemented ${e}. You should report this error to them.
|
|
2
2
|
`,t)}class Re extends Error{constructor({message:t,code:r,cause:i,name:o}){super(t),this.name=o??i.name,this.code=r}}function Xd(e){return e==="localhost"||/^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i.test(e)}class xr{constructor(){this.configured=!1,this.loginflow=null,this.navigationManager=null,this.flowEventBus=null,this.webauthnService=null}static get Instance(){return xr.instance||(xr.instance=new xr),xr.instance}ensureConfigured(){if(!this.configured)throw new nt("NOT_CONFIGURED","Auth.configure() must be called before using Auth methods")}ensureLoginflowInitialized(){if(!this.loginflow)throw new nt("LOGINFLOW_NOT_INITIALIZED","Call Auth.startLoginflow() first")}startLoginflowInternal(t){return B(this,arguments,void 0,function*(r,i=!1){this.ensureConfigured(),this.webauthnService&&(console.log("[AuthService] Cleaning up and aborting ongoing webauthn flows"),this.webauthnService.destroy(),this.webauthnService=null),this.loginflow&&(console.log("[AuthService] Cleaning up old loginflow instance"),this.loginflow.cleanup(),this.loginflow=null),this.navigationManager&&(console.log("[AuthService] Cleaning up old navigation manager instance"),this.navigationManager.destroy(),this.navigationManager=null),this.webauthnService=new Yd,this.webauthnService.detectCapabilities(),this.flowEventBus=new ql,this.navigationManager=new Kd(this.flowEventBus,()=>B(this,void 0,void 0,function*(){console.log("[AuthService] Navigation restart triggered, restarting loginflow"),yield this.startLoginflowInternal(void 0,!1)}),i);const o=yield Is.init(r,this.flowEventBus),s=Lt.getConfig();this.startPasskeyAuthentication(o.flowType,!0),this.loginflow=o.sdk;const a={flowType:o.flowType,isResumed:o.isResumed,state:o.state,render_spec:o.render_spec,auth_result:o.auth_result};return s.onFlowStateChange&&s.onFlowStateChange({eventType:o.isResumed?"flow_resumed":"flow_started",flowType:o.flowType,state:o.state,render_spec:o.render_spec,auth_result:o.auth_result,isResumed:o.isResumed}),a})}configure(t){if(!t.domain||!t.appId||!t.clientSecret)throw new nt("CONFIG_ERROR","domain, appId, and clientSecret are required");Lt.setConfig(t),this.configured=!0}isAuthenticated(){return this.ensureConfigured(),yt.Instance.isAuthenticated()}getCurrentSession(){return this.ensureConfigured(),yt.Instance.getCurrentSession()}getAuthenticatedUser(){return this.ensureConfigured(),yt.Instance.getAuthenticatedUser()}startLoginflow(t){return B(this,void 0,void 0,function*(){return this.startLoginflowInternal(t,!1)})}processLoginflowEvent(t){return B(this,void 0,void 0,function*(){var r,i;this.ensureConfigured(),this.ensureLoginflowInitialized();const o=Lt.getConfig(),s=Gi.detect(o,location);if(t.event==="signin_with_passkey"&&!(!((r=t.data)===null||r===void 0)&&r.credential_request)){const u=yield this.getPasskeyCredential(s,!1);if(!u||!u.cred)return console.error("no credential returned by browser"),{state:""};t.data={credential_request:u.cred}}if(t.event==="register_passkey"&&!(!((i=t.data)===null||i===void 0)&&i.conditional)){console.log("attempt to register a passkey");const u=yield this.createPasskeyCredential(s,!1);if(!u)return console.error("no credential created by browser"),{state:""};t.data={credential_creation_response:u,conditional:!1}}const a=yield this.loginflow.processEvent(t,u=>B(this,void 0,void 0,function*(){var p;if(!((p=u.data)===null||p===void 0)&&p.conditional_create_enabled){console.log("attempt to register a passkey conditionally");const g=yield this.registerPasskey(s,!0);g&&(g.render_spec=void 0,u.render_spec=void 0)}}));if(console.log("render spec",a.render_spec),o.onFlowStateChange){const u=sessionStorage.getItem("saasbase_loginflow_state");let p="custom";if(u)try{p=JSON.parse(u).flowType}catch(g){console.error("Failed to parse flow state:",g)}o.onFlowStateChange({eventType:"flow_updated",flowType:p,state:a.state,render_spec:a.render_spec,auth_result:a.auth_result})}return a})}getPasskeyCredential(t,r){return B(this,void 0,void 0,function*(){var i,o;const s=yield(i=this.webauthnService)===null||i===void 0?void 0:i.fetchPublicKeyCredentialRequestOptions(t,r);if(!s)return console.error("failed to fetch public key request options"),null;try{return{cred:yield(o=this.webauthnService)===null||o===void 0?void 0:o.startAuthentication(s,r),options:s}}catch(a){return console.error("error starting authentication",a),null}})}startPasskeyAuthentication(t){return B(this,arguments,void 0,function*(r,i=!1){var o,s;const a=yield this.getPasskeyCredential(r,i);if(!a||!a.cred){console.error("no credential found");return}const{cred:u,options:p}=a;try{((o=(yield this.processLoginflowEvent({event:"signin_with_passkey",data:{credential_request:u}})).data)===null||o===void 0?void 0:o.error)==="CredentialNotFound"&&(p.rpId&&u.id?(s=this.webauthnService)===null||s===void 0||s.signalUnknownCredential(p.rpId,u.id):console.log("missing rpId or credential id for signalUnknownCredential"))}catch(g){console.error("failed to signin with passkey",g)}})}createPasskeyCredential(t,r){return B(this,void 0,void 0,function*(){var i,o;try{const s=yield(i=this.webauthnService)===null||i===void 0?void 0:i.fetchPublicKeyCredentialCreationOptions(t,r);if(!s){console.warn("no credential creation option returned by webauthn service"),console.log("skipping conditional passkey registration...");return}return(o=this.webauthnService)===null||o===void 0?void 0:o.startRegistration(s,r)}catch(s){console.log("error",s)}})}registerPasskey(t){return B(this,arguments,void 0,function*(r,i=!1){try{const o=yield this.createPasskeyCredential(r,i),s=yield this.processLoginflowEvent({event:"register_passkey",data:{credential_creation_response:o,conditional:i}});return console.log("passkey creation res",s),s}catch(o){console.log("error",o)}})}signOut(){this.ensureConfigured(),yt.Instance.clearSession(),yt.Instance.cleanup(),this.loginflow&&(this.loginflow.cleanup(),this.loginflow=null),this.navigationManager&&(this.navigationManager.destroy(),this.navigationManager=null),this.flowEventBus&&(this.flowEventBus.clear(),this.flowEventBus=null),window.location.reload(),console.log("Refreshed the page to clear any in-memory state")}}const gt=xr.Instance;/**
|
|
3
3
|
* @license
|
|
4
4
|
* Copyright 2019 Google LLC
|