@mapnests/gateway-web-sdk 1.0.7 → 1.0.9

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/README.md CHANGED
@@ -10,7 +10,8 @@ A lightweight, production-ready session token management SDK for React and Next.
10
10
  - Singleton pattern ensuring a single session instance across your app
11
11
  - Zero runtime dependencies (`react` and `react-dom` as peer dependencies)
12
12
  - Next.js compatible (SSR-safe)
13
- - Built-in Fetch and Axios interceptors for automatic 401 handling
13
+ - Built-in Fetch and Axios interceptors with automatic session readiness gating and 401 handling
14
+ - Resilient to browser close/reopen — stale cookies are cleared and tokens refresh before any API call
14
15
  - Full TypeScript definitions included
15
16
 
16
17
  ## Installation
@@ -210,6 +211,9 @@ import { API_BASE_URL } from './config.js';
210
211
  const sm = SessionManager.getInstance();
211
212
 
212
213
  async function request(url, init = {}) {
214
+ // Ensure session is bootstrapped before sending (handles browser close/reopen, tab wake, etc.)
215
+ await sm.ensureReady();
216
+
213
217
  const opts = {
214
218
  ...init,
215
219
  credentials: 'include',
@@ -227,20 +231,17 @@ async function request(url, init = {}) {
227
231
 
228
232
  let res = await fetch(url, opts);
229
233
 
230
- // Handle 401 INVALID_SESSION
234
+ // Handle 401 with the configured invalidSessionError
231
235
  if (res.status === 401) {
232
236
  const cloned = res.clone();
233
237
  try {
234
238
  const body = await cloned.json();
235
- if (body.error_msg === 'INVALID_SESSION') {
239
+ if (body.error_msg === sm.config.invalidSessionError) {
236
240
  // Wait for any in-progress refresh, or trigger a new one
237
241
  if (sm.isRefreshing()) {
238
242
  await sm.waitForRefresh();
239
243
  } else {
240
- const existingToken = sm.getToken(sm.config.tokenCookieName);
241
- if (!existingToken) {
242
- await sm.refreshToken();
243
- }
244
+ await sm.refreshToken();
244
245
  }
245
246
 
246
247
  // Update headers with refreshed session
@@ -336,14 +337,17 @@ import { useEffect } from 'react';
336
337
  import { SessionManager } from '@mapnests/gateway-web-sdk';
337
338
  import { API_BASE_URL, BOOTSTRAP_PATH, TOKEN_COOKIE_NAME } from '@/src/config';
338
339
 
340
+ // Configure at module level so the SDK is ready before any child component mounts.
341
+ // configure() only sets config values — no browser APIs needed, safe during SSR.
339
342
  const sessionManager = SessionManager.getInstance();
343
+ sessionManager.configure({
344
+ bootstrapUrl: `${API_BASE_URL}${BOOTSTRAP_PATH}`,
345
+ tokenCookieName: TOKEN_COOKIE_NAME,
346
+ });
340
347
 
341
348
  export default function SessionProvider({ children }) {
349
+ // initialize() calls the bootstrap API — must run in useEffect (client-only)
342
350
  useEffect(() => {
343
- sessionManager.configure({
344
- bootstrapUrl: `${API_BASE_URL}${BOOTSTRAP_PATH}`,
345
- tokenCookieName: TOKEN_COOKIE_NAME,
346
- });
347
351
  sessionManager.initialize().catch(err =>
348
352
  console.error('Failed to initialize session:', err)
349
353
  );
@@ -428,14 +432,16 @@ import { useEffect } from 'react';
428
432
  import { SessionManager } from '@mapnests/gateway-web-sdk';
429
433
  import { API_BASE_URL, BOOTSTRAP_PATH, TOKEN_COOKIE_NAME } from '../src/config';
430
434
 
435
+ // Configure at module level — runs once when the module is first imported.
431
436
  const sessionManager = SessionManager.getInstance();
437
+ sessionManager.configure({
438
+ bootstrapUrl: `${API_BASE_URL}${BOOTSTRAP_PATH}`,
439
+ tokenCookieName: TOKEN_COOKIE_NAME,
440
+ });
432
441
 
433
442
  function MyApp({ Component, pageProps }) {
443
+ // initialize() needs the browser — must run in useEffect
434
444
  useEffect(() => {
435
- sessionManager.configure({
436
- bootstrapUrl: `${API_BASE_URL}${BOOTSTRAP_PATH}`,
437
- tokenCookieName: TOKEN_COOKIE_NAME,
438
- });
439
445
  sessionManager.initialize().catch(err =>
440
446
  console.error('Failed to initialize session:', err)
441
447
  );
@@ -559,6 +565,32 @@ Returns the token value from the named cookie, or `null`.
559
565
 
560
566
  Returns `true` if the token should be sent as a request header (i.e. when the app is served over HTTP, not HTTPS).
561
567
 
568
+ #### `ensureReady()`
569
+
570
+ Ensures the session is ready before making API calls. This is the recommended way to gate requests on session readiness.
571
+
572
+ - If initialized and token is fresh, resolves immediately (zero overhead).
573
+ - If initialization is in progress, waits for it to complete.
574
+ - If not initialized but configured, triggers `initialize()` and waits.
575
+ - If not configured or permanently failed, resolves and lets the request proceed (the 401 handler will deal with it).
576
+
577
+ ```javascript
578
+ await sessionManager.ensureReady();
579
+ // Session is now ready — safe to make API calls
580
+ ```
581
+
582
+ > **Note:** `fetchInterceptor` and `setupAxiosInterceptor` call `ensureReady()` automatically before every request. You only need to call it explicitly when building a manual fetch wrapper (Approach C).
583
+
584
+ #### `needsRefresh()`
585
+
586
+ Returns `true` if the session is initialized but the token has likely expired. Useful for proactive checks.
587
+
588
+ ```javascript
589
+ if (sessionManager.needsRefresh()) {
590
+ await sessionManager.refreshToken();
591
+ }
592
+ ```
593
+
562
594
  #### `isRefreshing()`
563
595
 
564
596
  Returns `true` if a refresh/initialize is currently in progress.
@@ -603,7 +635,7 @@ Clean up timers, listeners, and cookies. Resets the session manager.
603
635
 
604
636
  ### `fetchInterceptor(url, options?)`
605
637
 
606
- Drop-in `fetch` wrapper. Automatically attaches session headers and retries on 401 `INVALID_SESSION`.
638
+ Drop-in `fetch` wrapper. Automatically gates on session readiness via `ensureReady()`, attaches session headers, and retries on 401 `INVALID_SESSION`. Safe to call even before the session is initialized — the request will wait for bootstrap to complete.
607
639
 
608
640
  ```javascript
609
641
  import { fetchInterceptor } from '@mapnests/gateway-web-sdk';
@@ -612,7 +644,7 @@ const response = await fetchInterceptor('/api/data');
612
644
 
613
645
  ### `setupAxiosInterceptor(axiosInstance)`
614
646
 
615
- Attaches request/response interceptors to an Axios instance. Returns the same instance.
647
+ Attaches request/response interceptors to an Axios instance. Returns the same instance. Every request automatically gates on session readiness via `ensureReady()`, attaches session headers, and retries on 401 `INVALID_SESSION`.
616
648
 
617
649
  ```javascript
618
650
  import axios from 'axios';
@@ -686,6 +718,12 @@ This SDK prioritizes **server-set HttpOnly cookies** for maximum security. The S
686
718
  - The SDK uses singleton pattern, but ensure you're not calling `initialize()` multiple times
687
719
  - Use `autoInitialize: false` in `useSession()` if you want manual control
688
720
 
721
+ ### 401 errors after browser close and reopen
722
+ - The SDK automatically clears stale cookies and re-bootstraps on fresh page loads
723
+ - Both `fetchInterceptor` and `setupAxiosInterceptor` call `ensureReady()` before every request, which waits for bootstrap to complete
724
+ - If using a manual fetch wrapper (Approach C), ensure you call `await sm.ensureReady()` before each request
725
+ - Verify your 401 handler compares against `sm.config.invalidSessionError` (default: `'INVALID-GW-SESSION'`), not a hardcoded string
726
+
689
727
  ### Next.js SSR errors
690
728
  - The SDK detects SSR environments and prevents initialization
691
729
  - Always wrap initialization in `useEffect` or client components (`'use client'`)
package/dist/index.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react");class t extends Error{constructor(e,i,s={}){super(e),this.name="SessionError",this.code=i,this.details=s,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),Object.setPrototypeOf(this,t.prototype)}}class i extends t{constructor(e,t){super(e,"CONFIGURATION_ERROR",t),this.name="ConfigurationError"}}class s extends t{constructor(e,t){super(e,"BOOTSTRAP_ERROR",t),this.name="BootstrapError"}}class r extends t{constructor(e,t){super(e,"NETWORK_ERROR",t),this.name="NetworkError"}}class o extends t{constructor(e,t={}){super(e,"SSR_ERROR",t),this.name="SSRError"}}const n={NONE:0,ERROR:1,WARN:2,INFO:3,DEBUG:4};const a=new class{constructor(){this.level=n.WARN}setLevel(e){this.level="string"==typeof e?n[e.toUpperCase()]??n.WARN:e}error(...e){this.level>=n.ERROR&&console.error("[SessionManager]",...e)}warn(...e){this.level>=n.WARN&&console.warn("[SessionManager]",...e)}info(...e){this.level>=n.INFO&&console.info("[SessionManager]",...e)}debug(...e){this.level>=n.DEBUG&&console.debug("[SessionManager]",...e)}};class l{constructor(){if(l.instance)return l.instance;this.config={bootstrapUrl:"/session/bootstrap",refreshInterval:15e5,tokenExpiry:18e5,maxRetries:3,tokenCookieName:"token"},this.state={isInitialized:!1,isLoading:!1,lastRefreshTime:null,error:null,errorCode:null,nextRefreshTime:null,tokenExpiry:null,cfSessionId:null,initializationFailed:!1},this.refreshTimerId=null,this.tickTimerId=null,this.listeners=new Set,this.initializationPromise=null,l.instance=this}configure(e={}){if(!e.bootstrapUrl||"string"!=typeof e.bootstrapUrl||!e.bootstrapUrl.trim())throw new i("bootstrapUrl is required and must be a non-empty string",{bootstrapUrl:e.bootstrapUrl});if(void 0!==e.refreshInterval){if("number"!=typeof e.refreshInterval||!isFinite(e.refreshInterval))throw new i("refreshInterval must be a finite number",{refreshInterval:e.refreshInterval});if(e.refreshInterval<=0)throw new i("refreshInterval must be positive",{refreshInterval:e.refreshInterval})}if(void 0!==e.tokenExpiry){if("number"!=typeof e.tokenExpiry||!isFinite(e.tokenExpiry))throw new i("tokenExpiry must be a finite number",{tokenExpiry:e.tokenExpiry});if(e.tokenExpiry<=0)throw new i("tokenExpiry must be positive",{tokenExpiry:e.tokenExpiry})}this.config={...this.config,...e,credentials:void 0===e.credentials||e.credentials,logLevel:e.logLevel||"WARN"},a.setLevel(this.config.logLevel),this.config.refreshInterval&&this.config.tokenExpiry&&this.config.refreshInterval>=this.config.tokenExpiry&&a.warn("refreshInterval should be less than tokenExpiry to prevent race conditions")}async initialize(e=!1){if("undefined"==typeof window)throw new o("Cannot initialize in non-browser environment (SSR)");if(this.state.initializationFailed)throw a.warn("Initialization previously failed. Reload page to retry."),new s("Initialization failed after max retries. Reload page to retry.");if(this.initializationPromise)return a.debug("Initialization already in progress, waiting..."),this.initializationPromise;if(!e){if("true"===this.getCookie("session_initialized")){a.info("Session already initialized, skipping bootstrap API call"),this.state.isInitialized=!0,this.state.tokenExpiry=this.config.tokenExpiry;const e=this.getCookie("cf-session-id");e?this.state.cfSessionId=e:this.setCfSessionId(this.generateSessionId());const t=parseInt(this.getCookie("session_refresh_at"),10);let i;return i=t&&!isNaN(t)?Math.max(0,t-Date.now()):this.config.refreshInterval,this.state.lastRefreshTime=Date.now(),this.state.nextRefreshTime=Date.now()+i,this.startTokenRefresh(i),this.notifyListeners(),{token:this.getToken(this.config.tokenCookieName)}}}this.state.isLoading=!0,this.state.error=null,this.notifyListeners(),this.initializationPromise=(async()=>{let e;for(let t=1;t<=this.config.maxRetries;t++)try{const e=this.generateSessionId();this.setCfSessionId(e),a.info(`Calling bootstrap API (attempt ${t}/${this.config.maxRetries}):`,this.config.bootstrapUrl);const i=await fetch(this.config.bootstrapUrl,{method:"GET",credentials:this.config.credentials?"include":"same-origin",headers:{"Content-Type":"application/json","cf-session-id":this.state.cfSessionId,"x-client-platform":"web",...this.config.headers}});if(!i.ok)throw new s(`Bootstrap failed: ${i.status} ${i.statusText}`,{status:i.status,statusText:i.statusText,url:this.config.bootstrapUrl});let r;try{r=await i.json()}catch(e){throw new s("Bootstrap response is not valid JSON",{originalError:e.message})}a.info("Bootstrap successful");const o=i.headers.get("set-cookie"),n=o&&o.includes(this.config.tokenCookieName),l=r.refresh_time?1e3*r.refresh_time:this.config.refreshInterval,h=r.expire_time?1e3*r.expire_time:this.config.tokenExpiry;return r.token&&!n?(a.debug("Server did not set cookie, setting from SDK"),this.setCookie(this.config.tokenCookieName,r.token,h/1e3)):n&&a.debug("Server set cookie, skipping SDK cookie logic"),this.setCfSessionId(this.state.cfSessionId,h/1e3),this.state.isInitialized=!0,this.state.isLoading=!1,this.state.lastRefreshTime=Date.now(),this.state.nextRefreshTime=Date.now()+l,this.state.tokenExpiry=h,this.state.error=null,this.setCookie("session_initialized","true",l/1e3),this.setCookie("session_refresh_at",String(Date.now()+l),l/1e3),this.startTokenRefresh(l),this.notifyListeners(),r}catch(i){if(e=i instanceof Error?i:new r("Unknown error occurred",{error:i}),a.error(`Bootstrap attempt ${t} failed:`,e.message),t<this.config.maxRetries){const e=Math.min(1e3*Math.pow(2,t-1),5e3);a.info(`Retrying in ${e}ms...`),await new Promise(t=>setTimeout(t,e))}}throw a.error("All bootstrap attempts failed"),this.state.isLoading=!1,this.state.error=e?.message||"Unknown error",this.state.errorCode=e?.code||"UNKNOWN_ERROR",this.state.initializationFailed=!0,this.notifyListeners(),e})();try{return await this.initializationPromise}finally{this.initializationPromise=null}}isRefreshing(){return null!==this.initializationPromise}async waitForRefresh(){return this.initializationPromise?(a.debug("Waiting for ongoing refresh to complete"),this.initializationPromise):Promise.resolve()}setCfSessionId(e,t){this.state.cfSessionId=e;const i=t||this.config.tokenExpiry/1e3;this.setCookie("cf-session-id",e,i)}generateSessionId(){return"undefined"!=typeof crypto&&crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substr(2,9)}`}getSessionId(){return this.state.cfSessionId}shouldUseTokenHeader(){return"undefined"!=typeof window&&"http:"===window.location.protocol}getCookie(e){if("undefined"==typeof document)return null;const t=`; ${document.cookie}`.split(`; ${e}=`);if(2===t.length)try{return decodeURIComponent(t.pop().split(";").shift())}catch(e){return a.error("Failed to decode cookie value:",e instanceof Error?e.message:String(e)),null}return null}getToken(e=this.config.tokenCookieName){return this.getCookie(e)}setCookie(e,t,i){if("undefined"==typeof document)return void a.warn("Cannot set cookie in non-browser environment");const s=e.replace(/[^a-zA-Z0-9_-]/g,"");if(!s)return void a.error("Invalid cookie name provided");const r=new Date(Date.now()+1e3*i).toUTCString(),o="undefined"!=typeof window&&"https:"===window.location.protocol,n=o?"; Secure":"",l=encodeURIComponent(t);document.cookie=`${s}=${l}; path=/; expires=${r}; SameSite=Lax${n}`,a.debug(`Cookie set: ${s}, expires in ${i}s${o?" (Secure)":""} [WARNING: Not HttpOnly]`)}startTokenRefresh(e=this.config.refreshInterval){this.stopTokenRefresh(),a.info(`Scheduling token refresh in ${e/1e3} seconds`),this.refreshTimerId=setTimeout(async()=>{a.info("Auto-refreshing token...");try{await this.refreshToken()}catch(e){const t=e instanceof Error?e.message:String(e),i=e?.code||"UNKNOWN_ERROR";a.error("Auto-refresh failed:",t),this.state.error=t,this.state.errorCode=i,this.notifyListeners()}},e),this.startTickTimer()}stopTokenRefresh(){this.refreshTimerId&&(clearTimeout(this.refreshTimerId),this.refreshTimerId=null,a.debug("Token refresh timer stopped")),this.stopTickTimer()}startTickTimer(){this.stopTickTimer(),this.tickTimerId=setInterval(()=>{this.notifyListeners()},1e3)}stopTickTimer(){this.tickTimerId&&(clearInterval(this.tickTimerId),this.tickTimerId=null)}async refreshToken(){return a.info("Manual token refresh triggered"),this.state.initializationFailed=!1,this.initialize(!0)}getSessionStatus(){return{isInitialized:this.state.isInitialized,isLoading:this.state.isLoading,lastRefreshTime:this.state.lastRefreshTime,nextRefreshTime:this.state.nextRefreshTime,tokenExpiry:this.state.tokenExpiry,error:this.state.error,errorCode:this.state.errorCode,initializationFailed:this.state.initializationFailed,timeUntilRefresh:this.state.nextRefreshTime?Math.max(0,this.state.nextRefreshTime-Date.now()):null}}subscribe(e){if("function"!=typeof e)throw new TypeError("Listener must be a function");return this.listeners.add(e),()=>{this.listeners.delete(e)}}notifyListeners(){const e=this.getSessionStatus();Array.from(this.listeners).forEach(t=>{try{t(e)}catch(e){const t=e instanceof Error?e.message:String(e);a.error("Listener error:",t)}})}destroy(){this.stopTokenRefresh(),this.listeners.clear(),this.state={isInitialized:!1,isLoading:!1,lastRefreshTime:null,error:null,errorCode:null,nextRefreshTime:null,tokenExpiry:null,cfSessionId:null,initializationFailed:!1},this.setCookie("cf-session-id","",-1),this.setCookie("session_initialized","",-1),this.setCookie("session_refresh_at","",-1),a.info("Destroyed")}static getInstance(){return l.instance||(l.instance=new l),l.instance}}const h="web";exports.BootstrapError=s,exports.CLIENT_PLATFORM=h,exports.ConfigurationError=i,exports.LOG_LEVELS=n,exports.NetworkError=r,exports.SSRError=o,exports.SessionError=t,exports.SessionManager=l,exports.default=l,exports.fetchInterceptor=async function(e,t={}){const i=l.getInstance();if(t.credentials=t.credentials||"include",t.headers={...t.headers,"cf-session-id":i.getSessionId(),"x-client-platform":h},i.shouldUseTokenHeader()){const e=i.getToken(i.config.tokenCookieName);e&&(t.headers[i.config.tokenCookieName]=e)}let s=await fetch(e,t);if(401===s.status){const r=s.clone();try{if("INVALID_SESSION"===(await r.json()).error_msg){if(i.isRefreshing())await i.waitForRefresh();else{i.getToken(i.config.tokenCookieName)||await i.refreshToken()}if(t.headers["cf-session-id"]=i.getSessionId(),t.headers["x-client-platform"]=h,i.shouldUseTokenHeader()){const e=i.getToken(i.config.tokenCookieName);e&&(t.headers[i.config.tokenCookieName]=e)}s=await fetch(e,t)}}catch(e){}}return s},exports.logger=a,exports.setupAxiosInterceptor=function(e){const t=l.getInstance();return e.interceptors.request.use(e=>{if(e.headers["cf-session-id"]=t.getSessionId(),e.headers["x-client-platform"]=h,t.shouldUseTokenHeader()){const i=t.getToken(t.config.tokenCookieName);i&&(e.headers[t.config.tokenCookieName]=i)}return e},e=>Promise.reject(e)),e.interceptors.response.use(e=>e,async i=>{const s=i.config;if(401===i.response?.status&&!s._retry&&"INVALID_SESSION"===i.response?.data?.error_msg){if(s._retry=!0,t.isRefreshing())await t.waitForRefresh();else{t.getToken(t.config.tokenCookieName)||await t.refreshToken()}if(s.headers["cf-session-id"]=t.getSessionId(),s.headers["x-client-platform"]=h,t.shouldUseTokenHeader()){const e=t.getToken(t.config.tokenCookieName);e&&(s.headers[t.config.tokenCookieName]=e)}return e(s)}return Promise.reject(i)}),e},exports.useSession=function(t={}){const{autoInitialize:i=!0}=t,s=l.getInstance(),[r,o]=e.useState(()=>s.getSessionStatus());e.useEffect(()=>s.subscribe(e=>{o(e)}),[]),e.useEffect(()=>{!i||r.isInitialized||r.isLoading||r.initializationFailed||s.initialize().catch(e=>{a.error("Auto-initialization failed:",e)})},[i,r.isInitialized,r.isLoading,r.initializationFailed]);const n=e.useCallback(async()=>{try{await s.refreshToken()}catch(e){throw a.error("Manual refresh failed:",e),e}},[]),h=e.useCallback(async()=>{try{await s.initialize()}catch(e){throw a.error("Manual initialization failed:",e),e}},[]);return{isInitialized:r.isInitialized,isLoading:r.isLoading,error:r.error,lastRefreshTime:r.lastRefreshTime,nextRefreshTime:r.nextRefreshTime,timeUntilRefresh:r.timeUntilRefresh,initializationFailed:r.initializationFailed,refresh:n,initialize:h}};
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react");class t extends Error{constructor(e,i,s={}){super(e),this.name="SessionError",this.code=i,this.details=s,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),Object.setPrototypeOf(this,t.prototype)}}class i extends t{constructor(e,t){super(e,"CONFIGURATION_ERROR",t),this.name="ConfigurationError"}}class s extends t{constructor(e,t){super(e,"BOOTSTRAP_ERROR",t),this.name="BootstrapError"}}class n extends t{constructor(e,t){super(e,"NETWORK_ERROR",t),this.name="NetworkError"}}class r extends t{constructor(e,t={}){super(e,"SSR_ERROR",t),this.name="SSRError"}}const o={bootstrapUrl:"/session/bootstrap",refreshInterval:15e5,tokenExpiry:18e5,maxRetries:3,bootstrapTimeout:3e4,tokenCookieName:"token",invalidSessionError:"INVALID-GW-SESSION"};class a{constructor(){if(a.instance)return a.instance;this.config={...o},this.state={isInitialized:!1,isLoading:!1,lastRefreshTime:null,error:null,errorCode:null,nextRefreshTime:null,tokenExpiry:null,cfSessionId:null,initializationFailed:!1},this.refreshTimerId=null,this.listeners=new Set,this.initializationPromise=null,this._configured=!1,this._boundHandleVisibilityChange=this._handleVisibilityChange.bind(this),this._boundHandlePageShow=this._handlePageShow.bind(this),this._boundHandleOnline=this._handleOnline.bind(this),a.instance=this}configure(e={}){if(!e.bootstrapUrl||"string"!=typeof e.bootstrapUrl||!e.bootstrapUrl.trim())throw new i("bootstrapUrl is required and must be a non-empty string",{bootstrapUrl:e.bootstrapUrl});if(void 0!==e.refreshInterval){if("number"!=typeof e.refreshInterval||!isFinite(e.refreshInterval))throw new i("refreshInterval must be a finite number",{refreshInterval:e.refreshInterval});if(e.refreshInterval<=0)throw new i("refreshInterval must be positive",{refreshInterval:e.refreshInterval})}if(void 0!==e.tokenExpiry){if("number"!=typeof e.tokenExpiry||!isFinite(e.tokenExpiry))throw new i("tokenExpiry must be a finite number",{tokenExpiry:e.tokenExpiry});if(e.tokenExpiry<=0)throw new i("tokenExpiry must be positive",{tokenExpiry:e.tokenExpiry})}if(void 0!==e.maxRetries&&("number"!=typeof e.maxRetries||!Number.isInteger(e.maxRetries)||e.maxRetries<1))throw new i("maxRetries must be a positive integer",{maxRetries:e.maxRetries});this.config={...this.config,...e,credentials:void 0===e.credentials||e.credentials},this._configured=!0}async initialize(e=!1){if("undefined"==typeof window)throw new r("Cannot initialize in non-browser environment (SSR)");if(this.state.initializationFailed)throw new s("Initialization failed after max retries. Reload page to retry.");if(this.initializationPromise)return this.initializationPromise;if(this.state.isInitialized&&!e)return{token:this.getToken(this.config.tokenCookieName)};this.state.isLoading=!0,this.state.error=null,this.notifyListeners(),this.initializationPromise=(async()=>{let e;for(let t=1;t<=this.config.maxRetries;t++)try{const e=this.generateSessionId(),t=new AbortController,i=setTimeout(()=>t.abort(),this.config.bootstrapTimeout);let r,o;try{r=await fetch(this.config.bootstrapUrl,{method:"GET",credentials:this.config.credentials?"include":"same-origin",signal:t.signal,headers:{"cf-session-id":e,"x-client-platform":"web",...this.config.headers}})}catch(e){if("AbortError"===e.name)throw new n(`Bootstrap request timed out after ${this.config.bootstrapTimeout}ms`,{timeout:this.config.bootstrapTimeout,url:this.config.bootstrapUrl});throw e}finally{clearTimeout(i)}if(!r.ok)throw new s(`Bootstrap failed: ${r.status} ${r.statusText}`,{status:r.status,statusText:r.statusText,url:this.config.bootstrapUrl});try{o=await r.json()}catch(e){throw new s("Bootstrap response is not valid JSON",{originalError:e.message})}const a="number"==typeof o.refresh_time&&isFinite(o.refresh_time)&&o.refresh_time>0?1e3*o.refresh_time:this.config.refreshInterval,h="number"==typeof o.expire_time&&isFinite(o.expire_time)&&o.expire_time>0?1e3*o.expire_time:this.config.tokenExpiry,l=o[this.config.tokenCookieName];return l&&this.setCookie(this.config.tokenCookieName,l,h/1e3),this.setCfSessionId(e,h/1e3),this.state.isInitialized=!0,this.state.isLoading=!1,this.state.lastRefreshTime=Date.now(),this.state.nextRefreshTime=Date.now()+a,this.state.tokenExpiry=h,this.state.error=null,this.startTokenRefresh(a),this.notifyListeners(),o}catch(i){if(e=i instanceof Error?i:new n("Unknown error occurred",{error:i}),t<this.config.maxRetries){const e=Math.min(1e3*Math.pow(2,t-1),5e3);await new Promise(t=>setTimeout(t,e))}}throw this.state.isLoading=!1,this.state.error=e?.message||"Unknown error",this.state.errorCode=e?.code||"UNKNOWN_ERROR",this.state.initializationFailed=!0,this.notifyListeners(),e})();try{return await this.initializationPromise}finally{this.initializationPromise=null}}isRefreshing(){return null!==this.initializationPromise}async waitForRefresh(){return this.initializationPromise?this.initializationPromise:Promise.resolve()}_isTokenLikelyExpired(){return!this.state.lastRefreshTime||!this.state.tokenExpiry||Date.now()>=this.state.lastRefreshTime+this.state.tokenExpiry}async ensureReady(){if(!this.state.isInitialized||this._isTokenLikelyExpired())if(this.initializationPromise)try{await this.initializationPromise}catch(e){}else if(this._configured&&!this.state.initializationFailed)try{await this.initialize(this.state.isInitialized)}catch(e){}}needsRefresh(){return this.state.isInitialized&&this._isTokenLikelyExpired()}setCfSessionId(e,t){this.state.cfSessionId=e;const i=t||this.config.tokenExpiry/1e3;this.setCookie("cf-session-id",e,i)}generateSessionId(){return"undefined"!=typeof crypto&&crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substr(2,9)}`}getSessionId(){return this.state.cfSessionId}shouldUseTokenHeader(){return"undefined"!=typeof window&&"http:"===window.location.protocol}_sanitizeCookieName(e){return e.replace(/[^a-zA-Z0-9_-]/g,"")}getCookie(e){if("undefined"==typeof document)return null;const t=this._sanitizeCookieName(e);if(!t)return null;const i=`; ${document.cookie}`.split(`; ${t}=`);if(2===i.length)try{return decodeURIComponent(i.pop().split(";").shift())}catch(e){return null}return null}getToken(e=this.config.tokenCookieName){return this.getCookie(e)}setCookie(e,t,i){if("undefined"==typeof document)return;const s=this._sanitizeCookieName(e);if(!s)return;const n=new Date(Date.now()+1e3*i).toUTCString(),r="undefined"!=typeof window&&"https:"===window.location.protocol?"; Secure":"",o=encodeURIComponent(t);document.cookie=`${s}=${o}; path=/; expires=${n}; SameSite=Lax${r}`}startTokenRefresh(e=this.config.refreshInterval){this.stopTokenRefresh(),this.refreshTimerId=setTimeout(async()=>{try{await this.refreshToken()}catch(t){const i=t instanceof Error?t.message:String(t),s=t?.code||"UNKNOWN_ERROR";this.state.error=i,this.state.errorCode=s,this.notifyListeners();const n=Math.min(2*e,6e4);this.startTokenRefresh(n)}},e),this._startVisibilityListener(),this._startOnlineListener()}stopTokenRefresh(){this.refreshTimerId&&(clearTimeout(this.refreshTimerId),this.refreshTimerId=null),this._stopVisibilityListener(),this._stopOnlineListener()}_startVisibilityListener(){"undefined"!=typeof document&&(document.addEventListener("visibilitychange",this._boundHandleVisibilityChange),"undefined"!=typeof window&&window.addEventListener("pageshow",this._boundHandlePageShow))}_stopVisibilityListener(){"undefined"!=typeof document&&(document.removeEventListener("visibilitychange",this._boundHandleVisibilityChange),"undefined"!=typeof window&&window.removeEventListener("pageshow",this._boundHandlePageShow))}_refreshIfOverdue(){if(!this.state.isInitialized||this.state.initializationFailed)return!1;if(this.isRefreshing())return!1;const e=Date.now(),{nextRefreshTime:t}=this.state;return!!(t&&e>=t)&&(this.refreshToken().catch(()=>{}),!0)}_handleVisibilityChange(){"visible"===document.visibilityState&&this._refreshIfOverdue()}_handlePageShow(e){e.persisted&&this.state.isInitialized&&!this.state.initializationFailed&&(this.isRefreshing()||this.refreshToken().catch(()=>{}))}_startOnlineListener(){"undefined"!=typeof window&&window.addEventListener("online",this._boundHandleOnline)}_stopOnlineListener(){"undefined"!=typeof window&&window.removeEventListener("online",this._boundHandleOnline)}_handleOnline(){this.state.initializationFailed&&this.refreshToken().catch(()=>{})}async refreshToken(){return this.state.initializationFailed=!1,this.initialize(!0)}getSessionStatus(){return{isInitialized:this.state.isInitialized,isLoading:this.state.isLoading,lastRefreshTime:this.state.lastRefreshTime,nextRefreshTime:this.state.nextRefreshTime,tokenExpiry:this.state.tokenExpiry,error:this.state.error,errorCode:this.state.errorCode,initializationFailed:this.state.initializationFailed,timeUntilRefresh:this.state.nextRefreshTime?Math.max(0,this.state.nextRefreshTime-Date.now()):null}}subscribe(e){if("function"!=typeof e)throw new TypeError("Listener must be a function");return this.listeners.add(e),()=>{this.listeners.delete(e)}}notifyListeners(){const e=this.getSessionStatus();Array.from(this.listeners).forEach(t=>{try{t(e)}catch(e){}})}destroy(){this.stopTokenRefresh(),this.initializationPromise=null,this.listeners.clear(),this.setCookie("cf-session-id","",-1),this.setCookie(this.config.tokenCookieName,"",-1),this.config={...o},this.state={isInitialized:!1,isLoading:!1,lastRefreshTime:null,error:null,errorCode:null,nextRefreshTime:null,tokenExpiry:null,cfSessionId:null,initializationFailed:!1},this._configured=!1}static getInstance(){return a.instance||(a.instance=new a),a.instance}}const h="web";const l=new class{setLevel(){}error(){}warn(){}info(){}debug(){}};exports.BootstrapError=s,exports.CLIENT_PLATFORM=h,exports.ConfigurationError=i,exports.LOG_LEVELS={NONE:0,ERROR:1,WARN:2,INFO:3,DEBUG:4},exports.NetworkError=n,exports.SSRError=r,exports.SessionError=t,exports.SessionManager=a,exports.default=a,exports.fetchInterceptor=async function(e,t={}){const i=a.getInstance();await i.ensureReady();const s={...t,credentials:t.credentials||"include",headers:{...t.headers,"cf-session-id":i.getSessionId(),"x-client-platform":h}};if(i.shouldUseTokenHeader()){const e=i.getToken(i.config.tokenCookieName);e&&(s.headers[i.config.tokenCookieName]=e)}let n=await fetch(e,s);if(401===n.status){const t=n.clone();try{if((await t.json()).error_msg===i.config.invalidSessionError){if(i.isRefreshing()?await i.waitForRefresh():await i.refreshToken(),s.headers["cf-session-id"]=i.getSessionId(),i.shouldUseTokenHeader()){const e=i.getToken(i.config.tokenCookieName);e&&(s.headers[i.config.tokenCookieName]=e)}n=await fetch(e,s)}}catch(e){}}return n},exports.logger=l,exports.setupAxiosInterceptor=function(e){const t=a.getInstance();return void 0!==e._gwSessionRequestId&&e.interceptors.request.eject(e._gwSessionRequestId),void 0!==e._gwSessionResponseId&&e.interceptors.response.eject(e._gwSessionResponseId),e._gwSessionRequestId=e.interceptors.request.use(async e=>{if(await t.ensureReady(),t.config.credentials&&(e.withCredentials=!0),e.headers["cf-session-id"]=t.getSessionId(),e.headers["x-client-platform"]=h,t.shouldUseTokenHeader()){const i=t.getToken(t.config.tokenCookieName);i&&(e.headers[t.config.tokenCookieName]=i)}return e},e=>Promise.reject(e)),e._gwSessionResponseId=e.interceptors.response.use(e=>e,async i=>{const s=i.config;if(401===i.response?.status&&!s._retry&&i.response?.data?.error_msg===t.config.invalidSessionError){if(s._retry=!0,t.isRefreshing()?await t.waitForRefresh():await t.refreshToken(),s.headers["cf-session-id"]=t.getSessionId(),s.headers["x-client-platform"]=h,t.shouldUseTokenHeader()){const e=t.getToken(t.config.tokenCookieName);e&&(s.headers[t.config.tokenCookieName]=e)}return e(s)}return Promise.reject(i)}),e},exports.useSession=function(t={}){const{autoInitialize:i=!0}=t,s=a.getInstance(),[n,r]=e.useState(()=>s.getSessionStatus()),[o,h]=e.useState(()=>{const{nextRefreshTime:e}=s.getSessionStatus();return e?Math.max(0,e-Date.now()):null}),l=e.useRef(n.nextRefreshTime);e.useEffect(()=>s.subscribe(e=>{r(e),l.current=e.nextRefreshTime}),[]),e.useEffect(()=>{if(!n.nextRefreshTime)return void h(null);h(Math.max(0,n.nextRefreshTime-Date.now()));const e=setInterval(()=>{const e=l.current;h(e?Math.max(0,e-Date.now()):null)},1e3);return()=>clearInterval(e)},[n.nextRefreshTime]),e.useEffect(()=>{!i||n.isInitialized||n.isLoading||n.initializationFailed||s.initialize().catch(()=>{})},[i,n.isInitialized,n.isLoading,n.initializationFailed]);const d=e.useCallback(async()=>{await s.refreshToken()},[]),f=e.useCallback(async()=>{await s.initialize()},[]);return{isInitialized:n.isInitialized,isLoading:n.isLoading,error:n.error,lastRefreshTime:n.lastRefreshTime,nextRefreshTime:n.nextRefreshTime,timeUntilRefresh:o,initializationFailed:n.initializationFailed,refresh:d,initialize:f}};
2
2
  //# sourceMappingURL=index.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/errors.js","../src/logger.js","../src/SessionManager.js","../src/interceptors.js","../src/hooks/useSession.js"],"sourcesContent":["/**\n * Custom error classes for SessionManager\n */\n\nexport class SessionError extends Error {\n constructor(message, code, details = {}) {\n super(message);\n this.name = 'SessionError';\n this.code = code;\n this.details = details;\n \n // Maintains proper stack trace for where error was thrown\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n \n // Set the prototype explicitly for proper instanceof checks\n Object.setPrototypeOf(this, SessionError.prototype);\n }\n}\n\nexport class ConfigurationError extends SessionError {\n constructor(message, details) {\n super(message, 'CONFIGURATION_ERROR', details);\n this.name = 'ConfigurationError';\n }\n}\n\nexport class BootstrapError extends SessionError {\n constructor(message, details) {\n super(message, 'BOOTSTRAP_ERROR', details);\n this.name = 'BootstrapError';\n }\n}\n\nexport class NetworkError extends SessionError {\n constructor(message, details) {\n super(message, 'NETWORK_ERROR', details);\n this.name = 'NetworkError';\n }\n}\n\nexport class SSRError extends SessionError {\n constructor(message, details = {}) {\n super(message, 'SSR_ERROR', details);\n this.name = 'SSRError';\n }\n}\n","/**\n * Logger utility with configurable log levels\n */\n\nconst LOG_LEVELS = {\n NONE: 0,\n ERROR: 1,\n WARN: 2,\n INFO: 3,\n DEBUG: 4\n};\n\nclass Logger {\n constructor() {\n this.level = LOG_LEVELS.WARN; // Default to WARN in production\n }\n\n setLevel(level) {\n if (typeof level === 'string') {\n this.level = LOG_LEVELS[level.toUpperCase()] ?? LOG_LEVELS.WARN;\n } else {\n this.level = level;\n }\n }\n\n error(...args) {\n if (this.level >= LOG_LEVELS.ERROR) {\n console.error('[SessionManager]', ...args);\n }\n }\n\n warn(...args) {\n if (this.level >= LOG_LEVELS.WARN) {\n console.warn('[SessionManager]', ...args);\n }\n }\n\n info(...args) {\n if (this.level >= LOG_LEVELS.INFO) {\n console.info('[SessionManager]', ...args);\n }\n }\n\n debug(...args) {\n if (this.level >= LOG_LEVELS.DEBUG) {\n console.debug('[SessionManager]', ...args);\n }\n }\n}\n\nexport const logger = new Logger();\nexport { LOG_LEVELS };\n","import {BootstrapError, ConfigurationError, NetworkError, SSRError} from './errors.js';\nimport {logger} from './logger.js';\nconst CLIENT_PLATFORM = 'web';\n/**\n * SessionManager - Core session token management class\n * Handles session bootstrap, automatic token refresh, and lifecycle management\n */\nclass SessionManager {\n constructor() {\n if (SessionManager.instance) {\n return SessionManager.instance;\n }\n\n this.config = {\n bootstrapUrl: '/session/bootstrap',\n refreshInterval: 25 * 60 * 1000, // 25 minutes in milliseconds\n tokenExpiry: 30 * 60 * 1000, // 30 minutes in milliseconds\n maxRetries: 3,\n tokenCookieName: 'token', // Cookie name to read token from\n };\n\n this.state = {\n isInitialized: false,\n isLoading: false,\n lastRefreshTime: null,\n error: null,\n errorCode: null,\n nextRefreshTime: null,\n tokenExpiry: null,\n cfSessionId: null,\n initializationFailed: false,\n };\n\n this.refreshTimerId = null;\n this.tickTimerId = null;\n this.listeners = new Set();\n this.initializationPromise = null;\n\n SessionManager.instance = this;\n }\n\n /**\n * Configure the session manager\n * @param {Object} config - Configuration options\n * @param {string} config.bootstrapUrl - URL for bootstrap API endpoint\n * @param {number} config.refreshInterval - Interval for token refresh in milliseconds (default: 25 mins)\n * @param {number} config.tokenExpiry - Token expiry time in milliseconds (default: 30 mins)\n * @param {number} config.maxRetries - Maximum retry attempts on failure (default: 3)\n * @param {Object} config.headers - Additional headers for API calls\n * @param {boolean} config.credentials - Include credentials in requests (default: true)\n * @param {string} config.tokenCookieName - Cookie name to read token from (default: 'token')\n */\n configure(config = {}) {\n if (!config.bootstrapUrl || typeof config.bootstrapUrl !== 'string' || !config.bootstrapUrl.trim()) {\n throw new ConfigurationError('bootstrapUrl is required and must be a non-empty string', { bootstrapUrl: config.bootstrapUrl });\n }\n \n if (config.refreshInterval !== undefined) {\n if (typeof config.refreshInterval !== 'number' || !isFinite(config.refreshInterval)) {\n throw new ConfigurationError('refreshInterval must be a finite number', { refreshInterval: config.refreshInterval });\n }\n if (config.refreshInterval <= 0) {\n throw new ConfigurationError('refreshInterval must be positive', { refreshInterval: config.refreshInterval });\n }\n }\n \n if (config.tokenExpiry !== undefined) {\n if (typeof config.tokenExpiry !== 'number' || !isFinite(config.tokenExpiry)) {\n throw new ConfigurationError('tokenExpiry must be a finite number', { tokenExpiry: config.tokenExpiry });\n }\n if (config.tokenExpiry <= 0) {\n throw new ConfigurationError('tokenExpiry must be positive', { tokenExpiry: config.tokenExpiry });\n }\n }\n\n this.config = {\n ...this.config,\n ...config,\n credentials: config.credentials !== undefined ? config.credentials : true,\n logLevel: config.logLevel || 'WARN'\n };\n\n logger.setLevel(this.config.logLevel);\n\n if (this.config.refreshInterval && this.config.tokenExpiry && \n this.config.refreshInterval >= this.config.tokenExpiry) {\n logger.warn('refreshInterval should be less than tokenExpiry to prevent race conditions');\n }\n }\n\n /**\n * Initialize session by calling bootstrap API\n * @param {boolean} skipCookieCheck - Skip session_initialized cookie check (for auto-refresh)\n * @returns {Promise<Object>} Bootstrap response\n */\n async initialize(skipCookieCheck = false) {\n if (typeof window === 'undefined') {\n throw new SSRError('Cannot initialize in non-browser environment (SSR)');\n }\n \n if (this.state.initializationFailed) {\n logger.warn('Initialization previously failed. Reload page to retry.');\n throw new BootstrapError('Initialization failed after max retries. Reload page to retry.');\n }\n \n // Return existing promise if refresh already in progress\n if (this.initializationPromise) {\n logger.debug('Initialization already in progress, waiting...');\n return this.initializationPromise;\n }\n\n // Check if session is already initialized via cookie (only on page load)\n if (!skipCookieCheck) {\n const sessionInitialized = this.getCookie('session_initialized');\n if (sessionInitialized === 'true') {\n logger.info('Session already initialized, skipping bootstrap API call');\n this.state.isInitialized = true;\n this.state.tokenExpiry = this.config.tokenExpiry;\n\n // Restore cf-session-id from cookie\n const storedSessionId = this.getCookie('cf-session-id');\n if (storedSessionId) {\n this.state.cfSessionId = storedSessionId;\n } else {\n this.setCfSessionId(this.generateSessionId());\n }\n\n // Calculate remaining time from stored refresh timestamp\n const refreshAt = parseInt(this.getCookie('session_refresh_at'), 10);\n let remaining;\n if (refreshAt && !isNaN(refreshAt)) {\n remaining = Math.max(0, refreshAt - Date.now());\n } else {\n remaining = this.config.refreshInterval;\n }\n\n this.state.lastRefreshTime = Date.now();\n this.state.nextRefreshTime = Date.now() + remaining;\n\n this.startTokenRefresh(remaining);\n this.notifyListeners();\n return { token: this.getToken(this.config.tokenCookieName) };\n }\n }\n\n this.state.isLoading = true;\n this.state.error = null;\n this.notifyListeners();\n\n this.initializationPromise = (async () => {\n let lastError;\n \n for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) {\n try {\n // Generate new cf-session-id for each bootstrap call\n const newSessionId = this.generateSessionId();\n this.setCfSessionId(newSessionId);\n\n logger.info(`Calling bootstrap API (attempt ${attempt}/${this.config.maxRetries}):`, this.config.bootstrapUrl);\n\n const response = await fetch(this.config.bootstrapUrl, {\n method: 'GET',\n credentials: this.config.credentials ? 'include' : 'same-origin',\n headers: {\n 'Content-Type': 'application/json',\n 'cf-session-id': this.state.cfSessionId,\n 'x-client-platform': CLIENT_PLATFORM,\n ...this.config.headers,\n },\n });\n\n if (!response.ok) {\n throw new BootstrapError(`Bootstrap failed: ${response.status} ${response.statusText}`, {\n status: response.status,\n statusText: response.statusText,\n url: this.config.bootstrapUrl\n });\n }\n\n let data;\n try {\n data = await response.json();\n } catch (jsonError) {\n throw new BootstrapError('Bootstrap response is not valid JSON', { originalError: jsonError.message });\n }\n logger.info('Bootstrap successful');\n\n // Check if server set a cookie\n const setCookieHeader = response.headers.get('set-cookie');\n const serverSetCookie = setCookieHeader && setCookieHeader.includes(this.config.tokenCookieName);\n\n // Use refresh_time from response (in seconds) or fall back to config\n const refreshInterval = data.refresh_time \n ? data.refresh_time * 1000 \n : this.config.refreshInterval;\n\n // Calculate token expiry from response (in seconds) or fall back to config\n const tokenExpiry = data.expire_time\n ? data.expire_time * 1000\n : this.config.tokenExpiry;\n\n if (data.token && !serverSetCookie) {\n logger.debug('Server did not set cookie, setting from SDK');\n this.setCookie(this.config.tokenCookieName, data.token, tokenExpiry / 1000);\n } else if (serverSetCookie) {\n logger.debug('Server set cookie, skipping SDK cookie logic');\n }\n\n // Re-set cf-session-id cookie with actual expire_time from response\n this.setCfSessionId(this.state.cfSessionId, tokenExpiry / 1000);\n\n this.state.isInitialized = true;\n this.state.isLoading = false;\n this.state.lastRefreshTime = Date.now();\n this.state.nextRefreshTime = Date.now() + refreshInterval;\n this.state.tokenExpiry = tokenExpiry;\n this.state.error = null;\n\n // Set session_initialized cookie (expires at refresh interval time)\n this.setCookie('session_initialized', 'true', refreshInterval / 1000);\n\n // Store next refresh timestamp for reload recovery\n this.setCookie('session_refresh_at', String(Date.now() + refreshInterval), refreshInterval / 1000);\n\n // Start automatic refresh timer with dynamic interval\n this.startTokenRefresh(refreshInterval);\n\n this.notifyListeners();\n return data;\n } catch (error) {\n lastError = error instanceof Error ? error : new NetworkError('Unknown error occurred', { error });\n logger.error(`Bootstrap attempt ${attempt} failed:`, lastError.message);\n \n if (attempt < this.config.maxRetries) {\n const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);\n logger.info(`Retrying in ${delay}ms...`);\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n }\n \n logger.error('All bootstrap attempts failed');\n this.state.isLoading = false;\n this.state.error = lastError?.message || 'Unknown error';\n this.state.errorCode = lastError?.code || 'UNKNOWN_ERROR';\n this.state.initializationFailed = true;\n this.notifyListeners();\n throw lastError;\n })();\n\n try {\n return await this.initializationPromise;\n } finally {\n this.initializationPromise = null;\n }\n }\n\n /**\n * Check if token refresh is currently in progress\n * @returns {boolean} True if refresh is in progress\n */\n isRefreshing() {\n return this.initializationPromise !== null;\n }\n\n /**\n * Wait for ongoing refresh to complete\n * @returns {Promise<Object>} Bootstrap response from ongoing refresh\n */\n async waitForRefresh() {\n if (this.initializationPromise) {\n logger.debug('Waiting for ongoing refresh to complete');\n return this.initializationPromise;\n }\n return Promise.resolve();\n }\n\n /**\n * Set cf-session-id in cookie\n * @param {string} sessionId - Session ID\n */\n setCfSessionId(sessionId, maxAge) {\n this.state.cfSessionId = sessionId;\n const cookieMaxAge = maxAge || this.config.tokenExpiry / 1000;\n this.setCookie('cf-session-id', sessionId, cookieMaxAge);\n }\n\n /**\n * Generate unique session ID\n * @returns {string} Generated session ID\n */\n generateSessionId() {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }\n\n /**\n * Get cf-session-id for requests\n * @returns {string} Current cf-session-id\n */\n getSessionId() {\n return this.state.cfSessionId;\n }\n\n /**\n * Check if token should be added to headers (HTTP or cross-origin)\n * @returns {boolean} True if token should be added to headers\n */\n shouldUseTokenHeader() {\n if (typeof window === 'undefined') return false;\n return window.location.protocol === 'http:';\n }\n\n /**\n * Get cookie value\n * @param {string} name - Cookie name\n * @returns {string|null} Cookie value or null\n */\n getCookie(name) {\n if (typeof document === 'undefined') return null;\n const value = `; ${document.cookie}`;\n const parts = value.split(`; ${name}=`);\n if (parts.length === 2) {\n try {\n return decodeURIComponent(parts.pop().split(';').shift());\n } catch (error) {\n logger.error('Failed to decode cookie value:', error instanceof Error ? error.message : String(error));\n return null;\n }\n }\n return null;\n }\n\n /**\n * Get token from cookie\n * @param {string} name - Cookie name (default: configured tokenCookieName)\n * @returns {string|null} Token value or null\n */\n getToken(name = this.config.tokenCookieName) {\n return this.getCookie(name);\n }\n\n /**\n * Set cookie from client-side (WARNING: Not HttpOnly, less secure than server-set cookies)\n * @param {string} name - Cookie name\n * @param {string} value - Cookie value\n * @param {number} maxAge - Max age in seconds\n */\n setCookie(name, value, maxAge) {\n if (typeof document === 'undefined') {\n logger.warn('Cannot set cookie in non-browser environment');\n return;\n }\n \n // Sanitize cookie name to prevent XSS\n const sanitizedName = name.replace(/[^a-zA-Z0-9_-]/g, '');\n if (!sanitizedName) {\n logger.error('Invalid cookie name provided');\n return;\n }\n \n const expires = new Date(Date.now() + maxAge * 1000).toUTCString();\n const isSecure = typeof window !== 'undefined' && window.location.protocol === 'https:';\n const secureFlag = isSecure ? '; Secure' : '';\n const encodedValue = encodeURIComponent(value);\n document.cookie = `${sanitizedName}=${encodedValue}; path=/; expires=${expires}; SameSite=Lax${secureFlag}`;\n logger.debug(`Cookie set: ${sanitizedName}, expires in ${maxAge}s${isSecure ? ' (Secure)' : ''} [WARNING: Not HttpOnly]`);\n }\n\n /**\n * Start automatic token refresh timer\n * @param {number} interval - Refresh interval in milliseconds\n */\n startTokenRefresh(interval = this.config.refreshInterval) {\n this.stopTokenRefresh();\n logger.info(`Scheduling token refresh in ${interval / 1000} seconds`);\n\n this.refreshTimerId = setTimeout(async () => {\n logger.info('Auto-refreshing token...');\n try {\n await this.refreshToken();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n const errorCode = error?.code || 'UNKNOWN_ERROR';\n logger.error('Auto-refresh failed:', errorMessage);\n this.state.error = errorMessage;\n this.state.errorCode = errorCode;\n this.notifyListeners();\n }\n }, interval);\n\n this.startTickTimer();\n }\n\n /**\n * Stop automatic token refresh timer\n */\n stopTokenRefresh() {\n if (this.refreshTimerId) {\n clearTimeout(this.refreshTimerId);\n this.refreshTimerId = null;\n logger.debug('Token refresh timer stopped');\n }\n this.stopTickTimer();\n }\n\n /**\n * Start periodic tick timer to notify listeners for countdown updates\n */\n startTickTimer() {\n this.stopTickTimer();\n this.tickTimerId = setInterval(() => {\n this.notifyListeners();\n }, 1000);\n }\n\n /**\n * Stop periodic tick timer\n */\n stopTickTimer() {\n if (this.tickTimerId) {\n clearInterval(this.tickTimerId);\n this.tickTimerId = null;\n }\n }\n\n /**\n * Manually refresh the session token\n * @returns {Promise<Object>} Bootstrap response\n */\n async refreshToken() {\n logger.info('Manual token refresh triggered');\n this.state.initializationFailed = false;\n return this.initialize(true);\n }\n\n /**\n * Get current session status\n * @returns {Object} Current session state\n */\n getSessionStatus() {\n return {\n isInitialized: this.state.isInitialized,\n isLoading: this.state.isLoading,\n lastRefreshTime: this.state.lastRefreshTime,\n nextRefreshTime: this.state.nextRefreshTime,\n tokenExpiry: this.state.tokenExpiry,\n error: this.state.error,\n errorCode: this.state.errorCode,\n initializationFailed: this.state.initializationFailed,\n timeUntilRefresh: this.state.nextRefreshTime\n ? Math.max(0, this.state.nextRefreshTime - Date.now())\n : null,\n };\n }\n\n /**\n * Subscribe to session state changes\n * @param {Function} listener - Callback function to be called on state changes\n * @returns {Function} Unsubscribe function\n */\n subscribe(listener) {\n if (typeof listener !== 'function') {\n throw new TypeError('Listener must be a function');\n }\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Notify all listeners of state changes\n */\n notifyListeners() {\n const status = this.getSessionStatus();\n // Use Array.from to create a snapshot, preventing issues if listeners modify the Set\n Array.from(this.listeners).forEach(listener => {\n try {\n listener(status);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('Listener error:', errorMessage);\n }\n });\n }\n\n /**\n * Clean up and reset the session manager\n */\n destroy() {\n this.stopTokenRefresh();\n this.listeners.clear();\n this.state = {\n isInitialized: false,\n isLoading: false,\n lastRefreshTime: null,\n error: null,\n errorCode: null,\n nextRefreshTime: null,\n tokenExpiry: null,\n cfSessionId: null,\n initializationFailed: false,\n };\n // Clear cookies\n this.setCookie('cf-session-id', '', -1);\n this.setCookie('session_initialized', '', -1);\n this.setCookie('session_refresh_at', '', -1);\n logger.info('Destroyed');\n }\n\n /**\n * Get the singleton instance\n * @returns {SessionManager} Singleton instance\n */\n static getInstance() {\n if (!SessionManager.instance) {\n SessionManager.instance = new SessionManager();\n }\n return SessionManager.instance;\n }\n}\n\nexport default SessionManager;\n","import SessionManager from './SessionManager.js';\nexport const CLIENT_PLATFORM = 'web';\n\n/**\n * Fetch interceptor with automatic token refresh on 401\n * @param {string} url - Request URL\n * @param {Object} options - Fetch options\n * @returns {Promise<Response>} Fetch response\n */\nexport async function fetchInterceptor(url, options = {}) {\n const sessionManager = SessionManager.getInstance();\n \n // Ensure credentials are included to send cookies\n options.credentials = options.credentials || 'include';\n \n // Add headers\n options.headers = {\n ...options.headers,\n 'cf-session-id': sessionManager.getSessionId(),\n 'x-client-platform': CLIENT_PLATFORM,\n };\n \n // Add token to header if HTTP (not HTTPS)\n if (sessionManager.shouldUseTokenHeader()) {\n const token = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (token) {\n options.headers[sessionManager.config.tokenCookieName] = token;\n }\n }\n \n let response = await fetch(url, options);\n \n // Retry once if unauthorized with INVALID_SESSION\n if (response.status === 401) {\n const clonedResponse = response.clone();\n try {\n const data = await clonedResponse.json();\n if (data.error_msg === 'INVALID_SESSION') {\n if (sessionManager.isRefreshing()) {\n await sessionManager.waitForRefresh();\n } else {\n const existingToken = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (!existingToken) {\n await sessionManager.refreshToken();\n }\n }\n options.headers['cf-session-id'] = sessionManager.getSessionId();\n options.headers['x-client-platform'] = CLIENT_PLATFORM;\n if (sessionManager.shouldUseTokenHeader()) {\n const newToken = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (newToken) {\n options.headers[sessionManager.config.tokenCookieName] = newToken;\n }\n }\n response = await fetch(url, options);\n }\n } catch (e) {\n // Not JSON or parsing failed, return original response\n }\n }\n \n return response;\n}\n\n/**\n * Axios interceptor with automatic token refresh on 401 and INVALID_SESSION\n * @param {Object} axiosInstance - Axios instance\n */\nexport function setupAxiosInterceptor(axiosInstance) {\n const sessionManager = SessionManager.getInstance();\n \n // Request interceptor to add cf-session-id and token\n axiosInstance.interceptors.request.use(\n (config) => {\n config.headers['cf-session-id'] = sessionManager.getSessionId();\n config.headers['x-client-platform'] = CLIENT_PLATFORM;\n if (sessionManager.shouldUseTokenHeader()) {\n const token = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (token) {\n config.headers[sessionManager.config.tokenCookieName] = token;\n }\n }\n return config;\n },\n (error) => Promise.reject(error)\n );\n \n // Response interceptor for token refresh\n axiosInstance.interceptors.response.use(\n (response) => response,\n async (error) => {\n const originalRequest = error.config;\n \n // Retry once if unauthorized with INVALID_SESSION\n if (error.response?.status === 401 && !originalRequest._retry) {\n if (error.response?.data?.error_msg === 'INVALID_SESSION') {\n originalRequest._retry = true;\n \n if (sessionManager.isRefreshing()) {\n await sessionManager.waitForRefresh();\n } else {\n const existingToken = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (!existingToken) {\n await sessionManager.refreshToken();\n }\n }\n \n originalRequest.headers['cf-session-id'] = sessionManager.getSessionId();\n originalRequest.headers['x-client-platform'] = CLIENT_PLATFORM;\n if (sessionManager.shouldUseTokenHeader()) {\n const newToken = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (newToken) {\n originalRequest.headers[sessionManager.config.tokenCookieName] = newToken;\n }\n }\n return axiosInstance(originalRequest);\n }\n }\n \n return Promise.reject(error);\n }\n );\n \n return axiosInstance;\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport SessionManager from '../SessionManager.js';\nimport { logger } from '../logger.js';\n\n/**\n * React hook for session management\n * Provides session state and controls for React components\n * \n * @param {Object} options - Configuration options\n * @param {boolean} options.autoInitialize - Automatically initialize on mount (default: true)\n * @returns {Object} Session state and controls\n */\nexport function useSession(options = {}) {\n const { autoInitialize = true } = options;\n\n const sessionManager = SessionManager.getInstance();\n\n const [sessionState, setSessionState] = useState(() =>\n sessionManager.getSessionStatus()\n );\n\n // Subscribe to session state changes\n useEffect(() => {\n const unsubscribe = sessionManager.subscribe((newState) => {\n setSessionState(newState);\n });\n\n return unsubscribe;\n }, []); // sessionManager is a singleton, no need to include in deps\n\n // Auto-initialize if enabled\n useEffect(() => {\n if (autoInitialize && !sessionState.isInitialized && !sessionState.isLoading && !sessionState.initializationFailed) {\n sessionManager.initialize().catch(error => {\n logger.error('Auto-initialization failed:', error);\n });\n }\n }, [autoInitialize, sessionState.isInitialized, sessionState.isLoading, sessionState.initializationFailed]);\n\n // Manual refresh function\n const refresh = useCallback(async () => {\n try {\n await sessionManager.refreshToken();\n } catch (error) {\n logger.error('Manual refresh failed:', error);\n throw error;\n }\n }, []);\n\n // Initialize function (for manual control)\n const initialize = useCallback(async () => {\n try {\n await sessionManager.initialize();\n } catch (error) {\n logger.error('Manual initialization failed:', error);\n throw error;\n }\n }, []);\n\n return {\n // State\n isInitialized: sessionState.isInitialized,\n isLoading: sessionState.isLoading,\n error: sessionState.error,\n lastRefreshTime: sessionState.lastRefreshTime,\n nextRefreshTime: sessionState.nextRefreshTime,\n timeUntilRefresh: sessionState.timeUntilRefresh,\n initializationFailed: sessionState.initializationFailed,\n\n // Actions\n refresh,\n initialize,\n };\n}\n\nexport default useSession;\n"],"names":["SessionError","Error","constructor","message","code","details","super","this","name","captureStackTrace","Object","setPrototypeOf","prototype","ConfigurationError","BootstrapError","NetworkError","SSRError","LOG_LEVELS","NONE","ERROR","WARN","INFO","DEBUG","logger","level","setLevel","toUpperCase","error","args","console","warn","info","debug","SessionManager","instance","config","bootstrapUrl","refreshInterval","tokenExpiry","maxRetries","tokenCookieName","state","isInitialized","isLoading","lastRefreshTime","errorCode","nextRefreshTime","cfSessionId","initializationFailed","refreshTimerId","tickTimerId","listeners","Set","initializationPromise","configure","trim","undefined","isFinite","credentials","logLevel","initialize","skipCookieCheck","window","getCookie","storedSessionId","setCfSessionId","generateSessionId","refreshAt","parseInt","remaining","isNaN","Math","max","Date","now","startTokenRefresh","notifyListeners","token","getToken","lastError","attempt","newSessionId","response","fetch","method","headers","ok","status","statusText","url","data","json","jsonError","originalError","setCookieHeader","get","serverSetCookie","includes","refresh_time","expire_time","setCookie","String","delay","min","pow","Promise","resolve","setTimeout","isRefreshing","waitForRefresh","sessionId","maxAge","cookieMaxAge","crypto","randomUUID","random","toString","substr","getSessionId","shouldUseTokenHeader","location","protocol","document","parts","cookie","split","length","decodeURIComponent","pop","shift","value","sanitizedName","replace","expires","toUTCString","isSecure","secureFlag","encodedValue","encodeURIComponent","interval","stopTokenRefresh","async","refreshToken","errorMessage","startTickTimer","clearTimeout","stopTickTimer","setInterval","clearInterval","getSessionStatus","timeUntilRefresh","subscribe","listener","TypeError","add","delete","Array","from","forEach","destroy","clear","getInstance","CLIENT_PLATFORM","options","sessionManager","clonedResponse","clone","error_msg","newToken","e","axiosInstance","interceptors","request","use","reject","originalRequest","_retry","autoInitialize","sessionState","setSessionState","useState","useEffect","newState","catch","refresh","useCallback"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/errors.js","../src/SessionManager.js","../src/interceptors.js","../src/logger.js","../src/hooks/useSession.js"],"sourcesContent":["/**\n * Custom error classes for SessionManager\n */\n\nexport class SessionError extends Error {\n constructor(message, code, details = {}) {\n super(message);\n this.name = 'SessionError';\n this.code = code;\n this.details = details;\n \n // Maintains proper stack trace for where error was thrown\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n \n // Set the prototype explicitly for proper instanceof checks\n Object.setPrototypeOf(this, SessionError.prototype);\n }\n}\n\nexport class ConfigurationError extends SessionError {\n constructor(message, details) {\n super(message, 'CONFIGURATION_ERROR', details);\n this.name = 'ConfigurationError';\n }\n}\n\nexport class BootstrapError extends SessionError {\n constructor(message, details) {\n super(message, 'BOOTSTRAP_ERROR', details);\n this.name = 'BootstrapError';\n }\n}\n\nexport class NetworkError extends SessionError {\n constructor(message, details) {\n super(message, 'NETWORK_ERROR', details);\n this.name = 'NetworkError';\n }\n}\n\nexport class SSRError extends SessionError {\n constructor(message, details = {}) {\n super(message, 'SSR_ERROR', details);\n this.name = 'SSRError';\n }\n}\n","import {BootstrapError, ConfigurationError, NetworkError, SSRError} from './errors.js';\nconst CLIENT_PLATFORM = 'web';\n\nconst DEFAULT_CONFIG = {\n bootstrapUrl: '/session/bootstrap',\n refreshInterval: 25 * 60 * 1000, // 25 minutes in milliseconds\n tokenExpiry: 30 * 60 * 1000, // 30 minutes in milliseconds\n maxRetries: 3,\n bootstrapTimeout: 30000, // 30 seconds timeout for bootstrap fetch\n tokenCookieName: 'token', // Must match server's session_header\n invalidSessionError: 'INVALID-GW-SESSION', // Must match server's error response\n};\n\n/**\n * SessionManager - Core session token management class\n * Handles session bootstrap, automatic token refresh, and lifecycle management\n */\nclass SessionManager {\n constructor() {\n if (SessionManager.instance) {\n return SessionManager.instance;\n }\n\n this.config = { ...DEFAULT_CONFIG };\n\n this.state = {\n isInitialized: false,\n isLoading: false,\n lastRefreshTime: null,\n error: null,\n errorCode: null,\n nextRefreshTime: null,\n tokenExpiry: null,\n cfSessionId: null,\n initializationFailed: false,\n };\n\n this.refreshTimerId = null;\n this.listeners = new Set();\n this.initializationPromise = null;\n this._configured = false;\n this._boundHandleVisibilityChange = this._handleVisibilityChange.bind(this);\n this._boundHandlePageShow = this._handlePageShow.bind(this);\n this._boundHandleOnline = this._handleOnline.bind(this);\n\n SessionManager.instance = this;\n }\n\n /**\n * Configure the session manager\n * @param {Object} config - Configuration options\n * @param {string} config.bootstrapUrl - URL for bootstrap API endpoint\n * @param {number} config.refreshInterval - Interval for token refresh in milliseconds (default: 25 mins)\n * @param {number} config.tokenExpiry - Token expiry time in milliseconds (default: 30 mins)\n * @param {number} config.maxRetries - Maximum retry attempts on failure (default: 3)\n * @param {number} config.bootstrapTimeout - Timeout for bootstrap fetch in milliseconds (default: 30s)\n * @param {Object} config.headers - Additional headers for API calls\n * @param {boolean} config.credentials - Include credentials in requests (default: true)\n * @param {string} config.tokenCookieName - Cookie name to read token from (default: 'token')\n */\n configure(config = {}) {\n if (!config.bootstrapUrl || typeof config.bootstrapUrl !== 'string' || !config.bootstrapUrl.trim()) {\n throw new ConfigurationError('bootstrapUrl is required and must be a non-empty string', { bootstrapUrl: config.bootstrapUrl });\n }\n\n if (config.refreshInterval !== undefined) {\n if (typeof config.refreshInterval !== 'number' || !isFinite(config.refreshInterval)) {\n throw new ConfigurationError('refreshInterval must be a finite number', { refreshInterval: config.refreshInterval });\n }\n if (config.refreshInterval <= 0) {\n throw new ConfigurationError('refreshInterval must be positive', { refreshInterval: config.refreshInterval });\n }\n }\n\n if (config.tokenExpiry !== undefined) {\n if (typeof config.tokenExpiry !== 'number' || !isFinite(config.tokenExpiry)) {\n throw new ConfigurationError('tokenExpiry must be a finite number', { tokenExpiry: config.tokenExpiry });\n }\n if (config.tokenExpiry <= 0) {\n throw new ConfigurationError('tokenExpiry must be positive', { tokenExpiry: config.tokenExpiry });\n }\n }\n\n if (config.maxRetries !== undefined) {\n if (typeof config.maxRetries !== 'number' || !Number.isInteger(config.maxRetries) || config.maxRetries < 1) {\n throw new ConfigurationError('maxRetries must be a positive integer', { maxRetries: config.maxRetries });\n }\n }\n\n this.config = {\n ...this.config,\n ...config,\n credentials: config.credentials !== undefined ? config.credentials : true,\n };\n\n this._configured = true;\n }\n\n /**\n * Initialize session by calling bootstrap API\n * @param {boolean} forceRefresh - Force a new bootstrap call even if already initialized (used by refreshToken)\n * @returns {Promise<Object>} Bootstrap response\n */\n async initialize(forceRefresh = false) {\n if (typeof window === 'undefined') {\n throw new SSRError('Cannot initialize in non-browser environment (SSR)');\n }\n\n if (this.state.initializationFailed) {\n throw new BootstrapError('Initialization failed after max retries. Reload page to retry.');\n }\n\n // Return existing promise if refresh already in progress\n if (this.initializationPromise) {\n return this.initializationPromise;\n }\n\n // If already initialized in this page session and not a forced refresh, return current state\n if (this.state.isInitialized && !forceRefresh) {\n return { token: this.getToken(this.config.tokenCookieName) };\n }\n\n this.state.isLoading = true;\n this.state.error = null;\n this.notifyListeners();\n\n this.initializationPromise = (async () => {\n let lastError;\n\n for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) {\n try {\n const newSessionId = this.generateSessionId();\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.bootstrapTimeout);\n\n let response;\n try {\n response = await fetch(this.config.bootstrapUrl, {\n method: 'GET',\n credentials: this.config.credentials ? 'include' : 'same-origin',\n signal: controller.signal,\n headers: {\n 'cf-session-id': newSessionId,\n 'x-client-platform': CLIENT_PLATFORM,\n ...this.config.headers,\n },\n });\n } catch (fetchError) {\n if (fetchError.name === 'AbortError') {\n throw new NetworkError(`Bootstrap request timed out after ${this.config.bootstrapTimeout}ms`, {\n timeout: this.config.bootstrapTimeout,\n url: this.config.bootstrapUrl\n });\n }\n throw fetchError;\n } finally {\n clearTimeout(timeoutId);\n }\n\n if (!response.ok) {\n throw new BootstrapError(`Bootstrap failed: ${response.status} ${response.statusText}`, {\n status: response.status,\n statusText: response.statusText,\n url: this.config.bootstrapUrl\n });\n }\n\n let data;\n try {\n data = await response.json();\n } catch (jsonError) {\n throw new BootstrapError('Bootstrap response is not valid JSON', { originalError: jsonError.message });\n }\n\n // Use refresh_time from response (in seconds) or fall back to config.\n // Reject non-positive values to prevent silent cookie expiry failures.\n const refreshInterval =\n typeof data.refresh_time === 'number' && isFinite(data.refresh_time) && data.refresh_time > 0\n ? data.refresh_time * 1000\n : this.config.refreshInterval;\n\n // Calculate token expiry from response (in seconds) or fall back to config.\n // Reject non-positive values to prevent cookies with past expiry dates.\n const tokenExpiry =\n typeof data.expire_time === 'number' && isFinite(data.expire_time) && data.expire_time > 0\n ? data.expire_time * 1000\n : this.config.tokenExpiry;\n\n // If server returned token in response body (non-cookie mode),\n // store it as a client-side cookie. Key matches server's session_header.\n const tokenFromResponse = data[this.config.tokenCookieName];\n if (tokenFromResponse) {\n this.setCookie(this.config.tokenCookieName, tokenFromResponse, tokenExpiry / 1000);\n }\n\n // Now safe to update cf-session-id — new token is stored/set\n this.setCfSessionId(newSessionId, tokenExpiry / 1000);\n\n this.state.isInitialized = true;\n this.state.isLoading = false;\n this.state.lastRefreshTime = Date.now();\n this.state.nextRefreshTime = Date.now() + refreshInterval;\n this.state.tokenExpiry = tokenExpiry;\n this.state.error = null;\n\n // Start automatic refresh timer with dynamic interval\n this.startTokenRefresh(refreshInterval);\n\n this.notifyListeners();\n return data;\n } catch (error) {\n lastError = error instanceof Error ? error : new NetworkError('Unknown error occurred', { error });\n\n if (attempt < this.config.maxRetries) {\n const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n }\n\n this.state.isLoading = false;\n this.state.error = lastError?.message || 'Unknown error';\n this.state.errorCode = lastError?.code || 'UNKNOWN_ERROR';\n this.state.initializationFailed = true;\n this.notifyListeners();\n throw lastError;\n })();\n\n try {\n return await this.initializationPromise;\n } finally {\n this.initializationPromise = null;\n }\n }\n\n /**\n * Check if token refresh is currently in progress\n * @returns {boolean} True if refresh is in progress\n */\n isRefreshing() {\n return this.initializationPromise !== null;\n }\n\n /**\n * Wait for ongoing refresh to complete\n * @returns {Promise<Object>} Bootstrap response from ongoing refresh\n */\n async waitForRefresh() {\n if (this.initializationPromise) {\n return this.initializationPromise;\n }\n return Promise.resolve();\n }\n\n /**\n * Check if the token is likely expired based on in-memory timestamps.\n * After a full browser close + reopen, lastRefreshTime is null, so this returns true.\n * @returns {boolean} True if token appears expired or state is unknown\n */\n _isTokenLikelyExpired() {\n if (!this.state.lastRefreshTime || !this.state.tokenExpiry) return true;\n return Date.now() >= this.state.lastRefreshTime + this.state.tokenExpiry;\n }\n\n /**\n * Ensure the session is ready before making API calls.\n * - If initialized and token not expired, resolves immediately.\n * - If initialization is in progress, waits for it.\n * - If not initialized but configured, triggers initialization and waits.\n * - If not configured or permanently failed, resolves (lets request proceed;\n * the 401 handler in the interceptor will deal with it).\n * @returns {Promise<void>}\n */\n async ensureReady() {\n // Happy path: session is live and token hasn't expired\n if (this.state.isInitialized && !this._isTokenLikelyExpired()) {\n return;\n }\n\n // An initialization / refresh is already in flight — piggyback on it\n if (this.initializationPromise) {\n try {\n await this.initializationPromise;\n } catch (_) {\n // Initialization failed — let the request proceed, 401 handler will deal with it\n }\n return;\n }\n\n // Not initialized / token expired, no call in flight, but we *can* bootstrap\n if (this._configured && !this.state.initializationFailed) {\n try {\n await this.initialize(this.state.isInitialized); // force refresh if already initialized but token expired\n } catch (_) {\n // Initialization failed — nothing we can do, let request proceed\n }\n }\n\n // Not configured or permanently failed — nothing we can do here\n }\n\n /**\n * Check if the session needs a refresh (initialized but token expired).\n * Useful for interceptors to proactively refresh before sending a request.\n * @returns {boolean}\n */\n needsRefresh() {\n return this.state.isInitialized && this._isTokenLikelyExpired();\n }\n\n /**\n * Set cf-session-id in cookie\n * @param {string} sessionId - Session ID\n * @param {number} maxAge - Max age in seconds\n */\n setCfSessionId(sessionId, maxAge) {\n this.state.cfSessionId = sessionId;\n const cookieMaxAge = maxAge || this.config.tokenExpiry / 1000;\n this.setCookie('cf-session-id', sessionId, cookieMaxAge);\n }\n\n /**\n * Generate unique session ID\n * @returns {string} Generated session ID\n */\n generateSessionId() {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }\n\n /**\n * Get cf-session-id for requests\n * @returns {string} Current cf-session-id\n */\n getSessionId() {\n return this.state.cfSessionId;\n }\n\n /**\n * Check if token should be added to headers (HTTP or cross-origin)\n * @returns {boolean} True if token should be added to headers\n */\n shouldUseTokenHeader() {\n if (typeof window === 'undefined') return false;\n return window.location.protocol === 'http:';\n }\n\n /**\n * Shared cookie name sanitizer used by both getCookie and setCookie\n * @param {string} name - Raw cookie name\n * @returns {string} Sanitized cookie name\n */\n _sanitizeCookieName(name) {\n return name.replace(/[^a-zA-Z0-9_-]/g, '');\n }\n\n /**\n * Get cookie value\n * @param {string} name - Cookie name\n * @returns {string|null} Cookie value or null\n */\n getCookie(name) {\n if (typeof document === 'undefined') return null;\n const sanitizedName = this._sanitizeCookieName(name);\n if (!sanitizedName) return null;\n const value = `; ${document.cookie}`;\n const parts = value.split(`; ${sanitizedName}=`);\n if (parts.length === 2) {\n try {\n return decodeURIComponent(parts.pop().split(';').shift());\n } catch (_) {\n return null;\n }\n }\n return null;\n }\n\n /**\n * Get token from cookie\n * @param {string} name - Cookie name (default: configured tokenCookieName)\n * @returns {string|null} Token value or null\n */\n getToken(name = this.config.tokenCookieName) {\n return this.getCookie(name);\n }\n\n /**\n * Set cookie from client-side (WARNING: Not HttpOnly, less secure than server-set cookies)\n * @param {string} name - Cookie name\n * @param {string} value - Cookie value\n * @param {number} maxAge - Max age in seconds\n */\n setCookie(name, value, maxAge) {\n if (typeof document === 'undefined') return;\n\n const sanitizedName = this._sanitizeCookieName(name);\n if (!sanitizedName) return;\n\n const expires = new Date(Date.now() + maxAge * 1000).toUTCString();\n const isSecure = typeof window !== 'undefined' && window.location.protocol === 'https:';\n const secureFlag = isSecure ? '; Secure' : '';\n const encodedValue = encodeURIComponent(value);\n document.cookie = `${sanitizedName}=${encodedValue}; path=/; expires=${expires}; SameSite=Lax${secureFlag}`;\n }\n\n /**\n * Start automatic token refresh timer\n * @param {number} interval - Refresh interval in milliseconds\n */\n startTokenRefresh(interval = this.config.refreshInterval) {\n this.stopTokenRefresh();\n\n this.refreshTimerId = setTimeout(async () => {\n try {\n await this.refreshToken();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n const errorCode = error?.code || 'UNKNOWN_ERROR';\n this.state.error = errorMessage;\n this.state.errorCode = errorCode;\n this.notifyListeners();\n // Schedule retry instead of dying permanently\n const retryDelay = Math.min(interval * 2, 60000);\n this.startTokenRefresh(retryDelay);\n }\n }, interval);\n\n // Listen for visibility changes (tab wake from sleep / bfcache restore)\n this._startVisibilityListener();\n // Listen for network recovery\n this._startOnlineListener();\n }\n\n /**\n * Stop automatic token refresh timer\n */\n stopTokenRefresh() {\n if (this.refreshTimerId) {\n clearTimeout(this.refreshTimerId);\n this.refreshTimerId = null;\n }\n this._stopVisibilityListener();\n this._stopOnlineListener();\n }\n\n /**\n * Start listening for page visibility changes and bfcache restore\n */\n _startVisibilityListener() {\n if (typeof document === 'undefined') return;\n document.addEventListener('visibilitychange', this._boundHandleVisibilityChange);\n if (typeof window !== 'undefined') {\n window.addEventListener('pageshow', this._boundHandlePageShow);\n }\n }\n\n /**\n * Stop listening for page visibility changes\n */\n _stopVisibilityListener() {\n if (typeof document === 'undefined') return;\n document.removeEventListener('visibilitychange', this._boundHandleVisibilityChange);\n if (typeof window !== 'undefined') {\n window.removeEventListener('pageshow', this._boundHandlePageShow);\n }\n }\n\n /**\n * Check if token refresh is overdue and trigger refresh if so\n * @returns {boolean} True if refresh was triggered\n */\n _refreshIfOverdue() {\n if (!this.state.isInitialized || this.state.initializationFailed) return false;\n if (this.isRefreshing()) return false;\n\n const now = Date.now();\n const { nextRefreshTime } = this.state;\n\n if (nextRefreshTime && now >= nextRefreshTime) {\n this.refreshToken().catch(() => {});\n return true;\n }\n return false;\n }\n\n /**\n * Handle visibility change — refresh if overdue when tab becomes visible\n */\n _handleVisibilityChange() {\n if (document.visibilityState !== 'visible') return;\n this._refreshIfOverdue();\n }\n\n /**\n * Handle pageshow — force bootstrap on bfcache restore (browser close + reopen)\n */\n _handlePageShow(event) {\n if (!event.persisted) return;\n // bfcache restore means browser was closed — always force a fresh bootstrap\n if (!this.state.isInitialized || this.state.initializationFailed) return;\n if (this.isRefreshing()) return;\n this.refreshToken().catch(() => {});\n }\n\n /**\n * Start listening for network recovery\n */\n _startOnlineListener() {\n if (typeof window === 'undefined') return;\n window.addEventListener('online', this._boundHandleOnline);\n }\n\n /**\n * Stop listening for network recovery\n */\n _stopOnlineListener() {\n if (typeof window === 'undefined') return;\n window.removeEventListener('online', this._boundHandleOnline);\n }\n\n /**\n * Handle online event — retry if initialization had failed due to network\n */\n _handleOnline() {\n if (!this.state.initializationFailed) return;\n this.refreshToken().catch(() => {});\n }\n\n /**\n * Manually refresh the session token\n * @returns {Promise<Object>} Bootstrap response\n */\n async refreshToken() {\n this.state.initializationFailed = false;\n return this.initialize(true);\n }\n\n /**\n * Get current session status\n * @returns {Object} Current session state\n */\n getSessionStatus() {\n return {\n isInitialized: this.state.isInitialized,\n isLoading: this.state.isLoading,\n lastRefreshTime: this.state.lastRefreshTime,\n nextRefreshTime: this.state.nextRefreshTime,\n tokenExpiry: this.state.tokenExpiry,\n error: this.state.error,\n errorCode: this.state.errorCode,\n initializationFailed: this.state.initializationFailed,\n timeUntilRefresh: this.state.nextRefreshTime\n ? Math.max(0, this.state.nextRefreshTime - Date.now())\n : null,\n };\n }\n\n /**\n * Subscribe to session state changes\n * @param {Function} listener - Callback function to be called on state changes\n * @returns {Function} Unsubscribe function\n */\n subscribe(listener) {\n if (typeof listener !== 'function') {\n throw new TypeError('Listener must be a function');\n }\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Notify all listeners of state changes\n */\n notifyListeners() {\n const status = this.getSessionStatus();\n Array.from(this.listeners).forEach(listener => {\n try {\n listener(status);\n } catch (_) {\n // Silently ignore listener errors\n }\n });\n }\n\n /**\n * Clean up and reset the session manager\n */\n destroy() {\n this.stopTokenRefresh();\n this.initializationPromise = null;\n this.listeners.clear();\n this.setCookie('cf-session-id', '', -1);\n this.setCookie(this.config.tokenCookieName, '', -1);\n this.config = { ...DEFAULT_CONFIG };\n this.state = {\n isInitialized: false,\n isLoading: false,\n lastRefreshTime: null,\n error: null,\n errorCode: null,\n nextRefreshTime: null,\n tokenExpiry: null,\n cfSessionId: null,\n initializationFailed: false,\n };\n this._configured = false;\n }\n\n /**\n * Get the singleton instance\n * @returns {SessionManager} Singleton instance\n */\n static getInstance() {\n if (!SessionManager.instance) {\n SessionManager.instance = new SessionManager();\n }\n return SessionManager.instance;\n }\n}\n\nexport default SessionManager;","import SessionManager from './SessionManager.js';\nexport const CLIENT_PLATFORM = 'web';\n\n/**\n * Fetch interceptor with automatic token refresh on 401\n * @param {string} url - Request URL\n * @param {Object} options - Fetch options\n * @returns {Promise<Response>} Fetch response\n */\nexport async function fetchInterceptor(url, options = {}) {\n const sessionManager = SessionManager.getInstance();\n\n // Gate on session readiness: if not initialized (e.g., browser was closed and\n // reopened), this triggers bootstrap and waits; if a refresh is in progress\n // (tab wake, online recovery, concurrent 401), it piggybacks on it.\n await sessionManager.ensureReady();\n\n // FIX #6: Shallow-clone options to avoid mutating the caller's object\n const requestOptions = {\n ...options,\n credentials: options.credentials || 'include',\n headers: {\n ...options.headers,\n 'cf-session-id': sessionManager.getSessionId(),\n 'x-client-platform': CLIENT_PLATFORM,\n },\n };\n\n // Add token to header if HTTP (not HTTPS)\n if (sessionManager.shouldUseTokenHeader()) {\n const token = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (token) {\n requestOptions.headers[sessionManager.config.tokenCookieName] = token;\n }\n }\n\n let response = await fetch(url, requestOptions);\n\n // Retry once if unauthorized with INVALID_SESSION\n if (response.status === 401) {\n const clonedResponse = response.clone();\n try {\n const data = await clonedResponse.json();\n if (data.error_msg === sessionManager.config.invalidSessionError) {\n if (sessionManager.isRefreshing()) {\n await sessionManager.waitForRefresh();\n } else {\n await sessionManager.refreshToken();\n }\n requestOptions.headers['cf-session-id'] = sessionManager.getSessionId();\n if (sessionManager.shouldUseTokenHeader()) {\n const newToken = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (newToken) {\n requestOptions.headers[sessionManager.config.tokenCookieName] = newToken;\n }\n }\n response = await fetch(url, requestOptions);\n }\n } catch (e) {\n // Not JSON or parsing failed, return original response\n }\n }\n\n return response;\n}\n\n/**\n * Axios interceptor with automatic token refresh on 401 and INVALID_SESSION\n * @param {Object} axiosInstance - Axios instance\n * @returns {Object} The same axios instance with interceptors attached\n */\nexport function setupAxiosInterceptor(axiosInstance) {\n const sessionManager = SessionManager.getInstance();\n\n // FIX #7: Eject previous interceptors if this instance was already set up\n if (axiosInstance._gwSessionRequestId !== undefined) {\n axiosInstance.interceptors.request.eject(axiosInstance._gwSessionRequestId);\n }\n if (axiosInstance._gwSessionResponseId !== undefined) {\n axiosInstance.interceptors.response.eject(axiosInstance._gwSessionResponseId);\n }\n\n // Request interceptor to add cf-session-id, token, and credentials\n axiosInstance._gwSessionRequestId = axiosInstance.interceptors.request.use(\n async (config) => {\n // Gate on session readiness before every request\n await sessionManager.ensureReady();\n if (sessionManager.config.credentials) {\n config.withCredentials = true;\n }\n config.headers['cf-session-id'] = sessionManager.getSessionId();\n config.headers['x-client-platform'] = CLIENT_PLATFORM;\n if (sessionManager.shouldUseTokenHeader()) {\n const token = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (token) {\n config.headers[sessionManager.config.tokenCookieName] = token;\n }\n }\n return config;\n },\n (error) => Promise.reject(error)\n );\n\n // Response interceptor for token refresh\n axiosInstance._gwSessionResponseId = axiosInstance.interceptors.response.use(\n (response) => response,\n async (error) => {\n const originalRequest = error.config;\n\n // Retry once if unauthorized with INVALID_SESSION\n if (error.response?.status === 401 && !originalRequest._retry) {\n if (error.response?.data?.error_msg === sessionManager.config.invalidSessionError) {\n originalRequest._retry = true;\n\n if (sessionManager.isRefreshing()) {\n await sessionManager.waitForRefresh();\n } else {\n await sessionManager.refreshToken();\n }\n\n originalRequest.headers['cf-session-id'] = sessionManager.getSessionId();\n originalRequest.headers['x-client-platform'] = CLIENT_PLATFORM;\n if (sessionManager.shouldUseTokenHeader()) {\n const newToken = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (newToken) {\n originalRequest.headers[sessionManager.config.tokenCookieName] = newToken;\n }\n }\n return axiosInstance(originalRequest);\n }\n }\n\n return Promise.reject(error);\n }\n );\n\n return axiosInstance;\n}","/**\n * Logger utility — disabled for production builds.\n * All methods are no-ops to prevent leaking internal details to the browser console.\n */\n\nconst LOG_LEVELS = {\n NONE: 0,\n ERROR: 1,\n WARN: 2,\n INFO: 3,\n DEBUG: 4\n};\n\nclass Logger {\n setLevel() {}\n error() {}\n warn() {}\n info() {}\n debug() {}\n}\n\nexport const logger = new Logger();\nexport { LOG_LEVELS };\n","import { useState, useEffect, useCallback, useRef } from 'react';\nimport SessionManager from '../SessionManager.js';\n\n/**\n * React hook for session management\n * Provides session state and controls for React components\n *\n * @param {Object} options - Configuration options\n * @param {boolean} options.autoInitialize - Automatically initialize on mount (default: true)\n * @returns {Object} Session state and controls\n */\nexport function useSession(options = {}) {\n const { autoInitialize = true } = options;\n\n const sessionManager = SessionManager.getInstance();\n\n const [sessionState, setSessionState] = useState(() =>\n sessionManager.getSessionStatus()\n );\n\n // FIX #11: Separate countdown state — only this re-renders every second,\n // not the full session state which would cause unnecessary subscriber churn\n const [timeUntilRefresh, setTimeUntilRefresh] = useState(() => {\n const { nextRefreshTime } = sessionManager.getSessionStatus();\n return nextRefreshTime ? Math.max(0, nextRefreshTime - Date.now()) : null;\n });\n\n const nextRefreshTimeRef = useRef(sessionState.nextRefreshTime);\n\n // Subscribe to meaningful session state changes only\n useEffect(() => {\n const unsubscribe = sessionManager.subscribe((newState) => {\n setSessionState(newState);\n nextRefreshTimeRef.current = newState.nextRefreshTime;\n });\n\n return unsubscribe;\n }, []); // sessionManager is a singleton, no need to include in deps\n\n // Local tick timer for countdown display — avoids global tick in SessionManager\n useEffect(() => {\n if (!sessionState.nextRefreshTime) {\n setTimeUntilRefresh(null);\n return;\n }\n // Set immediately on mount / when nextRefreshTime changes\n setTimeUntilRefresh(Math.max(0, sessionState.nextRefreshTime - Date.now()));\n const tickId = setInterval(() => {\n const nrt = nextRefreshTimeRef.current;\n setTimeUntilRefresh(nrt ? Math.max(0, nrt - Date.now()) : null);\n }, 1000);\n return () => clearInterval(tickId);\n }, [sessionState.nextRefreshTime]);\n\n // Auto-initialize if enabled\n useEffect(() => {\n if (autoInitialize && !sessionState.isInitialized && !sessionState.isLoading && !sessionState.initializationFailed) {\n sessionManager.initialize().catch(() => {});\n }\n }, [autoInitialize, sessionState.isInitialized, sessionState.isLoading, sessionState.initializationFailed]);\n\n // Manual refresh function\n const refresh = useCallback(async () => {\n await sessionManager.refreshToken();\n }, []);\n\n // Initialize function (for manual control)\n const initialize = useCallback(async () => {\n await sessionManager.initialize();\n }, []);\n\n return {\n // State\n isInitialized: sessionState.isInitialized,\n isLoading: sessionState.isLoading,\n error: sessionState.error,\n lastRefreshTime: sessionState.lastRefreshTime,\n nextRefreshTime: sessionState.nextRefreshTime,\n timeUntilRefresh,\n initializationFailed: sessionState.initializationFailed,\n\n // Actions\n refresh,\n initialize,\n };\n}\n\nexport default useSession;"],"names":["SessionError","Error","constructor","message","code","details","super","this","name","captureStackTrace","Object","setPrototypeOf","prototype","ConfigurationError","BootstrapError","NetworkError","SSRError","DEFAULT_CONFIG","bootstrapUrl","refreshInterval","tokenExpiry","maxRetries","bootstrapTimeout","tokenCookieName","invalidSessionError","SessionManager","instance","config","state","isInitialized","isLoading","lastRefreshTime","error","errorCode","nextRefreshTime","cfSessionId","initializationFailed","refreshTimerId","listeners","Set","initializationPromise","_configured","_boundHandleVisibilityChange","_handleVisibilityChange","bind","_boundHandlePageShow","_handlePageShow","_boundHandleOnline","_handleOnline","configure","trim","undefined","isFinite","Number","isInteger","credentials","initialize","forceRefresh","window","token","getToken","notifyListeners","lastError","attempt","newSessionId","generateSessionId","controller","AbortController","timeoutId","setTimeout","abort","response","data","fetch","method","signal","headers","fetchError","timeout","url","clearTimeout","ok","status","statusText","json","jsonError","originalError","refresh_time","expire_time","tokenFromResponse","setCookie","setCfSessionId","Date","now","startTokenRefresh","delay","Math","min","pow","Promise","resolve","isRefreshing","waitForRefresh","_isTokenLikelyExpired","ensureReady","_","needsRefresh","sessionId","maxAge","cookieMaxAge","crypto","randomUUID","random","toString","substr","getSessionId","shouldUseTokenHeader","location","protocol","_sanitizeCookieName","replace","getCookie","document","sanitizedName","parts","cookie","split","length","decodeURIComponent","pop","shift","value","expires","toUTCString","secureFlag","encodedValue","encodeURIComponent","interval","stopTokenRefresh","async","refreshToken","errorMessage","String","retryDelay","_startVisibilityListener","_startOnlineListener","_stopVisibilityListener","_stopOnlineListener","addEventListener","removeEventListener","_refreshIfOverdue","catch","visibilityState","event","persisted","getSessionStatus","timeUntilRefresh","max","subscribe","listener","TypeError","add","delete","Array","from","forEach","destroy","clear","getInstance","CLIENT_PLATFORM","logger","setLevel","warn","info","debug","NONE","ERROR","WARN","INFO","DEBUG","options","sessionManager","requestOptions","clonedResponse","clone","error_msg","newToken","e","axiosInstance","_gwSessionRequestId","interceptors","request","eject","_gwSessionResponseId","use","withCredentials","reject","originalRequest","_retry","autoInitialize","sessionState","setSessionState","useState","setTimeUntilRefresh","nextRefreshTimeRef","useRef","useEffect","newState","current","tickId","setInterval","nrt","clearInterval","refresh","useCallback"],"mappings":"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"}
package/dist/index.esm.js CHANGED
@@ -1,2 +1,2 @@
1
- import{useState as e,useEffect as t,useCallback as i}from"react";class s extends Error{constructor(e,t,i={}){super(e),this.name="SessionError",this.code=t,this.details=i,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),Object.setPrototypeOf(this,s.prototype)}}class r extends s{constructor(e,t){super(e,"CONFIGURATION_ERROR",t),this.name="ConfigurationError"}}class n extends s{constructor(e,t){super(e,"BOOTSTRAP_ERROR",t),this.name="BootstrapError"}}class o extends s{constructor(e,t){super(e,"NETWORK_ERROR",t),this.name="NetworkError"}}class a extends s{constructor(e,t={}){super(e,"SSR_ERROR",t),this.name="SSRError"}}const h={NONE:0,ERROR:1,WARN:2,INFO:3,DEBUG:4};const l=new class{constructor(){this.level=h.WARN}setLevel(e){this.level="string"==typeof e?h[e.toUpperCase()]??h.WARN:e}error(...e){this.level>=h.ERROR&&console.error("[SessionManager]",...e)}warn(...e){this.level>=h.WARN&&console.warn("[SessionManager]",...e)}info(...e){this.level>=h.INFO&&console.info("[SessionManager]",...e)}debug(...e){this.level>=h.DEBUG&&console.debug("[SessionManager]",...e)}};class c{constructor(){if(c.instance)return c.instance;this.config={bootstrapUrl:"/session/bootstrap",refreshInterval:15e5,tokenExpiry:18e5,maxRetries:3,tokenCookieName:"token"},this.state={isInitialized:!1,isLoading:!1,lastRefreshTime:null,error:null,errorCode:null,nextRefreshTime:null,tokenExpiry:null,cfSessionId:null,initializationFailed:!1},this.refreshTimerId=null,this.tickTimerId=null,this.listeners=new Set,this.initializationPromise=null,c.instance=this}configure(e={}){if(!e.bootstrapUrl||"string"!=typeof e.bootstrapUrl||!e.bootstrapUrl.trim())throw new r("bootstrapUrl is required and must be a non-empty string",{bootstrapUrl:e.bootstrapUrl});if(void 0!==e.refreshInterval){if("number"!=typeof e.refreshInterval||!isFinite(e.refreshInterval))throw new r("refreshInterval must be a finite number",{refreshInterval:e.refreshInterval});if(e.refreshInterval<=0)throw new r("refreshInterval must be positive",{refreshInterval:e.refreshInterval})}if(void 0!==e.tokenExpiry){if("number"!=typeof e.tokenExpiry||!isFinite(e.tokenExpiry))throw new r("tokenExpiry must be a finite number",{tokenExpiry:e.tokenExpiry});if(e.tokenExpiry<=0)throw new r("tokenExpiry must be positive",{tokenExpiry:e.tokenExpiry})}this.config={...this.config,...e,credentials:void 0===e.credentials||e.credentials,logLevel:e.logLevel||"WARN"},l.setLevel(this.config.logLevel),this.config.refreshInterval&&this.config.tokenExpiry&&this.config.refreshInterval>=this.config.tokenExpiry&&l.warn("refreshInterval should be less than tokenExpiry to prevent race conditions")}async initialize(e=!1){if("undefined"==typeof window)throw new a("Cannot initialize in non-browser environment (SSR)");if(this.state.initializationFailed)throw l.warn("Initialization previously failed. Reload page to retry."),new n("Initialization failed after max retries. Reload page to retry.");if(this.initializationPromise)return l.debug("Initialization already in progress, waiting..."),this.initializationPromise;if(!e){if("true"===this.getCookie("session_initialized")){l.info("Session already initialized, skipping bootstrap API call"),this.state.isInitialized=!0,this.state.tokenExpiry=this.config.tokenExpiry;const e=this.getCookie("cf-session-id");e?this.state.cfSessionId=e:this.setCfSessionId(this.generateSessionId());const t=parseInt(this.getCookie("session_refresh_at"),10);let i;return i=t&&!isNaN(t)?Math.max(0,t-Date.now()):this.config.refreshInterval,this.state.lastRefreshTime=Date.now(),this.state.nextRefreshTime=Date.now()+i,this.startTokenRefresh(i),this.notifyListeners(),{token:this.getToken(this.config.tokenCookieName)}}}this.state.isLoading=!0,this.state.error=null,this.notifyListeners(),this.initializationPromise=(async()=>{let e;for(let t=1;t<=this.config.maxRetries;t++)try{const e=this.generateSessionId();this.setCfSessionId(e),l.info(`Calling bootstrap API (attempt ${t}/${this.config.maxRetries}):`,this.config.bootstrapUrl);const i=await fetch(this.config.bootstrapUrl,{method:"GET",credentials:this.config.credentials?"include":"same-origin",headers:{"Content-Type":"application/json","cf-session-id":this.state.cfSessionId,"x-client-platform":"web",...this.config.headers}});if(!i.ok)throw new n(`Bootstrap failed: ${i.status} ${i.statusText}`,{status:i.status,statusText:i.statusText,url:this.config.bootstrapUrl});let s;try{s=await i.json()}catch(e){throw new n("Bootstrap response is not valid JSON",{originalError:e.message})}l.info("Bootstrap successful");const r=i.headers.get("set-cookie"),o=r&&r.includes(this.config.tokenCookieName),a=s.refresh_time?1e3*s.refresh_time:this.config.refreshInterval,h=s.expire_time?1e3*s.expire_time:this.config.tokenExpiry;return s.token&&!o?(l.debug("Server did not set cookie, setting from SDK"),this.setCookie(this.config.tokenCookieName,s.token,h/1e3)):o&&l.debug("Server set cookie, skipping SDK cookie logic"),this.setCfSessionId(this.state.cfSessionId,h/1e3),this.state.isInitialized=!0,this.state.isLoading=!1,this.state.lastRefreshTime=Date.now(),this.state.nextRefreshTime=Date.now()+a,this.state.tokenExpiry=h,this.state.error=null,this.setCookie("session_initialized","true",a/1e3),this.setCookie("session_refresh_at",String(Date.now()+a),a/1e3),this.startTokenRefresh(a),this.notifyListeners(),s}catch(i){if(e=i instanceof Error?i:new o("Unknown error occurred",{error:i}),l.error(`Bootstrap attempt ${t} failed:`,e.message),t<this.config.maxRetries){const e=Math.min(1e3*Math.pow(2,t-1),5e3);l.info(`Retrying in ${e}ms...`),await new Promise(t=>setTimeout(t,e))}}throw l.error("All bootstrap attempts failed"),this.state.isLoading=!1,this.state.error=e?.message||"Unknown error",this.state.errorCode=e?.code||"UNKNOWN_ERROR",this.state.initializationFailed=!0,this.notifyListeners(),e})();try{return await this.initializationPromise}finally{this.initializationPromise=null}}isRefreshing(){return null!==this.initializationPromise}async waitForRefresh(){return this.initializationPromise?(l.debug("Waiting for ongoing refresh to complete"),this.initializationPromise):Promise.resolve()}setCfSessionId(e,t){this.state.cfSessionId=e;const i=t||this.config.tokenExpiry/1e3;this.setCookie("cf-session-id",e,i)}generateSessionId(){return"undefined"!=typeof crypto&&crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substr(2,9)}`}getSessionId(){return this.state.cfSessionId}shouldUseTokenHeader(){return"undefined"!=typeof window&&"http:"===window.location.protocol}getCookie(e){if("undefined"==typeof document)return null;const t=`; ${document.cookie}`.split(`; ${e}=`);if(2===t.length)try{return decodeURIComponent(t.pop().split(";").shift())}catch(e){return l.error("Failed to decode cookie value:",e instanceof Error?e.message:String(e)),null}return null}getToken(e=this.config.tokenCookieName){return this.getCookie(e)}setCookie(e,t,i){if("undefined"==typeof document)return void l.warn("Cannot set cookie in non-browser environment");const s=e.replace(/[^a-zA-Z0-9_-]/g,"");if(!s)return void l.error("Invalid cookie name provided");const r=new Date(Date.now()+1e3*i).toUTCString(),n="undefined"!=typeof window&&"https:"===window.location.protocol,o=n?"; Secure":"",a=encodeURIComponent(t);document.cookie=`${s}=${a}; path=/; expires=${r}; SameSite=Lax${o}`,l.debug(`Cookie set: ${s}, expires in ${i}s${n?" (Secure)":""} [WARNING: Not HttpOnly]`)}startTokenRefresh(e=this.config.refreshInterval){this.stopTokenRefresh(),l.info(`Scheduling token refresh in ${e/1e3} seconds`),this.refreshTimerId=setTimeout(async()=>{l.info("Auto-refreshing token...");try{await this.refreshToken()}catch(e){const t=e instanceof Error?e.message:String(e),i=e?.code||"UNKNOWN_ERROR";l.error("Auto-refresh failed:",t),this.state.error=t,this.state.errorCode=i,this.notifyListeners()}},e),this.startTickTimer()}stopTokenRefresh(){this.refreshTimerId&&(clearTimeout(this.refreshTimerId),this.refreshTimerId=null,l.debug("Token refresh timer stopped")),this.stopTickTimer()}startTickTimer(){this.stopTickTimer(),this.tickTimerId=setInterval(()=>{this.notifyListeners()},1e3)}stopTickTimer(){this.tickTimerId&&(clearInterval(this.tickTimerId),this.tickTimerId=null)}async refreshToken(){return l.info("Manual token refresh triggered"),this.state.initializationFailed=!1,this.initialize(!0)}getSessionStatus(){return{isInitialized:this.state.isInitialized,isLoading:this.state.isLoading,lastRefreshTime:this.state.lastRefreshTime,nextRefreshTime:this.state.nextRefreshTime,tokenExpiry:this.state.tokenExpiry,error:this.state.error,errorCode:this.state.errorCode,initializationFailed:this.state.initializationFailed,timeUntilRefresh:this.state.nextRefreshTime?Math.max(0,this.state.nextRefreshTime-Date.now()):null}}subscribe(e){if("function"!=typeof e)throw new TypeError("Listener must be a function");return this.listeners.add(e),()=>{this.listeners.delete(e)}}notifyListeners(){const e=this.getSessionStatus();Array.from(this.listeners).forEach(t=>{try{t(e)}catch(e){const t=e instanceof Error?e.message:String(e);l.error("Listener error:",t)}})}destroy(){this.stopTokenRefresh(),this.listeners.clear(),this.state={isInitialized:!1,isLoading:!1,lastRefreshTime:null,error:null,errorCode:null,nextRefreshTime:null,tokenExpiry:null,cfSessionId:null,initializationFailed:!1},this.setCookie("cf-session-id","",-1),this.setCookie("session_initialized","",-1),this.setCookie("session_refresh_at","",-1),l.info("Destroyed")}static getInstance(){return c.instance||(c.instance=new c),c.instance}}function f(s={}){const{autoInitialize:r=!0}=s,n=c.getInstance(),[o,a]=e(()=>n.getSessionStatus());t(()=>n.subscribe(e=>{a(e)}),[]),t(()=>{!r||o.isInitialized||o.isLoading||o.initializationFailed||n.initialize().catch(e=>{l.error("Auto-initialization failed:",e)})},[r,o.isInitialized,o.isLoading,o.initializationFailed]);const h=i(async()=>{try{await n.refreshToken()}catch(e){throw l.error("Manual refresh failed:",e),e}},[]),f=i(async()=>{try{await n.initialize()}catch(e){throw l.error("Manual initialization failed:",e),e}},[]);return{isInitialized:o.isInitialized,isLoading:o.isLoading,error:o.error,lastRefreshTime:o.lastRefreshTime,nextRefreshTime:o.nextRefreshTime,timeUntilRefresh:o.timeUntilRefresh,initializationFailed:o.initializationFailed,refresh:h,initialize:f}}const d="web";async function u(e,t={}){const i=c.getInstance();if(t.credentials=t.credentials||"include",t.headers={...t.headers,"cf-session-id":i.getSessionId(),"x-client-platform":d},i.shouldUseTokenHeader()){const e=i.getToken(i.config.tokenCookieName);e&&(t.headers[i.config.tokenCookieName]=e)}let s=await fetch(e,t);if(401===s.status){const r=s.clone();try{if("INVALID_SESSION"===(await r.json()).error_msg){if(i.isRefreshing())await i.waitForRefresh();else{i.getToken(i.config.tokenCookieName)||await i.refreshToken()}if(t.headers["cf-session-id"]=i.getSessionId(),t.headers["x-client-platform"]=d,i.shouldUseTokenHeader()){const e=i.getToken(i.config.tokenCookieName);e&&(t.headers[i.config.tokenCookieName]=e)}s=await fetch(e,t)}}catch(e){}}return s}function g(e){const t=c.getInstance();return e.interceptors.request.use(e=>{if(e.headers["cf-session-id"]=t.getSessionId(),e.headers["x-client-platform"]=d,t.shouldUseTokenHeader()){const i=t.getToken(t.config.tokenCookieName);i&&(e.headers[t.config.tokenCookieName]=i)}return e},e=>Promise.reject(e)),e.interceptors.response.use(e=>e,async i=>{const s=i.config;if(401===i.response?.status&&!s._retry&&"INVALID_SESSION"===i.response?.data?.error_msg){if(s._retry=!0,t.isRefreshing())await t.waitForRefresh();else{t.getToken(t.config.tokenCookieName)||await t.refreshToken()}if(s.headers["cf-session-id"]=t.getSessionId(),s.headers["x-client-platform"]=d,t.shouldUseTokenHeader()){const e=t.getToken(t.config.tokenCookieName);e&&(s.headers[t.config.tokenCookieName]=e)}return e(s)}return Promise.reject(i)}),e}export{n as BootstrapError,d as CLIENT_PLATFORM,r as ConfigurationError,h as LOG_LEVELS,o as NetworkError,a as SSRError,s as SessionError,c as SessionManager,c as default,u as fetchInterceptor,l as logger,g as setupAxiosInterceptor,f as useSession};
1
+ import{useState as e,useRef as t,useEffect as i,useCallback as s}from"react";class n extends Error{constructor(e,t,i={}){super(e),this.name="SessionError",this.code=t,this.details=i,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),Object.setPrototypeOf(this,n.prototype)}}class r extends n{constructor(e,t){super(e,"CONFIGURATION_ERROR",t),this.name="ConfigurationError"}}class o extends n{constructor(e,t){super(e,"BOOTSTRAP_ERROR",t),this.name="BootstrapError"}}class a extends n{constructor(e,t){super(e,"NETWORK_ERROR",t),this.name="NetworkError"}}class h extends n{constructor(e,t={}){super(e,"SSR_ERROR",t),this.name="SSRError"}}const l={bootstrapUrl:"/session/bootstrap",refreshInterval:15e5,tokenExpiry:18e5,maxRetries:3,bootstrapTimeout:3e4,tokenCookieName:"token",invalidSessionError:"INVALID-GW-SESSION"};class d{constructor(){if(d.instance)return d.instance;this.config={...l},this.state={isInitialized:!1,isLoading:!1,lastRefreshTime:null,error:null,errorCode:null,nextRefreshTime:null,tokenExpiry:null,cfSessionId:null,initializationFailed:!1},this.refreshTimerId=null,this.listeners=new Set,this.initializationPromise=null,this._configured=!1,this._boundHandleVisibilityChange=this._handleVisibilityChange.bind(this),this._boundHandlePageShow=this._handlePageShow.bind(this),this._boundHandleOnline=this._handleOnline.bind(this),d.instance=this}configure(e={}){if(!e.bootstrapUrl||"string"!=typeof e.bootstrapUrl||!e.bootstrapUrl.trim())throw new r("bootstrapUrl is required and must be a non-empty string",{bootstrapUrl:e.bootstrapUrl});if(void 0!==e.refreshInterval){if("number"!=typeof e.refreshInterval||!isFinite(e.refreshInterval))throw new r("refreshInterval must be a finite number",{refreshInterval:e.refreshInterval});if(e.refreshInterval<=0)throw new r("refreshInterval must be positive",{refreshInterval:e.refreshInterval})}if(void 0!==e.tokenExpiry){if("number"!=typeof e.tokenExpiry||!isFinite(e.tokenExpiry))throw new r("tokenExpiry must be a finite number",{tokenExpiry:e.tokenExpiry});if(e.tokenExpiry<=0)throw new r("tokenExpiry must be positive",{tokenExpiry:e.tokenExpiry})}if(void 0!==e.maxRetries&&("number"!=typeof e.maxRetries||!Number.isInteger(e.maxRetries)||e.maxRetries<1))throw new r("maxRetries must be a positive integer",{maxRetries:e.maxRetries});this.config={...this.config,...e,credentials:void 0===e.credentials||e.credentials},this._configured=!0}async initialize(e=!1){if("undefined"==typeof window)throw new h("Cannot initialize in non-browser environment (SSR)");if(this.state.initializationFailed)throw new o("Initialization failed after max retries. Reload page to retry.");if(this.initializationPromise)return this.initializationPromise;if(this.state.isInitialized&&!e)return{token:this.getToken(this.config.tokenCookieName)};this.state.isLoading=!0,this.state.error=null,this.notifyListeners(),this.initializationPromise=(async()=>{let e;for(let t=1;t<=this.config.maxRetries;t++)try{const e=this.generateSessionId(),t=new AbortController,i=setTimeout(()=>t.abort(),this.config.bootstrapTimeout);let s,n;try{s=await fetch(this.config.bootstrapUrl,{method:"GET",credentials:this.config.credentials?"include":"same-origin",signal:t.signal,headers:{"cf-session-id":e,"x-client-platform":"web",...this.config.headers}})}catch(e){if("AbortError"===e.name)throw new a(`Bootstrap request timed out after ${this.config.bootstrapTimeout}ms`,{timeout:this.config.bootstrapTimeout,url:this.config.bootstrapUrl});throw e}finally{clearTimeout(i)}if(!s.ok)throw new o(`Bootstrap failed: ${s.status} ${s.statusText}`,{status:s.status,statusText:s.statusText,url:this.config.bootstrapUrl});try{n=await s.json()}catch(e){throw new o("Bootstrap response is not valid JSON",{originalError:e.message})}const r="number"==typeof n.refresh_time&&isFinite(n.refresh_time)&&n.refresh_time>0?1e3*n.refresh_time:this.config.refreshInterval,h="number"==typeof n.expire_time&&isFinite(n.expire_time)&&n.expire_time>0?1e3*n.expire_time:this.config.tokenExpiry,l=n[this.config.tokenCookieName];return l&&this.setCookie(this.config.tokenCookieName,l,h/1e3),this.setCfSessionId(e,h/1e3),this.state.isInitialized=!0,this.state.isLoading=!1,this.state.lastRefreshTime=Date.now(),this.state.nextRefreshTime=Date.now()+r,this.state.tokenExpiry=h,this.state.error=null,this.startTokenRefresh(r),this.notifyListeners(),n}catch(i){if(e=i instanceof Error?i:new a("Unknown error occurred",{error:i}),t<this.config.maxRetries){const e=Math.min(1e3*Math.pow(2,t-1),5e3);await new Promise(t=>setTimeout(t,e))}}throw this.state.isLoading=!1,this.state.error=e?.message||"Unknown error",this.state.errorCode=e?.code||"UNKNOWN_ERROR",this.state.initializationFailed=!0,this.notifyListeners(),e})();try{return await this.initializationPromise}finally{this.initializationPromise=null}}isRefreshing(){return null!==this.initializationPromise}async waitForRefresh(){return this.initializationPromise?this.initializationPromise:Promise.resolve()}_isTokenLikelyExpired(){return!this.state.lastRefreshTime||!this.state.tokenExpiry||Date.now()>=this.state.lastRefreshTime+this.state.tokenExpiry}async ensureReady(){if(!this.state.isInitialized||this._isTokenLikelyExpired())if(this.initializationPromise)try{await this.initializationPromise}catch(e){}else if(this._configured&&!this.state.initializationFailed)try{await this.initialize(this.state.isInitialized)}catch(e){}}needsRefresh(){return this.state.isInitialized&&this._isTokenLikelyExpired()}setCfSessionId(e,t){this.state.cfSessionId=e;const i=t||this.config.tokenExpiry/1e3;this.setCookie("cf-session-id",e,i)}generateSessionId(){return"undefined"!=typeof crypto&&crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substr(2,9)}`}getSessionId(){return this.state.cfSessionId}shouldUseTokenHeader(){return"undefined"!=typeof window&&"http:"===window.location.protocol}_sanitizeCookieName(e){return e.replace(/[^a-zA-Z0-9_-]/g,"")}getCookie(e){if("undefined"==typeof document)return null;const t=this._sanitizeCookieName(e);if(!t)return null;const i=`; ${document.cookie}`.split(`; ${t}=`);if(2===i.length)try{return decodeURIComponent(i.pop().split(";").shift())}catch(e){return null}return null}getToken(e=this.config.tokenCookieName){return this.getCookie(e)}setCookie(e,t,i){if("undefined"==typeof document)return;const s=this._sanitizeCookieName(e);if(!s)return;const n=new Date(Date.now()+1e3*i).toUTCString(),r="undefined"!=typeof window&&"https:"===window.location.protocol?"; Secure":"",o=encodeURIComponent(t);document.cookie=`${s}=${o}; path=/; expires=${n}; SameSite=Lax${r}`}startTokenRefresh(e=this.config.refreshInterval){this.stopTokenRefresh(),this.refreshTimerId=setTimeout(async()=>{try{await this.refreshToken()}catch(t){const i=t instanceof Error?t.message:String(t),s=t?.code||"UNKNOWN_ERROR";this.state.error=i,this.state.errorCode=s,this.notifyListeners();const n=Math.min(2*e,6e4);this.startTokenRefresh(n)}},e),this._startVisibilityListener(),this._startOnlineListener()}stopTokenRefresh(){this.refreshTimerId&&(clearTimeout(this.refreshTimerId),this.refreshTimerId=null),this._stopVisibilityListener(),this._stopOnlineListener()}_startVisibilityListener(){"undefined"!=typeof document&&(document.addEventListener("visibilitychange",this._boundHandleVisibilityChange),"undefined"!=typeof window&&window.addEventListener("pageshow",this._boundHandlePageShow))}_stopVisibilityListener(){"undefined"!=typeof document&&(document.removeEventListener("visibilitychange",this._boundHandleVisibilityChange),"undefined"!=typeof window&&window.removeEventListener("pageshow",this._boundHandlePageShow))}_refreshIfOverdue(){if(!this.state.isInitialized||this.state.initializationFailed)return!1;if(this.isRefreshing())return!1;const e=Date.now(),{nextRefreshTime:t}=this.state;return!!(t&&e>=t)&&(this.refreshToken().catch(()=>{}),!0)}_handleVisibilityChange(){"visible"===document.visibilityState&&this._refreshIfOverdue()}_handlePageShow(e){e.persisted&&this.state.isInitialized&&!this.state.initializationFailed&&(this.isRefreshing()||this.refreshToken().catch(()=>{}))}_startOnlineListener(){"undefined"!=typeof window&&window.addEventListener("online",this._boundHandleOnline)}_stopOnlineListener(){"undefined"!=typeof window&&window.removeEventListener("online",this._boundHandleOnline)}_handleOnline(){this.state.initializationFailed&&this.refreshToken().catch(()=>{})}async refreshToken(){return this.state.initializationFailed=!1,this.initialize(!0)}getSessionStatus(){return{isInitialized:this.state.isInitialized,isLoading:this.state.isLoading,lastRefreshTime:this.state.lastRefreshTime,nextRefreshTime:this.state.nextRefreshTime,tokenExpiry:this.state.tokenExpiry,error:this.state.error,errorCode:this.state.errorCode,initializationFailed:this.state.initializationFailed,timeUntilRefresh:this.state.nextRefreshTime?Math.max(0,this.state.nextRefreshTime-Date.now()):null}}subscribe(e){if("function"!=typeof e)throw new TypeError("Listener must be a function");return this.listeners.add(e),()=>{this.listeners.delete(e)}}notifyListeners(){const e=this.getSessionStatus();Array.from(this.listeners).forEach(t=>{try{t(e)}catch(e){}})}destroy(){this.stopTokenRefresh(),this.initializationPromise=null,this.listeners.clear(),this.setCookie("cf-session-id","",-1),this.setCookie(this.config.tokenCookieName,"",-1),this.config={...l},this.state={isInitialized:!1,isLoading:!1,lastRefreshTime:null,error:null,errorCode:null,nextRefreshTime:null,tokenExpiry:null,cfSessionId:null,initializationFailed:!1},this._configured=!1}static getInstance(){return d.instance||(d.instance=new d),d.instance}}function f(n={}){const{autoInitialize:r=!0}=n,o=d.getInstance(),[a,h]=e(()=>o.getSessionStatus()),[l,f]=e(()=>{const{nextRefreshTime:e}=o.getSessionStatus();return e?Math.max(0,e-Date.now()):null}),c=t(a.nextRefreshTime);i(()=>o.subscribe(e=>{h(e),c.current=e.nextRefreshTime}),[]),i(()=>{if(!a.nextRefreshTime)return void f(null);f(Math.max(0,a.nextRefreshTime-Date.now()));const e=setInterval(()=>{const e=c.current;f(e?Math.max(0,e-Date.now()):null)},1e3);return()=>clearInterval(e)},[a.nextRefreshTime]),i(()=>{!r||a.isInitialized||a.isLoading||a.initializationFailed||o.initialize().catch(()=>{})},[r,a.isInitialized,a.isLoading,a.initializationFailed]);const u=s(async()=>{await o.refreshToken()},[]),m=s(async()=>{await o.initialize()},[]);return{isInitialized:a.isInitialized,isLoading:a.isLoading,error:a.error,lastRefreshTime:a.lastRefreshTime,nextRefreshTime:a.nextRefreshTime,timeUntilRefresh:l,initializationFailed:a.initializationFailed,refresh:u,initialize:m}}const c="web";async function u(e,t={}){const i=d.getInstance();await i.ensureReady();const s={...t,credentials:t.credentials||"include",headers:{...t.headers,"cf-session-id":i.getSessionId(),"x-client-platform":c}};if(i.shouldUseTokenHeader()){const e=i.getToken(i.config.tokenCookieName);e&&(s.headers[i.config.tokenCookieName]=e)}let n=await fetch(e,s);if(401===n.status){const t=n.clone();try{if((await t.json()).error_msg===i.config.invalidSessionError){if(i.isRefreshing()?await i.waitForRefresh():await i.refreshToken(),s.headers["cf-session-id"]=i.getSessionId(),i.shouldUseTokenHeader()){const e=i.getToken(i.config.tokenCookieName);e&&(s.headers[i.config.tokenCookieName]=e)}n=await fetch(e,s)}}catch(e){}}return n}function m(e){const t=d.getInstance();return void 0!==e._gwSessionRequestId&&e.interceptors.request.eject(e._gwSessionRequestId),void 0!==e._gwSessionResponseId&&e.interceptors.response.eject(e._gwSessionResponseId),e._gwSessionRequestId=e.interceptors.request.use(async e=>{if(await t.ensureReady(),t.config.credentials&&(e.withCredentials=!0),e.headers["cf-session-id"]=t.getSessionId(),e.headers["x-client-platform"]=c,t.shouldUseTokenHeader()){const i=t.getToken(t.config.tokenCookieName);i&&(e.headers[t.config.tokenCookieName]=i)}return e},e=>Promise.reject(e)),e._gwSessionResponseId=e.interceptors.response.use(e=>e,async i=>{const s=i.config;if(401===i.response?.status&&!s._retry&&i.response?.data?.error_msg===t.config.invalidSessionError){if(s._retry=!0,t.isRefreshing()?await t.waitForRefresh():await t.refreshToken(),s.headers["cf-session-id"]=t.getSessionId(),s.headers["x-client-platform"]=c,t.shouldUseTokenHeader()){const e=t.getToken(t.config.tokenCookieName);e&&(s.headers[t.config.tokenCookieName]=e)}return e(s)}return Promise.reject(i)}),e}const p={NONE:0,ERROR:1,WARN:2,INFO:3,DEBUG:4};const g=new class{setLevel(){}error(){}warn(){}info(){}debug(){}};export{o as BootstrapError,c as CLIENT_PLATFORM,r as ConfigurationError,p as LOG_LEVELS,a as NetworkError,h as SSRError,n as SessionError,d as SessionManager,d as default,u as fetchInterceptor,g as logger,m as setupAxiosInterceptor,f as useSession};
2
2
  //# sourceMappingURL=index.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../src/errors.js","../src/logger.js","../src/SessionManager.js","../src/hooks/useSession.js","../src/interceptors.js"],"sourcesContent":["/**\n * Custom error classes for SessionManager\n */\n\nexport class SessionError extends Error {\n constructor(message, code, details = {}) {\n super(message);\n this.name = 'SessionError';\n this.code = code;\n this.details = details;\n \n // Maintains proper stack trace for where error was thrown\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n \n // Set the prototype explicitly for proper instanceof checks\n Object.setPrototypeOf(this, SessionError.prototype);\n }\n}\n\nexport class ConfigurationError extends SessionError {\n constructor(message, details) {\n super(message, 'CONFIGURATION_ERROR', details);\n this.name = 'ConfigurationError';\n }\n}\n\nexport class BootstrapError extends SessionError {\n constructor(message, details) {\n super(message, 'BOOTSTRAP_ERROR', details);\n this.name = 'BootstrapError';\n }\n}\n\nexport class NetworkError extends SessionError {\n constructor(message, details) {\n super(message, 'NETWORK_ERROR', details);\n this.name = 'NetworkError';\n }\n}\n\nexport class SSRError extends SessionError {\n constructor(message, details = {}) {\n super(message, 'SSR_ERROR', details);\n this.name = 'SSRError';\n }\n}\n","/**\n * Logger utility with configurable log levels\n */\n\nconst LOG_LEVELS = {\n NONE: 0,\n ERROR: 1,\n WARN: 2,\n INFO: 3,\n DEBUG: 4\n};\n\nclass Logger {\n constructor() {\n this.level = LOG_LEVELS.WARN; // Default to WARN in production\n }\n\n setLevel(level) {\n if (typeof level === 'string') {\n this.level = LOG_LEVELS[level.toUpperCase()] ?? LOG_LEVELS.WARN;\n } else {\n this.level = level;\n }\n }\n\n error(...args) {\n if (this.level >= LOG_LEVELS.ERROR) {\n console.error('[SessionManager]', ...args);\n }\n }\n\n warn(...args) {\n if (this.level >= LOG_LEVELS.WARN) {\n console.warn('[SessionManager]', ...args);\n }\n }\n\n info(...args) {\n if (this.level >= LOG_LEVELS.INFO) {\n console.info('[SessionManager]', ...args);\n }\n }\n\n debug(...args) {\n if (this.level >= LOG_LEVELS.DEBUG) {\n console.debug('[SessionManager]', ...args);\n }\n }\n}\n\nexport const logger = new Logger();\nexport { LOG_LEVELS };\n","import {BootstrapError, ConfigurationError, NetworkError, SSRError} from './errors.js';\nimport {logger} from './logger.js';\nconst CLIENT_PLATFORM = 'web';\n/**\n * SessionManager - Core session token management class\n * Handles session bootstrap, automatic token refresh, and lifecycle management\n */\nclass SessionManager {\n constructor() {\n if (SessionManager.instance) {\n return SessionManager.instance;\n }\n\n this.config = {\n bootstrapUrl: '/session/bootstrap',\n refreshInterval: 25 * 60 * 1000, // 25 minutes in milliseconds\n tokenExpiry: 30 * 60 * 1000, // 30 minutes in milliseconds\n maxRetries: 3,\n tokenCookieName: 'token', // Cookie name to read token from\n };\n\n this.state = {\n isInitialized: false,\n isLoading: false,\n lastRefreshTime: null,\n error: null,\n errorCode: null,\n nextRefreshTime: null,\n tokenExpiry: null,\n cfSessionId: null,\n initializationFailed: false,\n };\n\n this.refreshTimerId = null;\n this.tickTimerId = null;\n this.listeners = new Set();\n this.initializationPromise = null;\n\n SessionManager.instance = this;\n }\n\n /**\n * Configure the session manager\n * @param {Object} config - Configuration options\n * @param {string} config.bootstrapUrl - URL for bootstrap API endpoint\n * @param {number} config.refreshInterval - Interval for token refresh in milliseconds (default: 25 mins)\n * @param {number} config.tokenExpiry - Token expiry time in milliseconds (default: 30 mins)\n * @param {number} config.maxRetries - Maximum retry attempts on failure (default: 3)\n * @param {Object} config.headers - Additional headers for API calls\n * @param {boolean} config.credentials - Include credentials in requests (default: true)\n * @param {string} config.tokenCookieName - Cookie name to read token from (default: 'token')\n */\n configure(config = {}) {\n if (!config.bootstrapUrl || typeof config.bootstrapUrl !== 'string' || !config.bootstrapUrl.trim()) {\n throw new ConfigurationError('bootstrapUrl is required and must be a non-empty string', { bootstrapUrl: config.bootstrapUrl });\n }\n \n if (config.refreshInterval !== undefined) {\n if (typeof config.refreshInterval !== 'number' || !isFinite(config.refreshInterval)) {\n throw new ConfigurationError('refreshInterval must be a finite number', { refreshInterval: config.refreshInterval });\n }\n if (config.refreshInterval <= 0) {\n throw new ConfigurationError('refreshInterval must be positive', { refreshInterval: config.refreshInterval });\n }\n }\n \n if (config.tokenExpiry !== undefined) {\n if (typeof config.tokenExpiry !== 'number' || !isFinite(config.tokenExpiry)) {\n throw new ConfigurationError('tokenExpiry must be a finite number', { tokenExpiry: config.tokenExpiry });\n }\n if (config.tokenExpiry <= 0) {\n throw new ConfigurationError('tokenExpiry must be positive', { tokenExpiry: config.tokenExpiry });\n }\n }\n\n this.config = {\n ...this.config,\n ...config,\n credentials: config.credentials !== undefined ? config.credentials : true,\n logLevel: config.logLevel || 'WARN'\n };\n\n logger.setLevel(this.config.logLevel);\n\n if (this.config.refreshInterval && this.config.tokenExpiry && \n this.config.refreshInterval >= this.config.tokenExpiry) {\n logger.warn('refreshInterval should be less than tokenExpiry to prevent race conditions');\n }\n }\n\n /**\n * Initialize session by calling bootstrap API\n * @param {boolean} skipCookieCheck - Skip session_initialized cookie check (for auto-refresh)\n * @returns {Promise<Object>} Bootstrap response\n */\n async initialize(skipCookieCheck = false) {\n if (typeof window === 'undefined') {\n throw new SSRError('Cannot initialize in non-browser environment (SSR)');\n }\n \n if (this.state.initializationFailed) {\n logger.warn('Initialization previously failed. Reload page to retry.');\n throw new BootstrapError('Initialization failed after max retries. Reload page to retry.');\n }\n \n // Return existing promise if refresh already in progress\n if (this.initializationPromise) {\n logger.debug('Initialization already in progress, waiting...');\n return this.initializationPromise;\n }\n\n // Check if session is already initialized via cookie (only on page load)\n if (!skipCookieCheck) {\n const sessionInitialized = this.getCookie('session_initialized');\n if (sessionInitialized === 'true') {\n logger.info('Session already initialized, skipping bootstrap API call');\n this.state.isInitialized = true;\n this.state.tokenExpiry = this.config.tokenExpiry;\n\n // Restore cf-session-id from cookie\n const storedSessionId = this.getCookie('cf-session-id');\n if (storedSessionId) {\n this.state.cfSessionId = storedSessionId;\n } else {\n this.setCfSessionId(this.generateSessionId());\n }\n\n // Calculate remaining time from stored refresh timestamp\n const refreshAt = parseInt(this.getCookie('session_refresh_at'), 10);\n let remaining;\n if (refreshAt && !isNaN(refreshAt)) {\n remaining = Math.max(0, refreshAt - Date.now());\n } else {\n remaining = this.config.refreshInterval;\n }\n\n this.state.lastRefreshTime = Date.now();\n this.state.nextRefreshTime = Date.now() + remaining;\n\n this.startTokenRefresh(remaining);\n this.notifyListeners();\n return { token: this.getToken(this.config.tokenCookieName) };\n }\n }\n\n this.state.isLoading = true;\n this.state.error = null;\n this.notifyListeners();\n\n this.initializationPromise = (async () => {\n let lastError;\n \n for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) {\n try {\n // Generate new cf-session-id for each bootstrap call\n const newSessionId = this.generateSessionId();\n this.setCfSessionId(newSessionId);\n\n logger.info(`Calling bootstrap API (attempt ${attempt}/${this.config.maxRetries}):`, this.config.bootstrapUrl);\n\n const response = await fetch(this.config.bootstrapUrl, {\n method: 'GET',\n credentials: this.config.credentials ? 'include' : 'same-origin',\n headers: {\n 'Content-Type': 'application/json',\n 'cf-session-id': this.state.cfSessionId,\n 'x-client-platform': CLIENT_PLATFORM,\n ...this.config.headers,\n },\n });\n\n if (!response.ok) {\n throw new BootstrapError(`Bootstrap failed: ${response.status} ${response.statusText}`, {\n status: response.status,\n statusText: response.statusText,\n url: this.config.bootstrapUrl\n });\n }\n\n let data;\n try {\n data = await response.json();\n } catch (jsonError) {\n throw new BootstrapError('Bootstrap response is not valid JSON', { originalError: jsonError.message });\n }\n logger.info('Bootstrap successful');\n\n // Check if server set a cookie\n const setCookieHeader = response.headers.get('set-cookie');\n const serverSetCookie = setCookieHeader && setCookieHeader.includes(this.config.tokenCookieName);\n\n // Use refresh_time from response (in seconds) or fall back to config\n const refreshInterval = data.refresh_time \n ? data.refresh_time * 1000 \n : this.config.refreshInterval;\n\n // Calculate token expiry from response (in seconds) or fall back to config\n const tokenExpiry = data.expire_time\n ? data.expire_time * 1000\n : this.config.tokenExpiry;\n\n if (data.token && !serverSetCookie) {\n logger.debug('Server did not set cookie, setting from SDK');\n this.setCookie(this.config.tokenCookieName, data.token, tokenExpiry / 1000);\n } else if (serverSetCookie) {\n logger.debug('Server set cookie, skipping SDK cookie logic');\n }\n\n // Re-set cf-session-id cookie with actual expire_time from response\n this.setCfSessionId(this.state.cfSessionId, tokenExpiry / 1000);\n\n this.state.isInitialized = true;\n this.state.isLoading = false;\n this.state.lastRefreshTime = Date.now();\n this.state.nextRefreshTime = Date.now() + refreshInterval;\n this.state.tokenExpiry = tokenExpiry;\n this.state.error = null;\n\n // Set session_initialized cookie (expires at refresh interval time)\n this.setCookie('session_initialized', 'true', refreshInterval / 1000);\n\n // Store next refresh timestamp for reload recovery\n this.setCookie('session_refresh_at', String(Date.now() + refreshInterval), refreshInterval / 1000);\n\n // Start automatic refresh timer with dynamic interval\n this.startTokenRefresh(refreshInterval);\n\n this.notifyListeners();\n return data;\n } catch (error) {\n lastError = error instanceof Error ? error : new NetworkError('Unknown error occurred', { error });\n logger.error(`Bootstrap attempt ${attempt} failed:`, lastError.message);\n \n if (attempt < this.config.maxRetries) {\n const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);\n logger.info(`Retrying in ${delay}ms...`);\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n }\n \n logger.error('All bootstrap attempts failed');\n this.state.isLoading = false;\n this.state.error = lastError?.message || 'Unknown error';\n this.state.errorCode = lastError?.code || 'UNKNOWN_ERROR';\n this.state.initializationFailed = true;\n this.notifyListeners();\n throw lastError;\n })();\n\n try {\n return await this.initializationPromise;\n } finally {\n this.initializationPromise = null;\n }\n }\n\n /**\n * Check if token refresh is currently in progress\n * @returns {boolean} True if refresh is in progress\n */\n isRefreshing() {\n return this.initializationPromise !== null;\n }\n\n /**\n * Wait for ongoing refresh to complete\n * @returns {Promise<Object>} Bootstrap response from ongoing refresh\n */\n async waitForRefresh() {\n if (this.initializationPromise) {\n logger.debug('Waiting for ongoing refresh to complete');\n return this.initializationPromise;\n }\n return Promise.resolve();\n }\n\n /**\n * Set cf-session-id in cookie\n * @param {string} sessionId - Session ID\n */\n setCfSessionId(sessionId, maxAge) {\n this.state.cfSessionId = sessionId;\n const cookieMaxAge = maxAge || this.config.tokenExpiry / 1000;\n this.setCookie('cf-session-id', sessionId, cookieMaxAge);\n }\n\n /**\n * Generate unique session ID\n * @returns {string} Generated session ID\n */\n generateSessionId() {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }\n\n /**\n * Get cf-session-id for requests\n * @returns {string} Current cf-session-id\n */\n getSessionId() {\n return this.state.cfSessionId;\n }\n\n /**\n * Check if token should be added to headers (HTTP or cross-origin)\n * @returns {boolean} True if token should be added to headers\n */\n shouldUseTokenHeader() {\n if (typeof window === 'undefined') return false;\n return window.location.protocol === 'http:';\n }\n\n /**\n * Get cookie value\n * @param {string} name - Cookie name\n * @returns {string|null} Cookie value or null\n */\n getCookie(name) {\n if (typeof document === 'undefined') return null;\n const value = `; ${document.cookie}`;\n const parts = value.split(`; ${name}=`);\n if (parts.length === 2) {\n try {\n return decodeURIComponent(parts.pop().split(';').shift());\n } catch (error) {\n logger.error('Failed to decode cookie value:', error instanceof Error ? error.message : String(error));\n return null;\n }\n }\n return null;\n }\n\n /**\n * Get token from cookie\n * @param {string} name - Cookie name (default: configured tokenCookieName)\n * @returns {string|null} Token value or null\n */\n getToken(name = this.config.tokenCookieName) {\n return this.getCookie(name);\n }\n\n /**\n * Set cookie from client-side (WARNING: Not HttpOnly, less secure than server-set cookies)\n * @param {string} name - Cookie name\n * @param {string} value - Cookie value\n * @param {number} maxAge - Max age in seconds\n */\n setCookie(name, value, maxAge) {\n if (typeof document === 'undefined') {\n logger.warn('Cannot set cookie in non-browser environment');\n return;\n }\n \n // Sanitize cookie name to prevent XSS\n const sanitizedName = name.replace(/[^a-zA-Z0-9_-]/g, '');\n if (!sanitizedName) {\n logger.error('Invalid cookie name provided');\n return;\n }\n \n const expires = new Date(Date.now() + maxAge * 1000).toUTCString();\n const isSecure = typeof window !== 'undefined' && window.location.protocol === 'https:';\n const secureFlag = isSecure ? '; Secure' : '';\n const encodedValue = encodeURIComponent(value);\n document.cookie = `${sanitizedName}=${encodedValue}; path=/; expires=${expires}; SameSite=Lax${secureFlag}`;\n logger.debug(`Cookie set: ${sanitizedName}, expires in ${maxAge}s${isSecure ? ' (Secure)' : ''} [WARNING: Not HttpOnly]`);\n }\n\n /**\n * Start automatic token refresh timer\n * @param {number} interval - Refresh interval in milliseconds\n */\n startTokenRefresh(interval = this.config.refreshInterval) {\n this.stopTokenRefresh();\n logger.info(`Scheduling token refresh in ${interval / 1000} seconds`);\n\n this.refreshTimerId = setTimeout(async () => {\n logger.info('Auto-refreshing token...');\n try {\n await this.refreshToken();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n const errorCode = error?.code || 'UNKNOWN_ERROR';\n logger.error('Auto-refresh failed:', errorMessage);\n this.state.error = errorMessage;\n this.state.errorCode = errorCode;\n this.notifyListeners();\n }\n }, interval);\n\n this.startTickTimer();\n }\n\n /**\n * Stop automatic token refresh timer\n */\n stopTokenRefresh() {\n if (this.refreshTimerId) {\n clearTimeout(this.refreshTimerId);\n this.refreshTimerId = null;\n logger.debug('Token refresh timer stopped');\n }\n this.stopTickTimer();\n }\n\n /**\n * Start periodic tick timer to notify listeners for countdown updates\n */\n startTickTimer() {\n this.stopTickTimer();\n this.tickTimerId = setInterval(() => {\n this.notifyListeners();\n }, 1000);\n }\n\n /**\n * Stop periodic tick timer\n */\n stopTickTimer() {\n if (this.tickTimerId) {\n clearInterval(this.tickTimerId);\n this.tickTimerId = null;\n }\n }\n\n /**\n * Manually refresh the session token\n * @returns {Promise<Object>} Bootstrap response\n */\n async refreshToken() {\n logger.info('Manual token refresh triggered');\n this.state.initializationFailed = false;\n return this.initialize(true);\n }\n\n /**\n * Get current session status\n * @returns {Object} Current session state\n */\n getSessionStatus() {\n return {\n isInitialized: this.state.isInitialized,\n isLoading: this.state.isLoading,\n lastRefreshTime: this.state.lastRefreshTime,\n nextRefreshTime: this.state.nextRefreshTime,\n tokenExpiry: this.state.tokenExpiry,\n error: this.state.error,\n errorCode: this.state.errorCode,\n initializationFailed: this.state.initializationFailed,\n timeUntilRefresh: this.state.nextRefreshTime\n ? Math.max(0, this.state.nextRefreshTime - Date.now())\n : null,\n };\n }\n\n /**\n * Subscribe to session state changes\n * @param {Function} listener - Callback function to be called on state changes\n * @returns {Function} Unsubscribe function\n */\n subscribe(listener) {\n if (typeof listener !== 'function') {\n throw new TypeError('Listener must be a function');\n }\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Notify all listeners of state changes\n */\n notifyListeners() {\n const status = this.getSessionStatus();\n // Use Array.from to create a snapshot, preventing issues if listeners modify the Set\n Array.from(this.listeners).forEach(listener => {\n try {\n listener(status);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('Listener error:', errorMessage);\n }\n });\n }\n\n /**\n * Clean up and reset the session manager\n */\n destroy() {\n this.stopTokenRefresh();\n this.listeners.clear();\n this.state = {\n isInitialized: false,\n isLoading: false,\n lastRefreshTime: null,\n error: null,\n errorCode: null,\n nextRefreshTime: null,\n tokenExpiry: null,\n cfSessionId: null,\n initializationFailed: false,\n };\n // Clear cookies\n this.setCookie('cf-session-id', '', -1);\n this.setCookie('session_initialized', '', -1);\n this.setCookie('session_refresh_at', '', -1);\n logger.info('Destroyed');\n }\n\n /**\n * Get the singleton instance\n * @returns {SessionManager} Singleton instance\n */\n static getInstance() {\n if (!SessionManager.instance) {\n SessionManager.instance = new SessionManager();\n }\n return SessionManager.instance;\n }\n}\n\nexport default SessionManager;\n","import { useState, useEffect, useCallback } from 'react';\nimport SessionManager from '../SessionManager.js';\nimport { logger } from '../logger.js';\n\n/**\n * React hook for session management\n * Provides session state and controls for React components\n * \n * @param {Object} options - Configuration options\n * @param {boolean} options.autoInitialize - Automatically initialize on mount (default: true)\n * @returns {Object} Session state and controls\n */\nexport function useSession(options = {}) {\n const { autoInitialize = true } = options;\n\n const sessionManager = SessionManager.getInstance();\n\n const [sessionState, setSessionState] = useState(() =>\n sessionManager.getSessionStatus()\n );\n\n // Subscribe to session state changes\n useEffect(() => {\n const unsubscribe = sessionManager.subscribe((newState) => {\n setSessionState(newState);\n });\n\n return unsubscribe;\n }, []); // sessionManager is a singleton, no need to include in deps\n\n // Auto-initialize if enabled\n useEffect(() => {\n if (autoInitialize && !sessionState.isInitialized && !sessionState.isLoading && !sessionState.initializationFailed) {\n sessionManager.initialize().catch(error => {\n logger.error('Auto-initialization failed:', error);\n });\n }\n }, [autoInitialize, sessionState.isInitialized, sessionState.isLoading, sessionState.initializationFailed]);\n\n // Manual refresh function\n const refresh = useCallback(async () => {\n try {\n await sessionManager.refreshToken();\n } catch (error) {\n logger.error('Manual refresh failed:', error);\n throw error;\n }\n }, []);\n\n // Initialize function (for manual control)\n const initialize = useCallback(async () => {\n try {\n await sessionManager.initialize();\n } catch (error) {\n logger.error('Manual initialization failed:', error);\n throw error;\n }\n }, []);\n\n return {\n // State\n isInitialized: sessionState.isInitialized,\n isLoading: sessionState.isLoading,\n error: sessionState.error,\n lastRefreshTime: sessionState.lastRefreshTime,\n nextRefreshTime: sessionState.nextRefreshTime,\n timeUntilRefresh: sessionState.timeUntilRefresh,\n initializationFailed: sessionState.initializationFailed,\n\n // Actions\n refresh,\n initialize,\n };\n}\n\nexport default useSession;\n","import SessionManager from './SessionManager.js';\nexport const CLIENT_PLATFORM = 'web';\n\n/**\n * Fetch interceptor with automatic token refresh on 401\n * @param {string} url - Request URL\n * @param {Object} options - Fetch options\n * @returns {Promise<Response>} Fetch response\n */\nexport async function fetchInterceptor(url, options = {}) {\n const sessionManager = SessionManager.getInstance();\n \n // Ensure credentials are included to send cookies\n options.credentials = options.credentials || 'include';\n \n // Add headers\n options.headers = {\n ...options.headers,\n 'cf-session-id': sessionManager.getSessionId(),\n 'x-client-platform': CLIENT_PLATFORM,\n };\n \n // Add token to header if HTTP (not HTTPS)\n if (sessionManager.shouldUseTokenHeader()) {\n const token = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (token) {\n options.headers[sessionManager.config.tokenCookieName] = token;\n }\n }\n \n let response = await fetch(url, options);\n \n // Retry once if unauthorized with INVALID_SESSION\n if (response.status === 401) {\n const clonedResponse = response.clone();\n try {\n const data = await clonedResponse.json();\n if (data.error_msg === 'INVALID_SESSION') {\n if (sessionManager.isRefreshing()) {\n await sessionManager.waitForRefresh();\n } else {\n const existingToken = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (!existingToken) {\n await sessionManager.refreshToken();\n }\n }\n options.headers['cf-session-id'] = sessionManager.getSessionId();\n options.headers['x-client-platform'] = CLIENT_PLATFORM;\n if (sessionManager.shouldUseTokenHeader()) {\n const newToken = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (newToken) {\n options.headers[sessionManager.config.tokenCookieName] = newToken;\n }\n }\n response = await fetch(url, options);\n }\n } catch (e) {\n // Not JSON or parsing failed, return original response\n }\n }\n \n return response;\n}\n\n/**\n * Axios interceptor with automatic token refresh on 401 and INVALID_SESSION\n * @param {Object} axiosInstance - Axios instance\n */\nexport function setupAxiosInterceptor(axiosInstance) {\n const sessionManager = SessionManager.getInstance();\n \n // Request interceptor to add cf-session-id and token\n axiosInstance.interceptors.request.use(\n (config) => {\n config.headers['cf-session-id'] = sessionManager.getSessionId();\n config.headers['x-client-platform'] = CLIENT_PLATFORM;\n if (sessionManager.shouldUseTokenHeader()) {\n const token = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (token) {\n config.headers[sessionManager.config.tokenCookieName] = token;\n }\n }\n return config;\n },\n (error) => Promise.reject(error)\n );\n \n // Response interceptor for token refresh\n axiosInstance.interceptors.response.use(\n (response) => response,\n async (error) => {\n const originalRequest = error.config;\n \n // Retry once if unauthorized with INVALID_SESSION\n if (error.response?.status === 401 && !originalRequest._retry) {\n if (error.response?.data?.error_msg === 'INVALID_SESSION') {\n originalRequest._retry = true;\n \n if (sessionManager.isRefreshing()) {\n await sessionManager.waitForRefresh();\n } else {\n const existingToken = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (!existingToken) {\n await sessionManager.refreshToken();\n }\n }\n \n originalRequest.headers['cf-session-id'] = sessionManager.getSessionId();\n originalRequest.headers['x-client-platform'] = CLIENT_PLATFORM;\n if (sessionManager.shouldUseTokenHeader()) {\n const newToken = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (newToken) {\n originalRequest.headers[sessionManager.config.tokenCookieName] = newToken;\n }\n }\n return axiosInstance(originalRequest);\n }\n }\n \n return Promise.reject(error);\n }\n );\n \n return axiosInstance;\n}\n"],"names":["SessionError","Error","constructor","message","code","details","super","this","name","captureStackTrace","Object","setPrototypeOf","prototype","ConfigurationError","BootstrapError","NetworkError","SSRError","LOG_LEVELS","NONE","ERROR","WARN","INFO","DEBUG","logger","level","setLevel","toUpperCase","error","args","console","warn","info","debug","SessionManager","instance","config","bootstrapUrl","refreshInterval","tokenExpiry","maxRetries","tokenCookieName","state","isInitialized","isLoading","lastRefreshTime","errorCode","nextRefreshTime","cfSessionId","initializationFailed","refreshTimerId","tickTimerId","listeners","Set","initializationPromise","configure","trim","undefined","isFinite","credentials","logLevel","initialize","skipCookieCheck","window","getCookie","storedSessionId","setCfSessionId","generateSessionId","refreshAt","parseInt","remaining","isNaN","Math","max","Date","now","startTokenRefresh","notifyListeners","token","getToken","lastError","attempt","newSessionId","response","fetch","method","headers","ok","status","statusText","url","data","json","jsonError","originalError","setCookieHeader","get","serverSetCookie","includes","refresh_time","expire_time","setCookie","String","delay","min","pow","Promise","resolve","setTimeout","isRefreshing","waitForRefresh","sessionId","maxAge","cookieMaxAge","crypto","randomUUID","random","toString","substr","getSessionId","shouldUseTokenHeader","location","protocol","document","parts","cookie","split","length","decodeURIComponent","pop","shift","value","sanitizedName","replace","expires","toUTCString","isSecure","secureFlag","encodedValue","encodeURIComponent","interval","stopTokenRefresh","async","refreshToken","errorMessage","startTickTimer","clearTimeout","stopTickTimer","setInterval","clearInterval","getSessionStatus","timeUntilRefresh","subscribe","listener","TypeError","add","delete","Array","from","forEach","destroy","clear","getInstance","useSession","options","autoInitialize","sessionManager","sessionState","setSessionState","useState","useEffect","newState","catch","refresh","useCallback","CLIENT_PLATFORM","fetchInterceptor","clonedResponse","clone","error_msg","newToken","e","setupAxiosInterceptor","axiosInstance","interceptors","request","use","reject","originalRequest","_retry"],"mappings":"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"}
1
+ {"version":3,"file":"index.esm.js","sources":["../src/errors.js","../src/SessionManager.js","../src/hooks/useSession.js","../src/interceptors.js","../src/logger.js"],"sourcesContent":["/**\n * Custom error classes for SessionManager\n */\n\nexport class SessionError extends Error {\n constructor(message, code, details = {}) {\n super(message);\n this.name = 'SessionError';\n this.code = code;\n this.details = details;\n \n // Maintains proper stack trace for where error was thrown\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n \n // Set the prototype explicitly for proper instanceof checks\n Object.setPrototypeOf(this, SessionError.prototype);\n }\n}\n\nexport class ConfigurationError extends SessionError {\n constructor(message, details) {\n super(message, 'CONFIGURATION_ERROR', details);\n this.name = 'ConfigurationError';\n }\n}\n\nexport class BootstrapError extends SessionError {\n constructor(message, details) {\n super(message, 'BOOTSTRAP_ERROR', details);\n this.name = 'BootstrapError';\n }\n}\n\nexport class NetworkError extends SessionError {\n constructor(message, details) {\n super(message, 'NETWORK_ERROR', details);\n this.name = 'NetworkError';\n }\n}\n\nexport class SSRError extends SessionError {\n constructor(message, details = {}) {\n super(message, 'SSR_ERROR', details);\n this.name = 'SSRError';\n }\n}\n","import {BootstrapError, ConfigurationError, NetworkError, SSRError} from './errors.js';\nconst CLIENT_PLATFORM = 'web';\n\nconst DEFAULT_CONFIG = {\n bootstrapUrl: '/session/bootstrap',\n refreshInterval: 25 * 60 * 1000, // 25 minutes in milliseconds\n tokenExpiry: 30 * 60 * 1000, // 30 minutes in milliseconds\n maxRetries: 3,\n bootstrapTimeout: 30000, // 30 seconds timeout for bootstrap fetch\n tokenCookieName: 'token', // Must match server's session_header\n invalidSessionError: 'INVALID-GW-SESSION', // Must match server's error response\n};\n\n/**\n * SessionManager - Core session token management class\n * Handles session bootstrap, automatic token refresh, and lifecycle management\n */\nclass SessionManager {\n constructor() {\n if (SessionManager.instance) {\n return SessionManager.instance;\n }\n\n this.config = { ...DEFAULT_CONFIG };\n\n this.state = {\n isInitialized: false,\n isLoading: false,\n lastRefreshTime: null,\n error: null,\n errorCode: null,\n nextRefreshTime: null,\n tokenExpiry: null,\n cfSessionId: null,\n initializationFailed: false,\n };\n\n this.refreshTimerId = null;\n this.listeners = new Set();\n this.initializationPromise = null;\n this._configured = false;\n this._boundHandleVisibilityChange = this._handleVisibilityChange.bind(this);\n this._boundHandlePageShow = this._handlePageShow.bind(this);\n this._boundHandleOnline = this._handleOnline.bind(this);\n\n SessionManager.instance = this;\n }\n\n /**\n * Configure the session manager\n * @param {Object} config - Configuration options\n * @param {string} config.bootstrapUrl - URL for bootstrap API endpoint\n * @param {number} config.refreshInterval - Interval for token refresh in milliseconds (default: 25 mins)\n * @param {number} config.tokenExpiry - Token expiry time in milliseconds (default: 30 mins)\n * @param {number} config.maxRetries - Maximum retry attempts on failure (default: 3)\n * @param {number} config.bootstrapTimeout - Timeout for bootstrap fetch in milliseconds (default: 30s)\n * @param {Object} config.headers - Additional headers for API calls\n * @param {boolean} config.credentials - Include credentials in requests (default: true)\n * @param {string} config.tokenCookieName - Cookie name to read token from (default: 'token')\n */\n configure(config = {}) {\n if (!config.bootstrapUrl || typeof config.bootstrapUrl !== 'string' || !config.bootstrapUrl.trim()) {\n throw new ConfigurationError('bootstrapUrl is required and must be a non-empty string', { bootstrapUrl: config.bootstrapUrl });\n }\n\n if (config.refreshInterval !== undefined) {\n if (typeof config.refreshInterval !== 'number' || !isFinite(config.refreshInterval)) {\n throw new ConfigurationError('refreshInterval must be a finite number', { refreshInterval: config.refreshInterval });\n }\n if (config.refreshInterval <= 0) {\n throw new ConfigurationError('refreshInterval must be positive', { refreshInterval: config.refreshInterval });\n }\n }\n\n if (config.tokenExpiry !== undefined) {\n if (typeof config.tokenExpiry !== 'number' || !isFinite(config.tokenExpiry)) {\n throw new ConfigurationError('tokenExpiry must be a finite number', { tokenExpiry: config.tokenExpiry });\n }\n if (config.tokenExpiry <= 0) {\n throw new ConfigurationError('tokenExpiry must be positive', { tokenExpiry: config.tokenExpiry });\n }\n }\n\n if (config.maxRetries !== undefined) {\n if (typeof config.maxRetries !== 'number' || !Number.isInteger(config.maxRetries) || config.maxRetries < 1) {\n throw new ConfigurationError('maxRetries must be a positive integer', { maxRetries: config.maxRetries });\n }\n }\n\n this.config = {\n ...this.config,\n ...config,\n credentials: config.credentials !== undefined ? config.credentials : true,\n };\n\n this._configured = true;\n }\n\n /**\n * Initialize session by calling bootstrap API\n * @param {boolean} forceRefresh - Force a new bootstrap call even if already initialized (used by refreshToken)\n * @returns {Promise<Object>} Bootstrap response\n */\n async initialize(forceRefresh = false) {\n if (typeof window === 'undefined') {\n throw new SSRError('Cannot initialize in non-browser environment (SSR)');\n }\n\n if (this.state.initializationFailed) {\n throw new BootstrapError('Initialization failed after max retries. Reload page to retry.');\n }\n\n // Return existing promise if refresh already in progress\n if (this.initializationPromise) {\n return this.initializationPromise;\n }\n\n // If already initialized in this page session and not a forced refresh, return current state\n if (this.state.isInitialized && !forceRefresh) {\n return { token: this.getToken(this.config.tokenCookieName) };\n }\n\n this.state.isLoading = true;\n this.state.error = null;\n this.notifyListeners();\n\n this.initializationPromise = (async () => {\n let lastError;\n\n for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) {\n try {\n const newSessionId = this.generateSessionId();\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.bootstrapTimeout);\n\n let response;\n try {\n response = await fetch(this.config.bootstrapUrl, {\n method: 'GET',\n credentials: this.config.credentials ? 'include' : 'same-origin',\n signal: controller.signal,\n headers: {\n 'cf-session-id': newSessionId,\n 'x-client-platform': CLIENT_PLATFORM,\n ...this.config.headers,\n },\n });\n } catch (fetchError) {\n if (fetchError.name === 'AbortError') {\n throw new NetworkError(`Bootstrap request timed out after ${this.config.bootstrapTimeout}ms`, {\n timeout: this.config.bootstrapTimeout,\n url: this.config.bootstrapUrl\n });\n }\n throw fetchError;\n } finally {\n clearTimeout(timeoutId);\n }\n\n if (!response.ok) {\n throw new BootstrapError(`Bootstrap failed: ${response.status} ${response.statusText}`, {\n status: response.status,\n statusText: response.statusText,\n url: this.config.bootstrapUrl\n });\n }\n\n let data;\n try {\n data = await response.json();\n } catch (jsonError) {\n throw new BootstrapError('Bootstrap response is not valid JSON', { originalError: jsonError.message });\n }\n\n // Use refresh_time from response (in seconds) or fall back to config.\n // Reject non-positive values to prevent silent cookie expiry failures.\n const refreshInterval =\n typeof data.refresh_time === 'number' && isFinite(data.refresh_time) && data.refresh_time > 0\n ? data.refresh_time * 1000\n : this.config.refreshInterval;\n\n // Calculate token expiry from response (in seconds) or fall back to config.\n // Reject non-positive values to prevent cookies with past expiry dates.\n const tokenExpiry =\n typeof data.expire_time === 'number' && isFinite(data.expire_time) && data.expire_time > 0\n ? data.expire_time * 1000\n : this.config.tokenExpiry;\n\n // If server returned token in response body (non-cookie mode),\n // store it as a client-side cookie. Key matches server's session_header.\n const tokenFromResponse = data[this.config.tokenCookieName];\n if (tokenFromResponse) {\n this.setCookie(this.config.tokenCookieName, tokenFromResponse, tokenExpiry / 1000);\n }\n\n // Now safe to update cf-session-id — new token is stored/set\n this.setCfSessionId(newSessionId, tokenExpiry / 1000);\n\n this.state.isInitialized = true;\n this.state.isLoading = false;\n this.state.lastRefreshTime = Date.now();\n this.state.nextRefreshTime = Date.now() + refreshInterval;\n this.state.tokenExpiry = tokenExpiry;\n this.state.error = null;\n\n // Start automatic refresh timer with dynamic interval\n this.startTokenRefresh(refreshInterval);\n\n this.notifyListeners();\n return data;\n } catch (error) {\n lastError = error instanceof Error ? error : new NetworkError('Unknown error occurred', { error });\n\n if (attempt < this.config.maxRetries) {\n const delay = Math.min(1000 * Math.pow(2, attempt - 1), 5000);\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n }\n\n this.state.isLoading = false;\n this.state.error = lastError?.message || 'Unknown error';\n this.state.errorCode = lastError?.code || 'UNKNOWN_ERROR';\n this.state.initializationFailed = true;\n this.notifyListeners();\n throw lastError;\n })();\n\n try {\n return await this.initializationPromise;\n } finally {\n this.initializationPromise = null;\n }\n }\n\n /**\n * Check if token refresh is currently in progress\n * @returns {boolean} True if refresh is in progress\n */\n isRefreshing() {\n return this.initializationPromise !== null;\n }\n\n /**\n * Wait for ongoing refresh to complete\n * @returns {Promise<Object>} Bootstrap response from ongoing refresh\n */\n async waitForRefresh() {\n if (this.initializationPromise) {\n return this.initializationPromise;\n }\n return Promise.resolve();\n }\n\n /**\n * Check if the token is likely expired based on in-memory timestamps.\n * After a full browser close + reopen, lastRefreshTime is null, so this returns true.\n * @returns {boolean} True if token appears expired or state is unknown\n */\n _isTokenLikelyExpired() {\n if (!this.state.lastRefreshTime || !this.state.tokenExpiry) return true;\n return Date.now() >= this.state.lastRefreshTime + this.state.tokenExpiry;\n }\n\n /**\n * Ensure the session is ready before making API calls.\n * - If initialized and token not expired, resolves immediately.\n * - If initialization is in progress, waits for it.\n * - If not initialized but configured, triggers initialization and waits.\n * - If not configured or permanently failed, resolves (lets request proceed;\n * the 401 handler in the interceptor will deal with it).\n * @returns {Promise<void>}\n */\n async ensureReady() {\n // Happy path: session is live and token hasn't expired\n if (this.state.isInitialized && !this._isTokenLikelyExpired()) {\n return;\n }\n\n // An initialization / refresh is already in flight — piggyback on it\n if (this.initializationPromise) {\n try {\n await this.initializationPromise;\n } catch (_) {\n // Initialization failed — let the request proceed, 401 handler will deal with it\n }\n return;\n }\n\n // Not initialized / token expired, no call in flight, but we *can* bootstrap\n if (this._configured && !this.state.initializationFailed) {\n try {\n await this.initialize(this.state.isInitialized); // force refresh if already initialized but token expired\n } catch (_) {\n // Initialization failed — nothing we can do, let request proceed\n }\n }\n\n // Not configured or permanently failed — nothing we can do here\n }\n\n /**\n * Check if the session needs a refresh (initialized but token expired).\n * Useful for interceptors to proactively refresh before sending a request.\n * @returns {boolean}\n */\n needsRefresh() {\n return this.state.isInitialized && this._isTokenLikelyExpired();\n }\n\n /**\n * Set cf-session-id in cookie\n * @param {string} sessionId - Session ID\n * @param {number} maxAge - Max age in seconds\n */\n setCfSessionId(sessionId, maxAge) {\n this.state.cfSessionId = sessionId;\n const cookieMaxAge = maxAge || this.config.tokenExpiry / 1000;\n this.setCookie('cf-session-id', sessionId, cookieMaxAge);\n }\n\n /**\n * Generate unique session ID\n * @returns {string} Generated session ID\n */\n generateSessionId() {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }\n\n /**\n * Get cf-session-id for requests\n * @returns {string} Current cf-session-id\n */\n getSessionId() {\n return this.state.cfSessionId;\n }\n\n /**\n * Check if token should be added to headers (HTTP or cross-origin)\n * @returns {boolean} True if token should be added to headers\n */\n shouldUseTokenHeader() {\n if (typeof window === 'undefined') return false;\n return window.location.protocol === 'http:';\n }\n\n /**\n * Shared cookie name sanitizer used by both getCookie and setCookie\n * @param {string} name - Raw cookie name\n * @returns {string} Sanitized cookie name\n */\n _sanitizeCookieName(name) {\n return name.replace(/[^a-zA-Z0-9_-]/g, '');\n }\n\n /**\n * Get cookie value\n * @param {string} name - Cookie name\n * @returns {string|null} Cookie value or null\n */\n getCookie(name) {\n if (typeof document === 'undefined') return null;\n const sanitizedName = this._sanitizeCookieName(name);\n if (!sanitizedName) return null;\n const value = `; ${document.cookie}`;\n const parts = value.split(`; ${sanitizedName}=`);\n if (parts.length === 2) {\n try {\n return decodeURIComponent(parts.pop().split(';').shift());\n } catch (_) {\n return null;\n }\n }\n return null;\n }\n\n /**\n * Get token from cookie\n * @param {string} name - Cookie name (default: configured tokenCookieName)\n * @returns {string|null} Token value or null\n */\n getToken(name = this.config.tokenCookieName) {\n return this.getCookie(name);\n }\n\n /**\n * Set cookie from client-side (WARNING: Not HttpOnly, less secure than server-set cookies)\n * @param {string} name - Cookie name\n * @param {string} value - Cookie value\n * @param {number} maxAge - Max age in seconds\n */\n setCookie(name, value, maxAge) {\n if (typeof document === 'undefined') return;\n\n const sanitizedName = this._sanitizeCookieName(name);\n if (!sanitizedName) return;\n\n const expires = new Date(Date.now() + maxAge * 1000).toUTCString();\n const isSecure = typeof window !== 'undefined' && window.location.protocol === 'https:';\n const secureFlag = isSecure ? '; Secure' : '';\n const encodedValue = encodeURIComponent(value);\n document.cookie = `${sanitizedName}=${encodedValue}; path=/; expires=${expires}; SameSite=Lax${secureFlag}`;\n }\n\n /**\n * Start automatic token refresh timer\n * @param {number} interval - Refresh interval in milliseconds\n */\n startTokenRefresh(interval = this.config.refreshInterval) {\n this.stopTokenRefresh();\n\n this.refreshTimerId = setTimeout(async () => {\n try {\n await this.refreshToken();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n const errorCode = error?.code || 'UNKNOWN_ERROR';\n this.state.error = errorMessage;\n this.state.errorCode = errorCode;\n this.notifyListeners();\n // Schedule retry instead of dying permanently\n const retryDelay = Math.min(interval * 2, 60000);\n this.startTokenRefresh(retryDelay);\n }\n }, interval);\n\n // Listen for visibility changes (tab wake from sleep / bfcache restore)\n this._startVisibilityListener();\n // Listen for network recovery\n this._startOnlineListener();\n }\n\n /**\n * Stop automatic token refresh timer\n */\n stopTokenRefresh() {\n if (this.refreshTimerId) {\n clearTimeout(this.refreshTimerId);\n this.refreshTimerId = null;\n }\n this._stopVisibilityListener();\n this._stopOnlineListener();\n }\n\n /**\n * Start listening for page visibility changes and bfcache restore\n */\n _startVisibilityListener() {\n if (typeof document === 'undefined') return;\n document.addEventListener('visibilitychange', this._boundHandleVisibilityChange);\n if (typeof window !== 'undefined') {\n window.addEventListener('pageshow', this._boundHandlePageShow);\n }\n }\n\n /**\n * Stop listening for page visibility changes\n */\n _stopVisibilityListener() {\n if (typeof document === 'undefined') return;\n document.removeEventListener('visibilitychange', this._boundHandleVisibilityChange);\n if (typeof window !== 'undefined') {\n window.removeEventListener('pageshow', this._boundHandlePageShow);\n }\n }\n\n /**\n * Check if token refresh is overdue and trigger refresh if so\n * @returns {boolean} True if refresh was triggered\n */\n _refreshIfOverdue() {\n if (!this.state.isInitialized || this.state.initializationFailed) return false;\n if (this.isRefreshing()) return false;\n\n const now = Date.now();\n const { nextRefreshTime } = this.state;\n\n if (nextRefreshTime && now >= nextRefreshTime) {\n this.refreshToken().catch(() => {});\n return true;\n }\n return false;\n }\n\n /**\n * Handle visibility change — refresh if overdue when tab becomes visible\n */\n _handleVisibilityChange() {\n if (document.visibilityState !== 'visible') return;\n this._refreshIfOverdue();\n }\n\n /**\n * Handle pageshow — force bootstrap on bfcache restore (browser close + reopen)\n */\n _handlePageShow(event) {\n if (!event.persisted) return;\n // bfcache restore means browser was closed — always force a fresh bootstrap\n if (!this.state.isInitialized || this.state.initializationFailed) return;\n if (this.isRefreshing()) return;\n this.refreshToken().catch(() => {});\n }\n\n /**\n * Start listening for network recovery\n */\n _startOnlineListener() {\n if (typeof window === 'undefined') return;\n window.addEventListener('online', this._boundHandleOnline);\n }\n\n /**\n * Stop listening for network recovery\n */\n _stopOnlineListener() {\n if (typeof window === 'undefined') return;\n window.removeEventListener('online', this._boundHandleOnline);\n }\n\n /**\n * Handle online event — retry if initialization had failed due to network\n */\n _handleOnline() {\n if (!this.state.initializationFailed) return;\n this.refreshToken().catch(() => {});\n }\n\n /**\n * Manually refresh the session token\n * @returns {Promise<Object>} Bootstrap response\n */\n async refreshToken() {\n this.state.initializationFailed = false;\n return this.initialize(true);\n }\n\n /**\n * Get current session status\n * @returns {Object} Current session state\n */\n getSessionStatus() {\n return {\n isInitialized: this.state.isInitialized,\n isLoading: this.state.isLoading,\n lastRefreshTime: this.state.lastRefreshTime,\n nextRefreshTime: this.state.nextRefreshTime,\n tokenExpiry: this.state.tokenExpiry,\n error: this.state.error,\n errorCode: this.state.errorCode,\n initializationFailed: this.state.initializationFailed,\n timeUntilRefresh: this.state.nextRefreshTime\n ? Math.max(0, this.state.nextRefreshTime - Date.now())\n : null,\n };\n }\n\n /**\n * Subscribe to session state changes\n * @param {Function} listener - Callback function to be called on state changes\n * @returns {Function} Unsubscribe function\n */\n subscribe(listener) {\n if (typeof listener !== 'function') {\n throw new TypeError('Listener must be a function');\n }\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Notify all listeners of state changes\n */\n notifyListeners() {\n const status = this.getSessionStatus();\n Array.from(this.listeners).forEach(listener => {\n try {\n listener(status);\n } catch (_) {\n // Silently ignore listener errors\n }\n });\n }\n\n /**\n * Clean up and reset the session manager\n */\n destroy() {\n this.stopTokenRefresh();\n this.initializationPromise = null;\n this.listeners.clear();\n this.setCookie('cf-session-id', '', -1);\n this.setCookie(this.config.tokenCookieName, '', -1);\n this.config = { ...DEFAULT_CONFIG };\n this.state = {\n isInitialized: false,\n isLoading: false,\n lastRefreshTime: null,\n error: null,\n errorCode: null,\n nextRefreshTime: null,\n tokenExpiry: null,\n cfSessionId: null,\n initializationFailed: false,\n };\n this._configured = false;\n }\n\n /**\n * Get the singleton instance\n * @returns {SessionManager} Singleton instance\n */\n static getInstance() {\n if (!SessionManager.instance) {\n SessionManager.instance = new SessionManager();\n }\n return SessionManager.instance;\n }\n}\n\nexport default SessionManager;","import { useState, useEffect, useCallback, useRef } from 'react';\nimport SessionManager from '../SessionManager.js';\n\n/**\n * React hook for session management\n * Provides session state and controls for React components\n *\n * @param {Object} options - Configuration options\n * @param {boolean} options.autoInitialize - Automatically initialize on mount (default: true)\n * @returns {Object} Session state and controls\n */\nexport function useSession(options = {}) {\n const { autoInitialize = true } = options;\n\n const sessionManager = SessionManager.getInstance();\n\n const [sessionState, setSessionState] = useState(() =>\n sessionManager.getSessionStatus()\n );\n\n // FIX #11: Separate countdown state — only this re-renders every second,\n // not the full session state which would cause unnecessary subscriber churn\n const [timeUntilRefresh, setTimeUntilRefresh] = useState(() => {\n const { nextRefreshTime } = sessionManager.getSessionStatus();\n return nextRefreshTime ? Math.max(0, nextRefreshTime - Date.now()) : null;\n });\n\n const nextRefreshTimeRef = useRef(sessionState.nextRefreshTime);\n\n // Subscribe to meaningful session state changes only\n useEffect(() => {\n const unsubscribe = sessionManager.subscribe((newState) => {\n setSessionState(newState);\n nextRefreshTimeRef.current = newState.nextRefreshTime;\n });\n\n return unsubscribe;\n }, []); // sessionManager is a singleton, no need to include in deps\n\n // Local tick timer for countdown display — avoids global tick in SessionManager\n useEffect(() => {\n if (!sessionState.nextRefreshTime) {\n setTimeUntilRefresh(null);\n return;\n }\n // Set immediately on mount / when nextRefreshTime changes\n setTimeUntilRefresh(Math.max(0, sessionState.nextRefreshTime - Date.now()));\n const tickId = setInterval(() => {\n const nrt = nextRefreshTimeRef.current;\n setTimeUntilRefresh(nrt ? Math.max(0, nrt - Date.now()) : null);\n }, 1000);\n return () => clearInterval(tickId);\n }, [sessionState.nextRefreshTime]);\n\n // Auto-initialize if enabled\n useEffect(() => {\n if (autoInitialize && !sessionState.isInitialized && !sessionState.isLoading && !sessionState.initializationFailed) {\n sessionManager.initialize().catch(() => {});\n }\n }, [autoInitialize, sessionState.isInitialized, sessionState.isLoading, sessionState.initializationFailed]);\n\n // Manual refresh function\n const refresh = useCallback(async () => {\n await sessionManager.refreshToken();\n }, []);\n\n // Initialize function (for manual control)\n const initialize = useCallback(async () => {\n await sessionManager.initialize();\n }, []);\n\n return {\n // State\n isInitialized: sessionState.isInitialized,\n isLoading: sessionState.isLoading,\n error: sessionState.error,\n lastRefreshTime: sessionState.lastRefreshTime,\n nextRefreshTime: sessionState.nextRefreshTime,\n timeUntilRefresh,\n initializationFailed: sessionState.initializationFailed,\n\n // Actions\n refresh,\n initialize,\n };\n}\n\nexport default useSession;","import SessionManager from './SessionManager.js';\nexport const CLIENT_PLATFORM = 'web';\n\n/**\n * Fetch interceptor with automatic token refresh on 401\n * @param {string} url - Request URL\n * @param {Object} options - Fetch options\n * @returns {Promise<Response>} Fetch response\n */\nexport async function fetchInterceptor(url, options = {}) {\n const sessionManager = SessionManager.getInstance();\n\n // Gate on session readiness: if not initialized (e.g., browser was closed and\n // reopened), this triggers bootstrap and waits; if a refresh is in progress\n // (tab wake, online recovery, concurrent 401), it piggybacks on it.\n await sessionManager.ensureReady();\n\n // FIX #6: Shallow-clone options to avoid mutating the caller's object\n const requestOptions = {\n ...options,\n credentials: options.credentials || 'include',\n headers: {\n ...options.headers,\n 'cf-session-id': sessionManager.getSessionId(),\n 'x-client-platform': CLIENT_PLATFORM,\n },\n };\n\n // Add token to header if HTTP (not HTTPS)\n if (sessionManager.shouldUseTokenHeader()) {\n const token = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (token) {\n requestOptions.headers[sessionManager.config.tokenCookieName] = token;\n }\n }\n\n let response = await fetch(url, requestOptions);\n\n // Retry once if unauthorized with INVALID_SESSION\n if (response.status === 401) {\n const clonedResponse = response.clone();\n try {\n const data = await clonedResponse.json();\n if (data.error_msg === sessionManager.config.invalidSessionError) {\n if (sessionManager.isRefreshing()) {\n await sessionManager.waitForRefresh();\n } else {\n await sessionManager.refreshToken();\n }\n requestOptions.headers['cf-session-id'] = sessionManager.getSessionId();\n if (sessionManager.shouldUseTokenHeader()) {\n const newToken = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (newToken) {\n requestOptions.headers[sessionManager.config.tokenCookieName] = newToken;\n }\n }\n response = await fetch(url, requestOptions);\n }\n } catch (e) {\n // Not JSON or parsing failed, return original response\n }\n }\n\n return response;\n}\n\n/**\n * Axios interceptor with automatic token refresh on 401 and INVALID_SESSION\n * @param {Object} axiosInstance - Axios instance\n * @returns {Object} The same axios instance with interceptors attached\n */\nexport function setupAxiosInterceptor(axiosInstance) {\n const sessionManager = SessionManager.getInstance();\n\n // FIX #7: Eject previous interceptors if this instance was already set up\n if (axiosInstance._gwSessionRequestId !== undefined) {\n axiosInstance.interceptors.request.eject(axiosInstance._gwSessionRequestId);\n }\n if (axiosInstance._gwSessionResponseId !== undefined) {\n axiosInstance.interceptors.response.eject(axiosInstance._gwSessionResponseId);\n }\n\n // Request interceptor to add cf-session-id, token, and credentials\n axiosInstance._gwSessionRequestId = axiosInstance.interceptors.request.use(\n async (config) => {\n // Gate on session readiness before every request\n await sessionManager.ensureReady();\n if (sessionManager.config.credentials) {\n config.withCredentials = true;\n }\n config.headers['cf-session-id'] = sessionManager.getSessionId();\n config.headers['x-client-platform'] = CLIENT_PLATFORM;\n if (sessionManager.shouldUseTokenHeader()) {\n const token = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (token) {\n config.headers[sessionManager.config.tokenCookieName] = token;\n }\n }\n return config;\n },\n (error) => Promise.reject(error)\n );\n\n // Response interceptor for token refresh\n axiosInstance._gwSessionResponseId = axiosInstance.interceptors.response.use(\n (response) => response,\n async (error) => {\n const originalRequest = error.config;\n\n // Retry once if unauthorized with INVALID_SESSION\n if (error.response?.status === 401 && !originalRequest._retry) {\n if (error.response?.data?.error_msg === sessionManager.config.invalidSessionError) {\n originalRequest._retry = true;\n\n if (sessionManager.isRefreshing()) {\n await sessionManager.waitForRefresh();\n } else {\n await sessionManager.refreshToken();\n }\n\n originalRequest.headers['cf-session-id'] = sessionManager.getSessionId();\n originalRequest.headers['x-client-platform'] = CLIENT_PLATFORM;\n if (sessionManager.shouldUseTokenHeader()) {\n const newToken = sessionManager.getToken(sessionManager.config.tokenCookieName);\n if (newToken) {\n originalRequest.headers[sessionManager.config.tokenCookieName] = newToken;\n }\n }\n return axiosInstance(originalRequest);\n }\n }\n\n return Promise.reject(error);\n }\n );\n\n return axiosInstance;\n}","/**\n * Logger utility — disabled for production builds.\n * All methods are no-ops to prevent leaking internal details to the browser console.\n */\n\nconst LOG_LEVELS = {\n NONE: 0,\n ERROR: 1,\n WARN: 2,\n INFO: 3,\n DEBUG: 4\n};\n\nclass Logger {\n setLevel() {}\n error() {}\n warn() {}\n info() {}\n debug() {}\n}\n\nexport const logger = new Logger();\nexport { LOG_LEVELS };\n"],"names":["SessionError","Error","constructor","message","code","details","super","this","name","captureStackTrace","Object","setPrototypeOf","prototype","ConfigurationError","BootstrapError","NetworkError","SSRError","DEFAULT_CONFIG","bootstrapUrl","refreshInterval","tokenExpiry","maxRetries","bootstrapTimeout","tokenCookieName","invalidSessionError","SessionManager","instance","config","state","isInitialized","isLoading","lastRefreshTime","error","errorCode","nextRefreshTime","cfSessionId","initializationFailed","refreshTimerId","listeners","Set","initializationPromise","_configured","_boundHandleVisibilityChange","_handleVisibilityChange","bind","_boundHandlePageShow","_handlePageShow","_boundHandleOnline","_handleOnline","configure","trim","undefined","isFinite","Number","isInteger","credentials","initialize","forceRefresh","window","token","getToken","notifyListeners","lastError","attempt","newSessionId","generateSessionId","controller","AbortController","timeoutId","setTimeout","abort","response","data","fetch","method","signal","headers","fetchError","timeout","url","clearTimeout","ok","status","statusText","json","jsonError","originalError","refresh_time","expire_time","tokenFromResponse","setCookie","setCfSessionId","Date","now","startTokenRefresh","delay","Math","min","pow","Promise","resolve","isRefreshing","waitForRefresh","_isTokenLikelyExpired","ensureReady","_","needsRefresh","sessionId","maxAge","cookieMaxAge","crypto","randomUUID","random","toString","substr","getSessionId","shouldUseTokenHeader","location","protocol","_sanitizeCookieName","replace","getCookie","document","sanitizedName","parts","cookie","split","length","decodeURIComponent","pop","shift","value","expires","toUTCString","secureFlag","encodedValue","encodeURIComponent","interval","stopTokenRefresh","async","refreshToken","errorMessage","String","retryDelay","_startVisibilityListener","_startOnlineListener","_stopVisibilityListener","_stopOnlineListener","addEventListener","removeEventListener","_refreshIfOverdue","catch","visibilityState","event","persisted","getSessionStatus","timeUntilRefresh","max","subscribe","listener","TypeError","add","delete","Array","from","forEach","destroy","clear","getInstance","useSession","options","autoInitialize","sessionManager","sessionState","setSessionState","useState","setTimeUntilRefresh","nextRefreshTimeRef","useRef","useEffect","newState","current","tickId","setInterval","nrt","clearInterval","refresh","useCallback","CLIENT_PLATFORM","fetchInterceptor","requestOptions","clonedResponse","clone","error_msg","newToken","e","setupAxiosInterceptor","axiosInstance","_gwSessionRequestId","interceptors","request","eject","_gwSessionResponseId","use","withCredentials","reject","originalRequest","_retry","LOG_LEVELS","NONE","ERROR","WARN","INFO","DEBUG","logger","setLevel","warn","info","debug"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mapnests/gateway-web-sdk",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Session token management SDK with automatic refresh for React/Next.js applications",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -36,7 +36,7 @@
36
36
  "nextjs",
37
37
  "sdk"
38
38
  ],
39
- "author": "Your Name <your.email@example.com>",
39
+ "author": "Md. Yeasir Arafat <myabadhon@gmail.com>",
40
40
  "license": "MIT",
41
41
  "engines": {
42
42
  "node": ">=14.0.0"
@@ -10,10 +10,12 @@ export interface SessionConfig {
10
10
  refreshInterval?: number;
11
11
  tokenExpiry?: number;
12
12
  maxRetries?: number;
13
+ bootstrapTimeout?: number;
13
14
  headers?: Record<string, string>;
14
15
  credentials?: boolean;
15
16
  logLevel?: 'NONE' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
16
17
  tokenCookieName?: string;
18
+ invalidSessionError?: string;
17
19
  }
18
20
 
19
21
  export interface SessionState {
@@ -82,6 +84,18 @@ export default class SessionManager {
82
84
  */
83
85
  waitForRefresh(): Promise<BootstrapResponse | void>;
84
86
 
87
+ /**
88
+ * Ensure the session is ready before making API calls.
89
+ * Waits for in-progress initialization, triggers bootstrap if needed,
90
+ * or resolves immediately if the session is live and token is fresh.
91
+ */
92
+ ensureReady(): Promise<void>;
93
+
94
+ /**
95
+ * Check if the session needs a refresh (initialized but token expired).
96
+ */
97
+ needsRefresh(): boolean;
98
+
85
99
  /**
86
100
  * Clean up and reset the session manager
87
101
  */