@mapnests/gateway-web-sdk 1.0.6 → 1.0.8

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
@@ -8,9 +8,10 @@ A lightweight, production-ready session token management SDK for React and Next.
8
8
  - HttpOnly cookie support for secure token storage
9
9
  - React hook (`useSession`) for seamless integration
10
10
  - Singleton pattern ensuring a single session instance across your app
11
- - Zero dependencies (only React as a peer dependency)
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
@@ -25,9 +26,11 @@ npm install @mapnests/gateway-web-sdk
25
26
 
26
27
  Choose one of the three approaches below based on your preferred HTTP client. All three approaches share the same **Step 1** (environment setup) and **Step 2** (session initialization).
27
28
 
29
+ > **Next.js users:** Skip the Common Setup below and go directly to the [Next.js Integration](#nextjs-integration) section, which provides its own Steps 1–2. Then return here for Approach A, B, or C.
30
+
28
31
  ---
29
32
 
30
- ### Common Setup (All Approaches)
33
+ ### Common Setup (Vite / CRA / React Apps)
31
34
 
32
35
  #### Step 1 — Environment Variables
33
36
 
@@ -45,8 +48,6 @@ VITE_TOKEN_COOKIE_NAME=token
45
48
  | `VITE_BOOTSTRAP_PATH` | Path to the session bootstrap endpoint |
46
49
  | `VITE_TOKEN_COOKIE_NAME` | Name of the token cookie set by your server |
47
50
 
48
- > If you are using Next.js, prefix with `NEXT_PUBLIC_` instead of `VITE_`.
49
-
50
51
  Then create a config helper to read these values:
51
52
 
52
53
  ```js
@@ -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
@@ -297,46 +298,153 @@ export default function Dashboard() {
297
298
 
298
299
  ## Next.js Integration
299
300
 
300
- ### App Router (`app/layout.js`)
301
+ > **Note:** For Next.js, use `NEXT_PUBLIC_` prefixed environment variables instead of `VITE_`, and replace the common **Step 1** config helper and **Step 2** initialization with the Next.js-specific setup below.
302
+
303
+ ### Step 1 — Environment Variables
304
+
305
+ ```env
306
+ NEXT_PUBLIC_API_BASE_URL=https://your-gateway.example.com
307
+ NEXT_PUBLIC_BOOTSTRAP_PATH=/api/session/bootstrap
308
+ NEXT_PUBLIC_TOKEN_COOKIE_NAME=token
309
+ ```
310
+
311
+ ### Step 2 — Config Helper
312
+
313
+ ```js
314
+ // src/config.js
315
+ const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL;
316
+ const BOOTSTRAP_PATH = process.env.NEXT_PUBLIC_BOOTSTRAP_PATH;
317
+ const TOKEN_COOKIE_NAME = process.env.NEXT_PUBLIC_TOKEN_COOKIE_NAME;
318
+
319
+ if (!API_BASE_URL) throw new Error('NEXT_PUBLIC_API_BASE_URL is not defined');
320
+ if (!BOOTSTRAP_PATH) throw new Error('NEXT_PUBLIC_BOOTSTRAP_PATH is not defined');
321
+ if (!TOKEN_COOKIE_NAME) throw new Error('NEXT_PUBLIC_TOKEN_COOKIE_NAME is not defined');
322
+
323
+ export { API_BASE_URL, BOOTSTRAP_PATH, TOKEN_COOKIE_NAME };
324
+ ```
325
+
326
+ ### App Router
327
+
328
+ #### Step 3 — Create a Session Provider
329
+
330
+ Create a client component that initializes the session. This keeps the root layout as a Server Component, preserving the benefits of React Server Components.
301
331
 
302
332
  ```jsx
333
+ // app/providers/SessionProvider.jsx
303
334
  'use client';
304
335
 
305
336
  import { useEffect } from 'react';
306
337
  import { SessionManager } from '@mapnests/gateway-web-sdk';
338
+ import { API_BASE_URL, BOOTSTRAP_PATH, TOKEN_COOKIE_NAME } from '@/src/config';
307
339
 
308
- export default function RootLayout({ children }) {
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.
342
+ const sessionManager = SessionManager.getInstance();
343
+ sessionManager.configure({
344
+ bootstrapUrl: `${API_BASE_URL}${BOOTSTRAP_PATH}`,
345
+ tokenCookieName: TOKEN_COOKIE_NAME,
346
+ });
347
+
348
+ export default function SessionProvider({ children }) {
349
+ // initialize() calls the bootstrap API — must run in useEffect (client-only)
309
350
  useEffect(() => {
310
- const sessionManager = SessionManager.getInstance();
311
- sessionManager.configure({
312
- bootstrapUrl: `${process.env.NEXT_PUBLIC_API_BASE_URL}${process.env.NEXT_PUBLIC_BOOTSTRAP_PATH}`,
313
- tokenCookieName: process.env.NEXT_PUBLIC_TOKEN_COOKIE_NAME,
314
- });
315
- sessionManager.initialize();
351
+ sessionManager.initialize().catch(err =>
352
+ console.error('Failed to initialize session:', err)
353
+ );
316
354
  }, []);
317
355
 
356
+ return children;
357
+ }
358
+ ```
359
+
360
+ #### Step 4 — Add the Provider to the Root Layout
361
+
362
+ The root layout stays as a Server Component — do **not** add `'use client'` here.
363
+
364
+ ```jsx
365
+ // app/layout.js
366
+ import SessionProvider from './providers/SessionProvider';
367
+
368
+ export default function RootLayout({ children }) {
318
369
  return (
319
370
  <html lang="en">
320
- <body>{children}</body>
371
+ <body>
372
+ <SessionProvider>{children}</SessionProvider>
373
+ </body>
321
374
  </html>
322
375
  );
323
376
  }
324
377
  ```
325
378
 
326
- ### Pages Router (`pages/_app.js`)
379
+ #### Step 5 — Create an API Layer and Use in a Page
380
+
381
+ After your router setup is complete, create an API layer using any of **Approach A / B / C** from the [Implementation Guide](#implementation-guide) above. The only change needed is to replace `import.meta.env.VITE_*` references with imports from your `src/config.js`.
382
+
383
+ For example, using Approach A (Fetch Interceptor):
384
+
385
+ ```js
386
+ // src/api.js
387
+ import { fetchInterceptor } from '@mapnests/gateway-web-sdk';
388
+ import { API_BASE_URL } from './config';
389
+
390
+ export const getUser = () =>
391
+ fetchInterceptor(`${API_BASE_URL}/api/user`);
392
+ ```
393
+
394
+ Then use it in a page component. Page components that use hooks must be client components.
327
395
 
328
396
  ```jsx
397
+ // app/dashboard/page.jsx
398
+ 'use client';
399
+
400
+ import { useEffect, useState } from 'react';
401
+ import { useSession } from '@mapnests/gateway-web-sdk';
402
+ import { getUser } from '@/src/api';
403
+
404
+ export default function DashboardPage() {
405
+ const { isInitialized, isLoading, error } = useSession();
406
+ const [user, setUser] = useState(null);
407
+
408
+ useEffect(() => {
409
+ if (!isInitialized) return;
410
+
411
+ getUser()
412
+ .then(res => res.json())
413
+ .then(setUser)
414
+ .catch(err => console.error('Failed to fetch user:', err));
415
+ }, [isInitialized]);
416
+
417
+ if (isLoading) return <p>Loading session...</p>;
418
+ if (error) return <p>Session error: {error}</p>;
419
+ if (!user) return <p>Loading data...</p>;
420
+
421
+ return <pre>{JSON.stringify(user, null, 2)}</pre>;
422
+ }
423
+ ```
424
+
425
+ ### Pages Router
426
+
427
+ #### Step 3 — Initialize in `_app.js`
428
+
429
+ ```jsx
430
+ // pages/_app.js
329
431
  import { useEffect } from 'react';
330
432
  import { SessionManager } from '@mapnests/gateway-web-sdk';
433
+ import { API_BASE_URL, BOOTSTRAP_PATH, TOKEN_COOKIE_NAME } from '../src/config';
434
+
435
+ // Configure at module level — runs once when the module is first imported.
436
+ const sessionManager = SessionManager.getInstance();
437
+ sessionManager.configure({
438
+ bootstrapUrl: `${API_BASE_URL}${BOOTSTRAP_PATH}`,
439
+ tokenCookieName: TOKEN_COOKIE_NAME,
440
+ });
331
441
 
332
442
  function MyApp({ Component, pageProps }) {
443
+ // initialize() needs the browser — must run in useEffect
333
444
  useEffect(() => {
334
- const sessionManager = SessionManager.getInstance();
335
- sessionManager.configure({
336
- bootstrapUrl: `${process.env.NEXT_PUBLIC_API_BASE_URL}${process.env.NEXT_PUBLIC_BOOTSTRAP_PATH}`,
337
- tokenCookieName: process.env.NEXT_PUBLIC_TOKEN_COOKIE_NAME,
338
- });
339
- sessionManager.initialize();
445
+ sessionManager.initialize().catch(err =>
446
+ console.error('Failed to initialize session:', err)
447
+ );
340
448
  }, []);
341
449
 
342
450
  return <Component {...pageProps} />;
@@ -345,6 +453,37 @@ function MyApp({ Component, pageProps }) {
345
453
  export default MyApp;
346
454
  ```
347
455
 
456
+ #### Step 4 — Create an API Layer and Use in a Page
457
+
458
+ Same as the App Router — create an `src/api.js` using any of **Approach A / B / C**, then use it in your page:
459
+
460
+ ```jsx
461
+ // pages/dashboard.jsx
462
+ import { useEffect, useState } from 'react';
463
+ import { useSession } from '@mapnests/gateway-web-sdk';
464
+ import { getUser } from '../src/api';
465
+
466
+ export default function Dashboard() {
467
+ const { isInitialized, isLoading, error } = useSession();
468
+ const [user, setUser] = useState(null);
469
+
470
+ useEffect(() => {
471
+ if (!isInitialized) return;
472
+
473
+ getUser()
474
+ .then(res => res.json())
475
+ .then(setUser)
476
+ .catch(err => console.error('Failed to fetch user:', err));
477
+ }, [isInitialized]);
478
+
479
+ if (isLoading) return <p>Loading session...</p>;
480
+ if (error) return <p>Session error: {error}</p>;
481
+ if (!user) return <p>Loading data...</p>;
482
+
483
+ return <pre>{JSON.stringify(user, null, 2)}</pre>;
484
+ }
485
+ ```
486
+
348
487
  ---
349
488
 
350
489
  ## API Reference
@@ -366,9 +505,10 @@ React hook for session management.
366
505
  isInitialized: boolean;
367
506
  isLoading: boolean;
368
507
  error: string | null;
369
- lastRefreshTime: number;
370
- nextRefreshTime: number;
371
- timeUntilRefresh: number;
508
+ lastRefreshTime: number | null;
509
+ nextRefreshTime: number | null;
510
+ timeUntilRefresh: number | null;
511
+ initializationFailed: boolean;
372
512
  refresh: () => Promise<void>;
373
513
  initialize: () => Promise<void>;
374
514
  }
@@ -423,7 +563,33 @@ Returns the token value from the named cookie, or `null`.
423
563
 
424
564
  #### `shouldUseTokenHeader()`
425
565
 
426
- Returns `true` if the token should be sent as a request header (manual token mode or HTTP protocol).
566
+ Returns `true` if the token should be sent as a request header (i.e. when the app is served over HTTP, not HTTPS).
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
+ ```
427
593
 
428
594
  #### `isRefreshing()`
429
595
 
@@ -437,6 +603,22 @@ Returns a promise that resolves when the in-progress refresh completes.
437
603
 
438
604
  Returns the current session state object.
439
605
 
606
+ ```typescript
607
+ {
608
+ isInitialized: boolean;
609
+ isLoading: boolean;
610
+ lastRefreshTime: number | null;
611
+ nextRefreshTime: number | null;
612
+ tokenExpiry: number | null;
613
+ error: string | null;
614
+ errorCode: string | null;
615
+ initializationFailed: boolean;
616
+ timeUntilRefresh: number | null;
617
+ }
618
+ ```
619
+
620
+ > **Note:** `getSessionStatus()` returns `tokenExpiry` and `errorCode` which are not exposed by the `useSession` hook. Use this method directly if you need those fields.
621
+
440
622
  #### `subscribe(listener)`
441
623
 
442
624
  Subscribe to session state changes. Returns an unsubscribe function.
@@ -453,7 +635,7 @@ Clean up timers, listeners, and cookies. Resets the session manager.
453
635
 
454
636
  ### `fetchInterceptor(url, options?)`
455
637
 
456
- 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.
457
639
 
458
640
  ```javascript
459
641
  import { fetchInterceptor } from '@mapnests/gateway-web-sdk';
@@ -462,7 +644,7 @@ const response = await fetchInterceptor('/api/data');
462
644
 
463
645
  ### `setupAxiosInterceptor(axiosInstance)`
464
646
 
465
- 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`.
466
648
 
467
649
  ```javascript
468
650
  import axios from 'axios';
@@ -470,6 +652,35 @@ import { setupAxiosInterceptor } from '@mapnests/gateway-web-sdk';
470
652
  const api = setupAxiosInterceptor(axios.create({ baseURL: '/api' }));
471
653
  ```
472
654
 
655
+ ### Error Classes
656
+
657
+ The SDK exports custom error classes for typed error handling:
658
+
659
+ ```javascript
660
+ import {
661
+ SessionError, // Base error class (code, details)
662
+ ConfigurationError, // Invalid configuration (code: 'CONFIGURATION_ERROR')
663
+ BootstrapError, // Bootstrap API failure (code: 'BOOTSTRAP_ERROR')
664
+ NetworkError, // Network-level failure (code: 'NETWORK_ERROR')
665
+ SSRError, // Called in non-browser environment (code: 'SSR_ERROR')
666
+ } from '@mapnests/gateway-web-sdk';
667
+ ```
668
+
669
+ All errors extend `SessionError`, which provides `code` (string) and `details` (object) properties.
670
+
671
+ ### `logger` and `LOG_LEVELS`
672
+
673
+ The SDK's internal logger is exported for advanced use (e.g. setting log level independently of `configure()`):
674
+
675
+ ```javascript
676
+ import { logger, LOG_LEVELS } from '@mapnests/gateway-web-sdk';
677
+
678
+ logger.setLevel('DEBUG'); // or logger.setLevel(LOG_LEVELS.DEBUG)
679
+ logger.info('Custom log'); // [SessionManager] Custom log
680
+ ```
681
+
682
+ Available levels: `NONE` (0), `ERROR` (1), `WARN` (2, default), `INFO` (3), `DEBUG` (4).
683
+
473
684
  ---
474
685
 
475
686
  ## Security Notice
@@ -507,6 +718,12 @@ This SDK prioritizes **server-set HttpOnly cookies** for maximum security. The S
507
718
  - The SDK uses singleton pattern, but ensure you're not calling `initialize()` multiple times
508
719
  - Use `autoInitialize: false` in `useSession()` if you want manual control
509
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
+
510
727
  ### Next.js SSR errors
511
728
  - The SDK detects SSR environments and prevents initialization
512
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={NONE:0,ERROR:1,WARN:2,INFO:3,DEBUG:4};const a=new class{constructor(){this.level=o.WARN}setLevel(e){this.level="string"==typeof e?o[e.toUpperCase()]??o.WARN:e}error(...e){this.level>=o.ERROR&&console.error("[SessionManager]",...e)}warn(...e){this.level>=o.WARN&&console.warn("[SessionManager]",...e)}info(...e){this.level>=o.INFO&&console.info("[SessionManager]",...e)}debug(...e){this.level>=o.DEBUG&&console.debug("[SessionManager]",...e)}},l={bootstrapUrl:"/session/bootstrap",refreshInterval:15e5,tokenExpiry:18e5,maxRetries:3,bootstrapTimeout:3e4,tokenCookieName:"token",invalidSessionError:"INVALID-GW-SESSION"};class h{constructor(){if(h.instance)return h.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),h.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,logLevel:e.logLevel||"WARN"},a.setLevel(this.config.logLevel),this._configured=!0,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 r("Cannot initialize in non-browser environment (SSR)");if(this._configured||a.warn("initialize() called before configure(). Using default config."),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(this.state.isInitialized&&!e)return a.debug("Session already initialized"),{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();a.info(`Calling bootstrap API (attempt ${t}/${this.config.maxRetries}):`,this.config.bootstrapUrl);const i=new AbortController,r=setTimeout(()=>i.abort(),this.config.bootstrapTimeout);let o,l;try{o=await fetch(this.config.bootstrapUrl,{method:"GET",credentials:this.config.credentials?"include":"same-origin",signal:i.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(r)}if(!o.ok)throw new s(`Bootstrap failed: ${o.status} ${o.statusText}`,{status:o.status,statusText:o.statusText,url:this.config.bootstrapUrl});try{l=await o.json()}catch(e){throw new s("Bootstrap response is not valid JSON",{originalError:e.message})}a.info("Bootstrap successful");const h=l.refresh_time?1e3*l.refresh_time:this.config.refreshInterval,d=l.expire_time?1e3*l.expire_time:this.config.tokenExpiry,f=l[this.config.tokenCookieName];return f&&this.setCookie(this.config.tokenCookieName,f,d/1e3),this.setCfSessionId(e,d/1e3),this.state.isInitialized=!0,this.state.isLoading=!1,this.state.lastRefreshTime=Date.now(),this.state.nextRefreshTime=Date.now()+h,this.state.tokenExpiry=d,this.state.error=null,this.startTokenRefresh(h),this.notifyListeners(),l}catch(i){if(e=i instanceof Error?i:new n("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()}_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){a.debug("ensureReady: in-flight initialization failed, proceeding with request")}else if(this._configured&&!this.state.initializationFailed)try{await this.initialize(this.state.isInitialized)}catch(e){a.debug("ensureReady: initialization failed, proceeding with request")}}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 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=this._sanitizeCookieName(e);if(!s)return void a.error("Invalid cookie name provided");const n=new Date(Date.now()+1e3*i).toUTCString(),r="undefined"!=typeof window&&"https:"===window.location.protocol,o=r?"; Secure":"",l=encodeURIComponent(t);document.cookie=`${s}=${l}; path=/; expires=${n}; SameSite=Lax${o}`,a.debug(`Cookie set: ${s}, expires in ${i}s${r?" (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(t){const i=t instanceof Error?t.message:String(t),s=t?.code||"UNKNOWN_ERROR";a.error("Auto-refresh failed:",i),this.state.error=i,this.state.errorCode=s,this.notifyListeners();const n=Math.min(2*e,6e4);a.info(`Scheduling auto-refresh retry in ${n/1e3}s`),this.startTokenRefresh(n)}},e),this._startVisibilityListener(),this._startOnlineListener()}stopTokenRefresh(){this.refreshTimerId&&(clearTimeout(this.refreshTimerId),this.refreshTimerId=null,a.debug("Token refresh timer stopped")),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)&&(a.info("Missed scheduled refresh — refreshing token now"),this.refreshToken().catch(e=>{const t=e instanceof Error?e.message:String(e);a.error("Triggered refresh failed:",t)}),!0)}_handleVisibilityChange(){"visible"===document.visibilityState&&this._refreshIfOverdue()}_handlePageShow(e){e.persisted&&this.state.isInitialized&&!this.state.initializationFailed&&(this.isRefreshing()||(a.info("Page restored from bfcache — forcing bootstrap"),this.refreshToken().catch(e=>{const t=e instanceof Error?e.message:String(e);a.error("bfcache-triggered refresh failed:",t)})))}_startOnlineListener(){"undefined"!=typeof window&&window.addEventListener("online",this._boundHandleOnline)}_stopOnlineListener(){"undefined"!=typeof window&&window.removeEventListener("online",this._boundHandleOnline)}_handleOnline(){this.state.initializationFailed&&(a.info("Network restored — retrying session initialization"),this.refreshToken().catch(e=>{const t=e instanceof Error?e.message:String(e);a.error("Online-triggered refresh failed:",t)}))}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.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,a.info("Destroyed")}static getInstance(){return h.instance||(h.instance=new h),h.instance}}const d="web";exports.BootstrapError=s,exports.CLIENT_PLATFORM=d,exports.ConfigurationError=i,exports.LOG_LEVELS=o,exports.NetworkError=n,exports.SSRError=r,exports.SessionError=t,exports.SessionManager=h,exports.default=h,exports.fetchInterceptor=async function(e,t={}){const i=h.getInstance();await i.ensureReady();const s={...t,credentials:t.credentials||"include",headers:{...t.headers,"cf-session-id":i.getSessionId(),"x-client-platform":d}};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=a,exports.setupAxiosInterceptor=function(e){const t=h.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"]=d,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"]=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},exports.useSession=function(t={}){const{autoInitialize:i=!0}=t,s=h.getInstance(),[n,r]=e.useState(()=>s.getSessionStatus()),[o,l]=e.useState(()=>{const{nextRefreshTime:e}=s.getSessionStatus();return e?Math.max(0,e-Date.now()):null}),d=e.useRef(n.nextRefreshTime);e.useEffect(()=>s.subscribe(e=>{r(e),d.current=e.nextRefreshTime}),[]),e.useEffect(()=>{if(!n.nextRefreshTime)return void l(null);l(Math.max(0,n.nextRefreshTime-Date.now()));const e=setInterval(()=>{const e=d.current;l(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(e=>{a.error("Auto-initialization failed:",e)})},[i,n.isInitialized,n.isLoading,n.initializationFailed]);const f=e.useCallback(async()=>{try{await s.refreshToken()}catch(e){throw a.error("Manual refresh failed:",e),e}},[]),c=e.useCallback(async()=>{try{await s.initialize()}catch(e){throw a.error("Manual initialization failed:",e),e}},[]);return{isInitialized:n.isInitialized,isLoading:n.isLoading,error:n.error,lastRefreshTime:n.lastRefreshTime,nextRefreshTime:n.nextRefreshTime,timeUntilRefresh:o,initializationFailed:n.initializationFailed,refresh:f,initialize:c}};
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/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\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 logLevel: config.logLevel || 'WARN'\n };\n\n logger.setLevel(this.config.logLevel);\n this._configured = true;\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} 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._configured) {\n logger.warn('initialize() called before configure(). Using default config.');\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 // If already initialized in this page session and not a forced refresh, return current state\n if (this.state.isInitialized && !forceRefresh) {\n logger.debug('Session already initialized');\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 logger.info(`Calling bootstrap API (attempt ${attempt}/${this.config.maxRetries}):`, this.config.bootstrapUrl);\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 logger.info('Bootstrap successful');\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 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 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 * 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 logger.debug('ensureReady: in-flight initialization failed, proceeding with request');\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 logger.debug('ensureReady: initialization failed, proceeding with request');\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 (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 const sanitizedName = this._sanitizeCookieName(name);\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 // Schedule retry instead of dying permanently\n const retryDelay = Math.min(interval * 2, 60000);\n logger.info(`Scheduling auto-refresh retry in ${retryDelay / 1000}s`);\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 logger.debug('Token refresh timer stopped');\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 logger.info('Missed scheduled refresh — refreshing token now');\n this.refreshToken().catch(error => {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('Triggered refresh failed:', errorMessage);\n });\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 logger.info('Page restored from bfcache — forcing bootstrap');\n this.refreshToken().catch(error => {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('bfcache-triggered refresh failed:', errorMessage);\n });\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 logger.info('Network restored — retrying session initialization');\n this.refreshToken().catch(error => {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('Online-triggered refresh failed:', errorMessage);\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 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.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 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;","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}","import { useState, useEffect, useCallback, useRef } 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 // 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(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,\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","LOG_LEVELS","NONE","ERROR","WARN","INFO","DEBUG","logger","level","setLevel","toUpperCase","error","args","console","warn","info","debug","DEFAULT_CONFIG","bootstrapUrl","refreshInterval","tokenExpiry","maxRetries","bootstrapTimeout","tokenCookieName","invalidSessionError","SessionManager","instance","config","state","isInitialized","isLoading","lastRefreshTime","errorCode","nextRefreshTime","cfSessionId","initializationFailed","refreshTimerId","listeners","Set","initializationPromise","_configured","_boundHandleVisibilityChange","_handleVisibilityChange","bind","_boundHandlePageShow","_handlePageShow","_boundHandleOnline","_handleOnline","configure","trim","undefined","isFinite","Number","isInteger","credentials","logLevel","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","String","value","expires","toUTCString","isSecure","secureFlag","encodedValue","encodeURIComponent","interval","stopTokenRefresh","async","refreshToken","errorMessage","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","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 i,useEffect as t,useCallback as s}from"react";class n extends Error{constructor(e,i,t={}){super(e),this.name="SessionError",this.code=i,this.details=t,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor),Object.setPrototypeOf(this,n.prototype)}}class r extends n{constructor(e,i){super(e,"CONFIGURATION_ERROR",i),this.name="ConfigurationError"}}class o extends n{constructor(e,i){super(e,"BOOTSTRAP_ERROR",i),this.name="BootstrapError"}}class a extends n{constructor(e,i){super(e,"NETWORK_ERROR",i),this.name="NetworkError"}}class h extends n{constructor(e,i={}){super(e,"SSR_ERROR",i),this.name="SSRError"}}const l={NONE:0,ERROR:1,WARN:2,INFO:3,DEBUG:4};const d=new class{constructor(){this.level=l.WARN}setLevel(e){this.level="string"==typeof e?l[e.toUpperCase()]??l.WARN:e}error(...e){this.level>=l.ERROR&&console.error("[SessionManager]",...e)}warn(...e){this.level>=l.WARN&&console.warn("[SessionManager]",...e)}info(...e){this.level>=l.INFO&&console.info("[SessionManager]",...e)}debug(...e){this.level>=l.DEBUG&&console.debug("[SessionManager]",...e)}},f={bootstrapUrl:"/session/bootstrap",refreshInterval:15e5,tokenExpiry:18e5,maxRetries:3,bootstrapTimeout:3e4,tokenCookieName:"token",invalidSessionError:"INVALID-GW-SESSION"};class c{constructor(){if(c.instance)return c.instance;this.config={...f},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),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})}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,logLevel:e.logLevel||"WARN"},d.setLevel(this.config.logLevel),this._configured=!0,this.config.refreshInterval&&this.config.tokenExpiry&&this.config.refreshInterval>=this.config.tokenExpiry&&d.warn("refreshInterval should be less than tokenExpiry to prevent race conditions")}async initialize(e=!1){if("undefined"==typeof window)throw new h("Cannot initialize in non-browser environment (SSR)");if(this._configured||d.warn("initialize() called before configure(). Using default config."),this.state.initializationFailed)throw d.warn("Initialization previously failed. Reload page to retry."),new o("Initialization failed after max retries. Reload page to retry.");if(this.initializationPromise)return d.debug("Initialization already in progress, waiting..."),this.initializationPromise;if(this.state.isInitialized&&!e)return d.debug("Session already initialized"),{token:this.getToken(this.config.tokenCookieName)};this.state.isLoading=!0,this.state.error=null,this.notifyListeners(),this.initializationPromise=(async()=>{let e;for(let i=1;i<=this.config.maxRetries;i++)try{const e=this.generateSessionId();d.info(`Calling bootstrap API (attempt ${i}/${this.config.maxRetries}):`,this.config.bootstrapUrl);const t=new AbortController,s=setTimeout(()=>t.abort(),this.config.bootstrapTimeout);let n,r;try{n=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(s)}if(!n.ok)throw new o(`Bootstrap failed: ${n.status} ${n.statusText}`,{status:n.status,statusText:n.statusText,url:this.config.bootstrapUrl});try{r=await n.json()}catch(e){throw new o("Bootstrap response is not valid JSON",{originalError:e.message})}d.info("Bootstrap successful");const h=r.refresh_time?1e3*r.refresh_time:this.config.refreshInterval,l=r.expire_time?1e3*r.expire_time:this.config.tokenExpiry,f=r[this.config.tokenCookieName];return f&&this.setCookie(this.config.tokenCookieName,f,l/1e3),this.setCfSessionId(e,l/1e3),this.state.isInitialized=!0,this.state.isLoading=!1,this.state.lastRefreshTime=Date.now(),this.state.nextRefreshTime=Date.now()+h,this.state.tokenExpiry=l,this.state.error=null,this.startTokenRefresh(h),this.notifyListeners(),r}catch(t){if(e=t instanceof Error?t:new a("Unknown error occurred",{error:t}),d.error(`Bootstrap attempt ${i} failed:`,e.message),i<this.config.maxRetries){const e=Math.min(1e3*Math.pow(2,i-1),5e3);d.info(`Retrying in ${e}ms...`),await new Promise(i=>setTimeout(i,e))}}throw d.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?(d.debug("Waiting for ongoing refresh to complete"),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){d.debug("ensureReady: in-flight initialization failed, proceeding with request")}else if(this._configured&&!this.state.initializationFailed)try{await this.initialize(this.state.isInitialized)}catch(e){d.debug("ensureReady: initialization failed, proceeding with request")}}needsRefresh(){return this.state.isInitialized&&this._isTokenLikelyExpired()}setCfSessionId(e,i){this.state.cfSessionId=e;const t=i||this.config.tokenExpiry/1e3;this.setCookie("cf-session-id",e,t)}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 i=this._sanitizeCookieName(e);if(!i)return null;const t=`; ${document.cookie}`.split(`; ${i}=`);if(2===t.length)try{return decodeURIComponent(t.pop().split(";").shift())}catch(e){return d.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,i,t){if("undefined"==typeof document)return void d.warn("Cannot set cookie in non-browser environment");const s=this._sanitizeCookieName(e);if(!s)return void d.error("Invalid cookie name provided");const n=new Date(Date.now()+1e3*t).toUTCString(),r="undefined"!=typeof window&&"https:"===window.location.protocol,o=r?"; Secure":"",a=encodeURIComponent(i);document.cookie=`${s}=${a}; path=/; expires=${n}; SameSite=Lax${o}`,d.debug(`Cookie set: ${s}, expires in ${t}s${r?" (Secure)":""} [WARNING: Not HttpOnly]`)}startTokenRefresh(e=this.config.refreshInterval){this.stopTokenRefresh(),d.info(`Scheduling token refresh in ${e/1e3} seconds`),this.refreshTimerId=setTimeout(async()=>{d.info("Auto-refreshing token...");try{await this.refreshToken()}catch(i){const t=i instanceof Error?i.message:String(i),s=i?.code||"UNKNOWN_ERROR";d.error("Auto-refresh failed:",t),this.state.error=t,this.state.errorCode=s,this.notifyListeners();const n=Math.min(2*e,6e4);d.info(`Scheduling auto-refresh retry in ${n/1e3}s`),this.startTokenRefresh(n)}},e),this._startVisibilityListener(),this._startOnlineListener()}stopTokenRefresh(){this.refreshTimerId&&(clearTimeout(this.refreshTimerId),this.refreshTimerId=null,d.debug("Token refresh timer stopped")),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:i}=this.state;return!!(i&&e>=i)&&(d.info("Missed scheduled refresh — refreshing token now"),this.refreshToken().catch(e=>{const i=e instanceof Error?e.message:String(e);d.error("Triggered refresh failed:",i)}),!0)}_handleVisibilityChange(){"visible"===document.visibilityState&&this._refreshIfOverdue()}_handlePageShow(e){e.persisted&&this.state.isInitialized&&!this.state.initializationFailed&&(this.isRefreshing()||(d.info("Page restored from bfcache — forcing bootstrap"),this.refreshToken().catch(e=>{const i=e instanceof Error?e.message:String(e);d.error("bfcache-triggered refresh failed:",i)})))}_startOnlineListener(){"undefined"!=typeof window&&window.addEventListener("online",this._boundHandleOnline)}_stopOnlineListener(){"undefined"!=typeof window&&window.removeEventListener("online",this._boundHandleOnline)}_handleOnline(){this.state.initializationFailed&&(d.info("Network restored — retrying session initialization"),this.refreshToken().catch(e=>{const i=e instanceof Error?e.message:String(e);d.error("Online-triggered refresh failed:",i)}))}async refreshToken(){return d.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(i=>{try{i(e)}catch(e){const i=e instanceof Error?e.message:String(e);d.error("Listener error:",i)}})}destroy(){this.stopTokenRefresh(),this.initializationPromise=null,this.listeners.clear(),this.setCookie("cf-session-id","",-1),this.setCookie(this.config.tokenCookieName,"",-1),this.config={...f},this.state={isInitialized:!1,isLoading:!1,lastRefreshTime:null,error:null,errorCode:null,nextRefreshTime:null,tokenExpiry:null,cfSessionId:null,initializationFailed:!1},this._configured=!1,d.info("Destroyed")}static getInstance(){return c.instance||(c.instance=new c),c.instance}}function u(n={}){const{autoInitialize:r=!0}=n,o=c.getInstance(),[a,h]=e(()=>o.getSessionStatus()),[l,f]=e(()=>{const{nextRefreshTime:e}=o.getSessionStatus();return e?Math.max(0,e-Date.now()):null}),u=i(a.nextRefreshTime);t(()=>o.subscribe(e=>{h(e),u.current=e.nextRefreshTime}),[]),t(()=>{if(!a.nextRefreshTime)return void f(null);f(Math.max(0,a.nextRefreshTime-Date.now()));const e=setInterval(()=>{const e=u.current;f(e?Math.max(0,e-Date.now()):null)},1e3);return()=>clearInterval(e)},[a.nextRefreshTime]),t(()=>{!r||a.isInitialized||a.isLoading||a.initializationFailed||o.initialize().catch(e=>{d.error("Auto-initialization failed:",e)})},[r,a.isInitialized,a.isLoading,a.initializationFailed]);const g=s(async()=>{try{await o.refreshToken()}catch(e){throw d.error("Manual refresh failed:",e),e}},[]),m=s(async()=>{try{await o.initialize()}catch(e){throw d.error("Manual initialization failed:",e),e}},[]);return{isInitialized:a.isInitialized,isLoading:a.isLoading,error:a.error,lastRefreshTime:a.lastRefreshTime,nextRefreshTime:a.nextRefreshTime,timeUntilRefresh:l,initializationFailed:a.initializationFailed,refresh:g,initialize:m}}const g="web";async function m(e,i={}){const t=c.getInstance();await t.ensureReady();const s={...i,credentials:i.credentials||"include",headers:{...i.headers,"cf-session-id":t.getSessionId(),"x-client-platform":g}};if(t.shouldUseTokenHeader()){const e=t.getToken(t.config.tokenCookieName);e&&(s.headers[t.config.tokenCookieName]=e)}let n=await fetch(e,s);if(401===n.status){const i=n.clone();try{if((await i.json()).error_msg===t.config.invalidSessionError){if(t.isRefreshing()?await t.waitForRefresh():await t.refreshToken(),s.headers["cf-session-id"]=t.getSessionId(),t.shouldUseTokenHeader()){const e=t.getToken(t.config.tokenCookieName);e&&(s.headers[t.config.tokenCookieName]=e)}n=await fetch(e,s)}}catch(e){}}return n}function p(e){const i=c.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 i.ensureReady(),i.config.credentials&&(e.withCredentials=!0),e.headers["cf-session-id"]=i.getSessionId(),e.headers["x-client-platform"]=g,i.shouldUseTokenHeader()){const t=i.getToken(i.config.tokenCookieName);t&&(e.headers[i.config.tokenCookieName]=t)}return e},e=>Promise.reject(e)),e._gwSessionResponseId=e.interceptors.response.use(e=>e,async t=>{const s=t.config;if(401===t.response?.status&&!s._retry&&t.response?.data?.error_msg===i.config.invalidSessionError){if(s._retry=!0,i.isRefreshing()?await i.waitForRefresh():await i.refreshToken(),s.headers["cf-session-id"]=i.getSessionId(),s.headers["x-client-platform"]=g,i.shouldUseTokenHeader()){const e=i.getToken(i.config.tokenCookieName);e&&(s.headers[i.config.tokenCookieName]=e)}return e(s)}return Promise.reject(t)}),e}export{o as BootstrapError,g as CLIENT_PLATFORM,r as ConfigurationError,l as LOG_LEVELS,a as NetworkError,h as SSRError,n as SessionError,c as SessionManager,c as default,m as fetchInterceptor,d as logger,p as setupAxiosInterceptor,u 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/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\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 logLevel: config.logLevel || 'WARN'\n };\n\n logger.setLevel(this.config.logLevel);\n this._configured = true;\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} 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._configured) {\n logger.warn('initialize() called before configure(). Using default config.');\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 // If already initialized in this page session and not a forced refresh, return current state\n if (this.state.isInitialized && !forceRefresh) {\n logger.debug('Session already initialized');\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 logger.info(`Calling bootstrap API (attempt ${attempt}/${this.config.maxRetries}):`, this.config.bootstrapUrl);\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 logger.info('Bootstrap successful');\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 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 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 * 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 logger.debug('ensureReady: in-flight initialization failed, proceeding with request');\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 logger.debug('ensureReady: initialization failed, proceeding with request');\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 (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 const sanitizedName = this._sanitizeCookieName(name);\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 // Schedule retry instead of dying permanently\n const retryDelay = Math.min(interval * 2, 60000);\n logger.info(`Scheduling auto-refresh retry in ${retryDelay / 1000}s`);\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 logger.debug('Token refresh timer stopped');\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 logger.info('Missed scheduled refresh — refreshing token now');\n this.refreshToken().catch(error => {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('Triggered refresh failed:', errorMessage);\n });\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 logger.info('Page restored from bfcache — forcing bootstrap');\n this.refreshToken().catch(error => {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('bfcache-triggered refresh failed:', errorMessage);\n });\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 logger.info('Network restored — retrying session initialization');\n this.refreshToken().catch(error => {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('Online-triggered refresh failed:', errorMessage);\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 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.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 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;","import { useState, useEffect, useCallback, useRef } 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 // 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(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,\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}"],"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","DEFAULT_CONFIG","bootstrapUrl","refreshInterval","tokenExpiry","maxRetries","bootstrapTimeout","tokenCookieName","invalidSessionError","SessionManager","instance","config","state","isInitialized","isLoading","lastRefreshTime","errorCode","nextRefreshTime","cfSessionId","initializationFailed","refreshTimerId","listeners","Set","initializationPromise","_configured","_boundHandleVisibilityChange","_handleVisibilityChange","bind","_boundHandlePageShow","_handlePageShow","_boundHandleOnline","_handleOnline","configure","trim","undefined","isFinite","Number","isInteger","credentials","logLevel","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","String","value","expires","toUTCString","isSecure","secureFlag","encodedValue","encodeURIComponent","interval","stopTokenRefresh","async","refreshToken","errorMessage","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"],"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.6",
3
+ "version": "1.0.8",
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",
@@ -43,7 +43,8 @@
43
43
  },
44
44
  "peerDependencies": {
45
45
  "react": ">=16.8.0",
46
- "react-dom": ">=16.8.0"
46
+ "react-dom": ">=16.8.0",
47
+ "axios": ">=0.21.0"
47
48
  },
48
49
  "peerDependenciesMeta": {
49
50
  "axios": {
@@ -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
  */