@mapnests/gateway-web-sdk 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,6 +31,150 @@ npm install gateway-web-sdk
31
31
 
32
32
  ## Quick Start
33
33
 
34
+ ### Simple Implementation (Copy-Paste)
35
+
36
+ This setup covers bootstrap on app start and three API patterns:
37
+ - Users → Fetch interceptor
38
+ - Products → Axios interceptor
39
+ - Orders → Manual implementation
40
+
41
+ 1) Configure and initialize once on app start
42
+ ```jsx
43
+ import React from 'react';
44
+ import ReactDOM from 'react-dom/client';
45
+ import { SessionManager } from 'gateway-web-sdk';
46
+ import { API_BASE_URL, BOOTSTRAP_PATH, TOKEN_COOKIE_NAME, REFRESH_INTERVAL, TOKEN_EXPIRY, CREDENTIALS } from './config.js';
47
+ import App from './App';
48
+
49
+ const sessionManager = SessionManager.getInstance();
50
+ try {
51
+ sessionManager.configure({
52
+ bootstrapUrl: `${API_BASE_URL}${BOOTSTRAP_PATH}`,
53
+ tokenCookieName: TOKEN_COOKIE_NAME,
54
+ refreshInterval: (REFRESH_INTERVAL ? parseInt(REFRESH_INTERVAL) : 25) * 60 * 1000,
55
+ tokenExpiry: (TOKEN_EXPIRY ? parseInt(TOKEN_EXPIRY) : 30) * 60 * 1000,
56
+ credentials: CREDENTIALS === 'true',
57
+ });
58
+ } catch (error) {
59
+ console.error('Failed to configure session manager:', error);
60
+ }
61
+
62
+ sessionManager.initialize().catch(err => console.error('Failed to initialize session:', err));
63
+
64
+ ReactDOM.createRoot(document.getElementById('root')).render(
65
+ <React.StrictMode>
66
+ <App />
67
+ </React.StrictMode>
68
+ );
69
+ ```
70
+
71
+ 2) API layer with three patterns
72
+ ```js
73
+ // src/api/index.js
74
+ import axios from 'axios';
75
+ import { fetchInterceptor, setupAxiosInterceptor, SessionManager } from 'gateway-web-sdk';
76
+
77
+ const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'https://your-gateway.example.com';
78
+
79
+ // Users via Fetch interceptor
80
+ export const getUser = () => fetchInterceptor(`${API_BASE_URL}/api/user`);
81
+
82
+ // Products via Axios interceptor
83
+ export const axiosInstance = setupAxiosInterceptor(
84
+ axios.create({ baseURL: API_BASE_URL, withCredentials: true })
85
+ );
86
+ export const getProducts = () => axiosInstance.get('/api/products');
87
+
88
+ // Orders via Manual implementation
89
+ const sm = SessionManager.getInstance();
90
+ async function manual(url, init = {}) {
91
+ const opts = { ...init, headers: { ...(init.headers || {}) }, credentials: 'include' };
92
+ opts.headers['cf-session-id'] = sm.getSessionId();
93
+ if (sm.shouldUseTokenHeader()) {
94
+ const t = sm.getToken(sm.config.tokenCookieName);
95
+ if (t) opts.headers[sm.config.tokenCookieName] = t;
96
+ }
97
+ let res = await fetch(url, opts);
98
+ if (res.status === 401) {
99
+ const cloned = res.clone();
100
+ try {
101
+ const data = await cloned.json();
102
+ if (data.error_msg === 'INVALID_SESSION') {
103
+ if (sm.isRefreshing()) {
104
+ await sm.waitForRefresh();
105
+ } else {
106
+ const existingToken = sm.getToken(sm.config.tokenCookieName);
107
+ if (!existingToken) {
108
+ await sm.refreshToken();
109
+ }
110
+ }
111
+ opts.headers['cf-session-id'] = sm.getSessionId();
112
+ if (sm.shouldUseTokenHeader()) {
113
+ const nt = sm.getToken(sm.config.tokenCookieName);
114
+ if (nt) opts.headers[sm.config.tokenCookieName] = nt;
115
+ }
116
+ res = await fetch(url, opts);
117
+ }
118
+ } catch {
119
+ // Not JSON or parsing failed
120
+ }
121
+ }
122
+ return res;
123
+ }
124
+ export const getOrders = () => manual(`${API_BASE_URL}/api/orders`);
125
+ ```
126
+
127
+ 3) Usage in a component
128
+ ```jsx
129
+ import { useEffect, useState } from 'react';
130
+ import { useSession } from 'gateway-web-sdk';
131
+ import { getUser, getProducts, getOrders } from './api/index.js';
132
+
133
+ export default function Dashboard() {
134
+ const { isInitialized, isLoading, error } = useSession();
135
+ const [data, setData] = useState(null);
136
+
137
+ useEffect(() => {
138
+ if (!isInitialized) return;
139
+ (async () => {
140
+ try {
141
+ const [u, p, o] = await Promise.all([getUser(), getProducts(), getOrders()]);
142
+ if (!u.ok) throw new Error('User failed');
143
+ if (!o.ok) throw new Error('Orders failed');
144
+ setData({ user: await u.json(), products: p.data, orders: await o.json() });
145
+ } catch (e) {
146
+ console.error(e);
147
+ setData(null);
148
+ }
149
+ })();
150
+ }, [isInitialized]);
151
+
152
+ if (isLoading) return <p>Loading session…</p>;
153
+ if (error) return <p>Session error, limited mode.</p>;
154
+ if (!data) return <p>Loading data…</p>;
155
+
156
+ return (
157
+ <div>
158
+ <h3>User</h3>
159
+ <pre>{JSON.stringify(data.user, null, 2)}</pre>
160
+ <h3>Products</h3>
161
+ <pre>{JSON.stringify(data.products, null, 2)}</pre>
162
+ <h3>Orders</h3>
163
+ <pre>{JSON.stringify(data.orders, null, 2)}</pre>
164
+ </div>
165
+ );
166
+ }
167
+ ```
168
+
169
+ Notes
170
+ - Install axios if you use the Axios pattern: `npm i axios`
171
+ - Ensure your gateway exposes:
172
+ - GET /api/session/bootstrap
173
+ - GET /api/user
174
+ - GET /api/products
175
+ - GET /api/orders
176
+ - The SDK automatically sends cookies; token header fallback is added only when necessary.
177
+
34
178
  ### React Application
35
179
 
36
180
  ```jsx
@@ -185,11 +329,12 @@ Configure the session manager.
185
329
  ```javascript
186
330
  sessionManager.configure({
187
331
  bootstrapUrl: '/session/bootstrap', // Required: Bootstrap API endpoint
188
- refreshInterval: 25 * 60 * 1000, // Optional: Refresh interval (ms)
189
- tokenExpiry: 30 * 60 * 1000, // Optional: Token expiry (ms)
190
- maxRetries: 3, // Optional: Max retry attempts
191
- headers: {}, // Optional: Additional headers
192
- credentials: true, // Optional: Include credentials
332
+ tokenCookieName: 'stoken', // Optional: Custom token cookie name
333
+ refreshInterval: 25 * 60 * 1000, // Optional: Refresh interval (ms)
334
+ tokenExpiry: 30 * 60 * 1000, // Optional: Token expiry (ms)
335
+ maxRetries: 3, // Optional: Max retry attempts
336
+ headers: {}, // Optional: Additional headers
337
+ credentials: true, // Optional: Include credentials
193
338
  });
194
339
  ```
195
340
 
@@ -265,53 +410,14 @@ const api = setupAxiosInterceptor(axios.create({
265
410
  }));
266
411
  ```
267
412
 
268
- ## Server-Side Setup
269
-
270
- Your `/session/bootstrap` endpoint must:
271
-
272
- 1. Generate or retrieve the session token
273
- 2. Set it as an httpOnly cookie via `Set-Cookie` header
274
- 3. Return a JSON response (optional, can be empty object)
275
-
276
- ### Example Express.js Endpoint
277
-
278
- ```javascript
279
- app.get('/session/bootstrap', async (req, res) => {
280
- const token = generateSessionToken(); // Your token generation logic
281
-
282
- res.cookie('session_token', token, {
283
- httpOnly: true,
284
- secure: true, // HTTPS only
285
- sameSite: 'strict',
286
- maxAge: 30 * 60 * 1000, // 30 minutes
287
- });
288
-
289
- res.json({
290
- success: true,
291
- expiresIn: 30 * 60 * 1000,
292
- });
293
- });
294
- ```
295
-
296
- ### Response Format (Optional)
297
-
298
- ```json
299
- {
300
- "refresh_time": 1500,
301
- "expire": 1800,
302
- "token": "optional-token-if-not-using-httponly"
303
- }
304
- ```
305
-
306
- ## Best Practices
413
+ ## Client-Side Best Practices
307
414
 
308
- 1. **Server-Side Cookies**: Always set cookies from your server with `HttpOnly` flag for security
309
- 2. **Token Timing**: Set `refreshInterval` to 5 minutes before `tokenExpiry` to prevent race conditions
310
- 3. **Error Handling**: Always handle errors in production and provide user feedback
311
- 4. **HTTPS Only**: Use `secure: true` cookies in production
312
- 5. **CORS**: Configure CORS correctly to allow credentials
313
- 6. **Single Instance**: Only call `configure()` once at app startup
314
- 7. **Configuration Validation**: The SDK validates required fields - ensure `bootstrapUrl` is always provided
415
+ 1. Single Instance: Call configure() once at app startup and reuse the singleton.
416
+ 2. Token Timing: Set refreshInterval about 5 minutes before tokenExpiry to avoid race conditions.
417
+ 3. Error Handling: Handle errors and provide user feedback for limited mode.
418
+ 4. HTTPS: Use secure, production-grade origins (https) in production environments.
419
+ 5. CORS/Credentials: If cross-origin, ensure credentials are enabled in client config and server CORS.
420
+ 6. Initialization: Initialize after configure() and before issuing business API calls.
315
421
 
316
422
  ## Troubleshooting
317
423
 
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.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(){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;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,...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("token"),l=r.refresh_time?1e3*r.refresh_time:this.config.refreshInterval,h=r.expire?1e3*r.expire:this.config.tokenExpiry;return r.token&&!n?(a.debug("Server did not set cookie, setting from SDK"),this.setCookie("token",r.token,h/1e3)):n&&a.debug("Server set cookie, skipping SDK cookie logic"),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.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){this.state.cfSessionId=e,"undefined"!=typeof sessionStorage&&sessionStorage.setItem("cf-session-id",e)}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}getToken(e="token"){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}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)}stopTokenRefresh(){this.refreshTimerId&&(clearTimeout(this.refreshTimerId),this.refreshTimerId=null,a.debug("Token refresh timer stopped"))}async refreshToken(){return a.info("Manual token refresh triggered"),this.state.initializationFailed=!1,this.initialize()}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},"undefined"!=typeof sessionStorage&&sessionStorage.removeItem("cf-session-id"),a.info("Destroyed")}static getInstance(){return l.instance||(l.instance=new l),l.instance}}exports.BootstrapError=s,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.headers={...t.headers,"cf-session-id":i.getSessionId()},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||403===s.status){if(i.isRefreshing()?await i.waitForRefresh():await i.refreshToken(),t.headers["cf-session-id"]=i.getSessionId(),i.shouldUseTokenHeader()){const e=i.getToken(i.config.tokenCookieName);e&&(t.headers[i.config.tokenCookieName]=e)}s=await fetch(e,t)}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(),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||403===i.response?.status)&&!s._retry){if(s._retry=!0,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)}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 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.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.lastRefreshTime=Date.now(),this.state.nextRefreshTime=Date.now()+this.config.refreshInterval,this.state.tokenExpiry=this.config.tokenExpiry;const e=this.getCookie("cf-session-id");return e?this.state.cfSessionId=e:this.setCfSessionId(this.generateSessionId()),this.startTokenRefresh(this.config.refreshInterval),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,...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?1e3*r.expire: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.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",h/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){this.state.cfSessionId=e;const t=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}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)}stopTokenRefresh(){this.refreshTimerId&&(clearTimeout(this.refreshTimerId),this.refreshTimerId=null,a.debug("Token refresh timer stopped"))}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),a.info("Destroyed")}static getInstance(){return l.instance||(l.instance=new l),l.instance}}exports.BootstrapError=s,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()},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(),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(),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(),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}};
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';\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 = {\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.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 * @returns {Promise<Object>} Bootstrap response\n */\n async initialize() {\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 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 ...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('token');\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: refresh_time + window_time (both in seconds)\n const tokenExpiry = data.expire\n ? data.expire * 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('token', data.token, tokenExpiry / 1000);\n } else if (serverSetCookie) {\n logger.debug('Server set cookie, skipping SDK cookie logic');\n }\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 * Set cf-session-id\n * @param {string} sessionId - Session ID\n */\n setCfSessionId(sessionId) {\n this.state.cfSessionId = sessionId;\n if (typeof sessionStorage !== 'undefined') {\n sessionStorage.setItem('cf-session-id', sessionId);\n }\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 token from cookie\n * @param {string} name - Cookie name (default: 'token')\n * @returns {string|null} Token value or null\n */\n getToken(name = 'token') {\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 * 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\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 }\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();\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 if (typeof sessionStorage !== 'undefined') {\n sessionStorage.removeItem('cf-session-id');\n }\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';\nimport { logger } from './logger.js';\n\n/**\n * Fetch interceptor with automatic token refresh on 401/403\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 // Add headers\n options.headers = {\n ...options.headers,\n 'cf-session-id': sessionManager.getSessionId(),\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\n if (response.status === 401 || response.status === 403) {\n // Check if refresh already in progress, wait for it\n if (sessionManager.isRefreshing()) {\n await sessionManager.waitForRefresh();\n } else {\n await sessionManager.refreshToken();\n }\n options.headers['cf-session-id'] = sessionManager.getSessionId();\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 \n return response;\n}\n\n/**\n * Axios interceptor with automatic token refresh on 401/403\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 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 and not already retried\n if ((error.response?.status === 401 || error.response?.status === 403) && !originalRequest._retry) {\n originalRequest._retry = true;\n \n // Check if refresh already in progress, wait for it\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 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 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","listeners","Set","initializationPromise","configure","trim","undefined","isFinite","credentials","logLevel","initialize","window","notifyListeners","lastError","attempt","newSessionId","generateSessionId","setCfSessionId","response","fetch","method","headers","ok","status","statusText","url","data","json","jsonError","originalError","setCookieHeader","get","serverSetCookie","includes","refresh_time","expire","token","setCookie","Date","now","startTokenRefresh","delay","Math","min","pow","Promise","resolve","setTimeout","isRefreshing","waitForRefresh","sessionId","sessionStorage","setItem","crypto","randomUUID","random","toString","substr","getSessionId","shouldUseTokenHeader","location","protocol","getToken","document","parts","cookie","split","length","decodeURIComponent","pop","shift","String","value","maxAge","sanitizedName","replace","expires","toUTCString","isSecure","secureFlag","encodedValue","encodeURIComponent","interval","stopTokenRefresh","async","refreshToken","errorMessage","clearTimeout","getSessionStatus","timeUntilRefresh","max","subscribe","listener","TypeError","add","delete","Array","from","forEach","destroy","clear","removeItem","getInstance","options","sessionManager","newToken","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';\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 = {\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.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.lastRefreshTime = Date.now();\n this.state.nextRefreshTime = Date.now() + this.config.refreshInterval;\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 this.startTokenRefresh(this.config.refreshInterval);\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 ...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: refresh_time + window_time (both in seconds)\n const tokenExpiry = data.expire\n ? data.expire * 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 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 token expiry time)\n this.setCookie('session_initialized', 'true', tokenExpiry / 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) {\n this.state.cfSessionId = sessionId;\n const maxAge = this.config.tokenExpiry / 1000;\n this.setCookie('cf-session-id', sessionId, maxAge);\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\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 }\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 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';\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 };\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 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 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 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","listeners","Set","initializationPromise","configure","trim","undefined","isFinite","credentials","logLevel","initialize","skipCookieCheck","window","getCookie","Date","now","storedSessionId","setCfSessionId","generateSessionId","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","setCookie","delay","Math","min","pow","Promise","resolve","setTimeout","isRefreshing","waitForRefresh","sessionId","maxAge","crypto","randomUUID","random","toString","substr","getSessionId","shouldUseTokenHeader","location","protocol","document","parts","cookie","split","length","decodeURIComponent","pop","shift","String","value","sanitizedName","replace","expires","toUTCString","isSecure","secureFlag","encodedValue","encodeURIComponent","interval","stopTokenRefresh","async","refreshToken","errorMessage","clearTimeout","getSessionStatus","timeUntilRefresh","max","subscribe","listener","TypeError","add","delete","Array","from","forEach","destroy","clear","getInstance","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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"}
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 l={NONE:0,ERROR:1,WARN:2,INFO:3,DEBUG:4};const h=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)}};class f{constructor(){if(f.instance)return f.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.listeners=new Set,this.initializationPromise=null,f.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"},h.setLevel(this.config.logLevel),this.config.refreshInterval&&this.config.tokenExpiry&&this.config.refreshInterval>=this.config.tokenExpiry&&h.warn("refreshInterval should be less than tokenExpiry to prevent race conditions")}async initialize(){if("undefined"==typeof window)throw new a("Cannot initialize in non-browser environment (SSR)");if(this.state.initializationFailed)throw h.warn("Initialization previously failed. Reload page to retry."),new n("Initialization failed after max retries. Reload page to retry.");if(this.initializationPromise)return h.debug("Initialization already in progress, waiting..."),this.initializationPromise;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),h.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,...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})}h.info("Bootstrap successful");const r=i.headers.get("set-cookie"),o=r&&r.includes("token"),a=s.refresh_time?1e3*s.refresh_time:this.config.refreshInterval,l=s.expire?1e3*s.expire:this.config.tokenExpiry;return s.token&&!o?(h.debug("Server did not set cookie, setting from SDK"),this.setCookie("token",s.token,l/1e3)):o&&h.debug("Server set cookie, skipping SDK cookie logic"),this.state.isInitialized=!0,this.state.isLoading=!1,this.state.lastRefreshTime=Date.now(),this.state.nextRefreshTime=Date.now()+a,this.state.tokenExpiry=l,this.state.error=null,this.startTokenRefresh(a),this.notifyListeners(),s}catch(i){if(e=i instanceof Error?i:new o("Unknown error occurred",{error:i}),h.error(`Bootstrap attempt ${t} failed:`,e.message),t<this.config.maxRetries){const e=Math.min(1e3*Math.pow(2,t-1),5e3);h.info(`Retrying in ${e}ms...`),await new Promise(t=>setTimeout(t,e))}}throw h.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?(h.debug("Waiting for ongoing refresh to complete"),this.initializationPromise):Promise.resolve()}setCfSessionId(e){this.state.cfSessionId=e,"undefined"!=typeof sessionStorage&&sessionStorage.setItem("cf-session-id",e)}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}getToken(e="token"){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 h.error("Failed to decode cookie value:",e instanceof Error?e.message:String(e)),null}return null}setCookie(e,t,i){if("undefined"==typeof document)return void h.warn("Cannot set cookie in non-browser environment");const s=e.replace(/[^a-zA-Z0-9_-]/g,"");if(!s)return void h.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}`,h.debug(`Cookie set: ${s}, expires in ${i}s${n?" (Secure)":""} [WARNING: Not HttpOnly]`)}startTokenRefresh(e=this.config.refreshInterval){this.stopTokenRefresh(),h.info(`Scheduling token refresh in ${e/1e3} seconds`),this.refreshTimerId=setTimeout(async()=>{h.info("Auto-refreshing token...");try{await this.refreshToken()}catch(e){const t=e instanceof Error?e.message:String(e),i=e?.code||"UNKNOWN_ERROR";h.error("Auto-refresh failed:",t),this.state.error=t,this.state.errorCode=i,this.notifyListeners()}},e)}stopTokenRefresh(){this.refreshTimerId&&(clearTimeout(this.refreshTimerId),this.refreshTimerId=null,h.debug("Token refresh timer stopped"))}async refreshToken(){return h.info("Manual token refresh triggered"),this.state.initializationFailed=!1,this.initialize()}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);h.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},"undefined"!=typeof sessionStorage&&sessionStorage.removeItem("cf-session-id"),h.info("Destroyed")}static getInstance(){return f.instance||(f.instance=new f),f.instance}}function c(s={}){const{autoInitialize:r=!0}=s,n=f.getInstance(),[o,a]=e(()=>n.getSessionStatus());t(()=>n.subscribe(e=>{a(e)}),[]),t(()=>{!r||o.isInitialized||o.isLoading||o.initializationFailed||n.initialize().catch(e=>{h.error("Auto-initialization failed:",e)})},[r,o.isInitialized,o.isLoading,o.initializationFailed]);const l=i(async()=>{try{await n.refreshToken()}catch(e){throw h.error("Manual refresh failed:",e),e}},[]),c=i(async()=>{try{await n.initialize()}catch(e){throw h.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:l,initialize:c}}async function d(e,t={}){const i=f.getInstance();if(t.headers={...t.headers,"cf-session-id":i.getSessionId()},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||403===s.status){if(i.isRefreshing()?await i.waitForRefresh():await i.refreshToken(),t.headers["cf-session-id"]=i.getSessionId(),i.shouldUseTokenHeader()){const e=i.getToken(i.config.tokenCookieName);e&&(t.headers[i.config.tokenCookieName]=e)}s=await fetch(e,t)}return s}function u(e){const t=f.getInstance();return e.interceptors.request.use(e=>{if(e.headers["cf-session-id"]=t.getSessionId(),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||403===i.response?.status)&&!s._retry){if(s._retry=!0,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)}return e(s)}return Promise.reject(i)}),e}export{n as BootstrapError,r as ConfigurationError,l as LOG_LEVELS,o as NetworkError,a as SSRError,s as SessionError,f as SessionManager,f as default,d as fetchInterceptor,h as logger,u as setupAxiosInterceptor,c as useSession};
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 l={NONE:0,ERROR:1,WARN:2,INFO:3,DEBUG:4};const h=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)}};class f{constructor(){if(f.instance)return f.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.listeners=new Set,this.initializationPromise=null,f.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"},h.setLevel(this.config.logLevel),this.config.refreshInterval&&this.config.tokenExpiry&&this.config.refreshInterval>=this.config.tokenExpiry&&h.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 h.warn("Initialization previously failed. Reload page to retry."),new n("Initialization failed after max retries. Reload page to retry.");if(this.initializationPromise)return h.debug("Initialization already in progress, waiting..."),this.initializationPromise;if(!e){if("true"===this.getCookie("session_initialized")){h.info("Session already initialized, skipping bootstrap API call"),this.state.isInitialized=!0,this.state.lastRefreshTime=Date.now(),this.state.nextRefreshTime=Date.now()+this.config.refreshInterval,this.state.tokenExpiry=this.config.tokenExpiry;const e=this.getCookie("cf-session-id");return e?this.state.cfSessionId=e:this.setCfSessionId(this.generateSessionId()),this.startTokenRefresh(this.config.refreshInterval),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),h.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,...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})}h.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,l=s.expire?1e3*s.expire:this.config.tokenExpiry;return s.token&&!o?(h.debug("Server did not set cookie, setting from SDK"),this.setCookie(this.config.tokenCookieName,s.token,l/1e3)):o&&h.debug("Server set cookie, skipping SDK cookie logic"),this.state.isInitialized=!0,this.state.isLoading=!1,this.state.lastRefreshTime=Date.now(),this.state.nextRefreshTime=Date.now()+a,this.state.tokenExpiry=l,this.state.error=null,this.setCookie("session_initialized","true",l/1e3),this.startTokenRefresh(a),this.notifyListeners(),s}catch(i){if(e=i instanceof Error?i:new o("Unknown error occurred",{error:i}),h.error(`Bootstrap attempt ${t} failed:`,e.message),t<this.config.maxRetries){const e=Math.min(1e3*Math.pow(2,t-1),5e3);h.info(`Retrying in ${e}ms...`),await new Promise(t=>setTimeout(t,e))}}throw h.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?(h.debug("Waiting for ongoing refresh to complete"),this.initializationPromise):Promise.resolve()}setCfSessionId(e){this.state.cfSessionId=e;const t=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}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 h.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 h.warn("Cannot set cookie in non-browser environment");const s=e.replace(/[^a-zA-Z0-9_-]/g,"");if(!s)return void h.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}`,h.debug(`Cookie set: ${s}, expires in ${i}s${n?" (Secure)":""} [WARNING: Not HttpOnly]`)}startTokenRefresh(e=this.config.refreshInterval){this.stopTokenRefresh(),h.info(`Scheduling token refresh in ${e/1e3} seconds`),this.refreshTimerId=setTimeout(async()=>{h.info("Auto-refreshing token...");try{await this.refreshToken()}catch(e){const t=e instanceof Error?e.message:String(e),i=e?.code||"UNKNOWN_ERROR";h.error("Auto-refresh failed:",t),this.state.error=t,this.state.errorCode=i,this.notifyListeners()}},e)}stopTokenRefresh(){this.refreshTimerId&&(clearTimeout(this.refreshTimerId),this.refreshTimerId=null,h.debug("Token refresh timer stopped"))}async refreshToken(){return h.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);h.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),h.info("Destroyed")}static getInstance(){return f.instance||(f.instance=new f),f.instance}}function c(s={}){const{autoInitialize:r=!0}=s,n=f.getInstance(),[o,a]=e(()=>n.getSessionStatus());t(()=>n.subscribe(e=>{a(e)}),[]),t(()=>{!r||o.isInitialized||o.isLoading||o.initializationFailed||n.initialize().catch(e=>{h.error("Auto-initialization failed:",e)})},[r,o.isInitialized,o.isLoading,o.initializationFailed]);const l=i(async()=>{try{await n.refreshToken()}catch(e){throw h.error("Manual refresh failed:",e),e}},[]),c=i(async()=>{try{await n.initialize()}catch(e){throw h.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:l,initialize:c}}async function d(e,t={}){const i=f.getInstance();if(t.credentials=t.credentials||"include",t.headers={...t.headers,"cf-session-id":i.getSessionId()},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(),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 u(e){const t=f.getInstance();return e.interceptors.request.use(e=>{if(e.headers["cf-session-id"]=t.getSessionId(),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(),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,r as ConfigurationError,l as LOG_LEVELS,o as NetworkError,a as SSRError,s as SessionError,f as SessionManager,f as default,d as fetchInterceptor,h as logger,u as setupAxiosInterceptor,c 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';\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 = {\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.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 * @returns {Promise<Object>} Bootstrap response\n */\n async initialize() {\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 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 ...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('token');\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: refresh_time + window_time (both in seconds)\n const tokenExpiry = data.expire\n ? data.expire * 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('token', data.token, tokenExpiry / 1000);\n } else if (serverSetCookie) {\n logger.debug('Server set cookie, skipping SDK cookie logic');\n }\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 * Set cf-session-id\n * @param {string} sessionId - Session ID\n */\n setCfSessionId(sessionId) {\n this.state.cfSessionId = sessionId;\n if (typeof sessionStorage !== 'undefined') {\n sessionStorage.setItem('cf-session-id', sessionId);\n }\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 token from cookie\n * @param {string} name - Cookie name (default: 'token')\n * @returns {string|null} Token value or null\n */\n getToken(name = 'token') {\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 * 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\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 }\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();\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 if (typeof sessionStorage !== 'undefined') {\n sessionStorage.removeItem('cf-session-id');\n }\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';\nimport { logger } from './logger.js';\n\n/**\n * Fetch interceptor with automatic token refresh on 401/403\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 // Add headers\n options.headers = {\n ...options.headers,\n 'cf-session-id': sessionManager.getSessionId(),\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\n if (response.status === 401 || response.status === 403) {\n // Check if refresh already in progress, wait for it\n if (sessionManager.isRefreshing()) {\n await sessionManager.waitForRefresh();\n } else {\n await sessionManager.refreshToken();\n }\n options.headers['cf-session-id'] = sessionManager.getSessionId();\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 \n return response;\n}\n\n/**\n * Axios interceptor with automatic token refresh on 401/403\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 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 and not already retried\n if ((error.response?.status === 401 || error.response?.status === 403) && !originalRequest._retry) {\n originalRequest._retry = true;\n \n // Check if refresh already in progress, wait for it\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 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 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","listeners","Set","initializationPromise","configure","trim","undefined","isFinite","credentials","logLevel","initialize","window","notifyListeners","lastError","attempt","newSessionId","generateSessionId","setCfSessionId","response","fetch","method","headers","ok","status","statusText","url","data","json","jsonError","originalError","setCookieHeader","get","serverSetCookie","includes","refresh_time","expire","token","setCookie","Date","now","startTokenRefresh","delay","Math","min","pow","Promise","resolve","setTimeout","isRefreshing","waitForRefresh","sessionId","sessionStorage","setItem","crypto","randomUUID","random","toString","substr","getSessionId","shouldUseTokenHeader","location","protocol","getToken","document","parts","cookie","split","length","decodeURIComponent","pop","shift","String","value","maxAge","sanitizedName","replace","expires","toUTCString","isSecure","secureFlag","encodedValue","encodeURIComponent","interval","stopTokenRefresh","async","refreshToken","errorMessage","clearTimeout","getSessionStatus","timeUntilRefresh","max","subscribe","listener","TypeError","add","delete","Array","from","forEach","destroy","clear","removeItem","getInstance","useSession","options","autoInitialize","sessionManager","sessionState","setSessionState","useState","useEffect","newState","catch","refresh","useCallback","fetchInterceptor","newToken","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';\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 = {\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.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.lastRefreshTime = Date.now();\n this.state.nextRefreshTime = Date.now() + this.config.refreshInterval;\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 this.startTokenRefresh(this.config.refreshInterval);\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 ...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: refresh_time + window_time (both in seconds)\n const tokenExpiry = data.expire\n ? data.expire * 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 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 token expiry time)\n this.setCookie('session_initialized', 'true', tokenExpiry / 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) {\n this.state.cfSessionId = sessionId;\n const maxAge = this.config.tokenExpiry / 1000;\n this.setCookie('cf-session-id', sessionId, maxAge);\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\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 }\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 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';\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 };\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 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 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 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","listeners","Set","initializationPromise","configure","trim","undefined","isFinite","credentials","logLevel","initialize","skipCookieCheck","window","getCookie","Date","now","storedSessionId","setCfSessionId","generateSessionId","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","setCookie","delay","Math","min","pow","Promise","resolve","setTimeout","isRefreshing","waitForRefresh","sessionId","maxAge","crypto","randomUUID","random","toString","substr","getSessionId","shouldUseTokenHeader","location","protocol","document","parts","cookie","split","length","decodeURIComponent","pop","shift","String","value","sanitizedName","replace","expires","toUTCString","isSecure","secureFlag","encodedValue","encodeURIComponent","interval","stopTokenRefresh","async","refreshToken","errorMessage","clearTimeout","getSessionStatus","timeUntilRefresh","max","subscribe","listener","TypeError","add","delete","Array","from","forEach","destroy","clear","getInstance","useSession","options","autoInitialize","sessionManager","sessionState","setSessionState","useState","useEffect","newState","catch","refresh","useCallback","fetchInterceptor","clonedResponse","clone","error_msg","newToken","e","setupAxiosInterceptor","axiosInstance","interceptors","request","use","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.0",
3
+ "version": "1.0.2",
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",
@@ -13,6 +13,7 @@ export interface SessionConfig {
13
13
  headers?: Record<string, string>;
14
14
  credentials?: boolean;
15
15
  logLevel?: 'NONE' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
16
+ tokenCookieName?: string;
16
17
  }
17
18
 
18
19
  export interface SessionState {
@@ -29,14 +30,63 @@ export interface SessionState {
29
30
  export type SessionListener = (state: SessionState) => void;
30
31
 
31
32
  export default class SessionManager {
33
+ /**
34
+ * Get the singleton instance of SessionManager
35
+ * @returns SessionManager instance
36
+ */
32
37
  static getInstance(): SessionManager;
38
+
39
+ /**
40
+ * Configure the session manager
41
+ * @param config - Configuration options
42
+ */
33
43
  configure(config: SessionConfig): void;
44
+
45
+ /**
46
+ * Initialize session by calling bootstrap API
47
+ * @returns Promise resolving to bootstrap response
48
+ */
34
49
  initialize(): Promise<BootstrapResponse>;
50
+
51
+ /**
52
+ * Manually refresh the session token
53
+ * @returns Promise resolving to bootstrap response
54
+ */
35
55
  refreshToken(): Promise<BootstrapResponse>;
56
+
57
+ /**
58
+ * Get current session status
59
+ * @returns Current session state
60
+ */
36
61
  getSessionStatus(): SessionState;
62
+
63
+ /**
64
+ * Subscribe to session state changes
65
+ * @param listener - Callback function called on state changes
66
+ * @returns Unsubscribe function
67
+ */
37
68
  subscribe(listener: SessionListener): () => void;
69
+
70
+ /**
71
+ * Check if token refresh is currently in progress
72
+ * @returns True if refresh is in progress
73
+ */
38
74
  isRefreshing(): boolean;
75
+
76
+ /**
77
+ * Wait for ongoing refresh to complete
78
+ * @returns Promise resolving to bootstrap response or void
79
+ */
39
80
  waitForRefresh(): Promise<BootstrapResponse | void>;
81
+
82
+ /**
83
+ * Clean up and reset the session manager
84
+ */
40
85
  destroy(): void;
86
+
87
+ /**
88
+ * Get current cf-session-id
89
+ * @returns Current session ID or null
90
+ */
41
91
  getSessionId(): string | null;
42
92
  }