@mapnests/gateway-web-sdk 1.0.0
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/LICENSE +21 -0
- package/README.md +389 -0
- package/dist/index.cjs.js +2 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/package.json +62 -0
- package/src/SessionManager.d.ts +42 -0
- package/src/errors.d.ts +21 -0
- package/src/hooks/useSession.d.ts +13 -0
- package/src/index.d.ts +9 -0
- package/src/interceptors.d.ts +3 -0
- package/src/logger.d.ts +19 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# Gateway Session SDK
|
|
2
|
+
|
|
3
|
+
A lightweight, production-ready session token management SDK for React and Next.js applications. Handles automatic token refresh with configurable intervals to prevent race conditions and token expiration issues.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔄 **Automatic Token Refresh**: Configurable background refresh (default: 25 minutes)
|
|
8
|
+
- 🔒 **HttpOnly Cookie Support**: Secure token storage via server-set cookies
|
|
9
|
+
- ⚛️ **React Integration**: Simple React hook for seamless integration
|
|
10
|
+
- 🎯 **Singleton Pattern**: Single session instance across your entire app
|
|
11
|
+
- 📦 **Zero Dependencies**: Lightweight with only React as a peer dependency
|
|
12
|
+
- 🚀 **Next.js Compatible**: Works with both React and Next.js applications (SSR-safe)
|
|
13
|
+
- ⏱️ **Configurable Intervals**: Customize refresh and expiry times
|
|
14
|
+
- 🔔 **State Subscriptions**: React to session state changes
|
|
15
|
+
- 📘 **TypeScript Support**: Full TypeScript definitions included
|
|
16
|
+
|
|
17
|
+
## Security Notice
|
|
18
|
+
|
|
19
|
+
⚠️ **Important**: This SDK prioritizes **server-set HttpOnly cookies** for maximum security. The SDK includes a fallback mechanism to set cookies client-side, but this is **less secure** as these cookies cannot be HttpOnly and are accessible to JavaScript.
|
|
20
|
+
|
|
21
|
+
**Recommended Setup:**
|
|
22
|
+
- Always set cookies from your server using the `Set-Cookie` header with `HttpOnly` flag
|
|
23
|
+
- The SDK will automatically detect and skip client-side cookie setting when server cookies are present
|
|
24
|
+
- Client-side cookies should only be used for development or specific use cases where server-side setting is not possible
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install gateway-web-sdk
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### React Application
|
|
35
|
+
|
|
36
|
+
```jsx
|
|
37
|
+
import React from 'react';
|
|
38
|
+
import { useSession } from 'gateway-web-sdk';
|
|
39
|
+
|
|
40
|
+
function App() {
|
|
41
|
+
const { isInitialized, isLoading, error, timeUntilRefresh, refresh } = useSession();
|
|
42
|
+
|
|
43
|
+
if (isLoading) {
|
|
44
|
+
return <div>Loading session...</div>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (error) {
|
|
48
|
+
return <div>Error: {error}</div>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!isInitialized) {
|
|
52
|
+
return <div>Session not initialized</div>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div>
|
|
57
|
+
<h1>Session Active</h1>
|
|
58
|
+
<p>Next refresh in: {Math.floor(timeUntilRefresh / 1000)} seconds</p>
|
|
59
|
+
<button onClick={refresh}>Refresh Now</button>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default App;
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Configuration
|
|
68
|
+
|
|
69
|
+
Configure the session manager before your app renders:
|
|
70
|
+
|
|
71
|
+
```jsx
|
|
72
|
+
import React from 'react';
|
|
73
|
+
import ReactDOM from 'react-dom/client';
|
|
74
|
+
import { SessionManager } from 'gateway-web-sdk';
|
|
75
|
+
import App from './App';
|
|
76
|
+
|
|
77
|
+
// Configure session manager
|
|
78
|
+
const sessionManager = SessionManager.getInstance();
|
|
79
|
+
sessionManager.configure({
|
|
80
|
+
bootstrapUrl: 'https://your-api.com/session/bootstrap',
|
|
81
|
+
refreshInterval: 25 * 60 * 1000, // 25 minutes
|
|
82
|
+
tokenExpiry: 30 * 60 * 1000, // 30 minutes
|
|
83
|
+
headers: {
|
|
84
|
+
'X-Custom-Header': 'value',
|
|
85
|
+
},
|
|
86
|
+
credentials: true, // Include cookies in requests
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
90
|
+
<React.StrictMode>
|
|
91
|
+
<App />
|
|
92
|
+
</React.StrictMode>
|
|
93
|
+
);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Next.js Application
|
|
97
|
+
|
|
98
|
+
#### Using App Router (`app/layout.js`)
|
|
99
|
+
|
|
100
|
+
```jsx
|
|
101
|
+
'use client';
|
|
102
|
+
|
|
103
|
+
import { useEffect } from 'react';
|
|
104
|
+
import { SessionManager } from 'gateway-web-sdk';
|
|
105
|
+
|
|
106
|
+
export default function RootLayout({ children }) {
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
const sessionManager = SessionManager.getInstance();
|
|
109
|
+
sessionManager.configure({
|
|
110
|
+
bootstrapUrl: '/api/session/bootstrap',
|
|
111
|
+
refreshInterval: 25 * 60 * 1000,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
sessionManager.initialize();
|
|
115
|
+
}, []);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<html lang="en">
|
|
119
|
+
<body>{children}</body>
|
|
120
|
+
</html>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### Using Pages Router (`pages/_app.js`)
|
|
126
|
+
|
|
127
|
+
```jsx
|
|
128
|
+
import { useEffect } from 'react';
|
|
129
|
+
import { SessionManager } from 'gateway-web-sdk';
|
|
130
|
+
|
|
131
|
+
function MyApp({ Component, pageProps }) {
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
const sessionManager = SessionManager.getInstance();
|
|
134
|
+
sessionManager.configure({
|
|
135
|
+
bootstrapUrl: '/api/session/bootstrap',
|
|
136
|
+
refreshInterval: 25 * 60 * 1000,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
sessionManager.initialize();
|
|
140
|
+
}, []);
|
|
141
|
+
|
|
142
|
+
return <Component {...pageProps} />;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export default MyApp;
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## API Reference
|
|
149
|
+
|
|
150
|
+
### `useSession(options)`
|
|
151
|
+
|
|
152
|
+
React hook for session management.
|
|
153
|
+
|
|
154
|
+
#### Parameters
|
|
155
|
+
|
|
156
|
+
| Parameter | Type | Default | Description |
|
|
157
|
+
|-----------|------|---------|-------------|
|
|
158
|
+
| `options.autoInitialize` | boolean | `true` | Automatically initialize session on mount |
|
|
159
|
+
|
|
160
|
+
#### Return Value
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
{
|
|
164
|
+
isInitialized: boolean; // Whether session is initialized
|
|
165
|
+
isLoading: boolean; // Whether bootstrap is in progress
|
|
166
|
+
error: string | null; // Error message if any
|
|
167
|
+
lastRefreshTime: number; // Timestamp of last refresh
|
|
168
|
+
nextRefreshTime: number; // Timestamp of next scheduled refresh
|
|
169
|
+
timeUntilRefresh: number; // Milliseconds until next refresh
|
|
170
|
+
refresh: () => Promise<void>; // Manual refresh function
|
|
171
|
+
initialize: () => Promise<void>; // Manual initialize function
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### `SessionManager`
|
|
176
|
+
|
|
177
|
+
Core session management class (Singleton).
|
|
178
|
+
|
|
179
|
+
#### Methods
|
|
180
|
+
|
|
181
|
+
##### `configure(config)`
|
|
182
|
+
|
|
183
|
+
Configure the session manager.
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
sessionManager.configure({
|
|
187
|
+
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
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
##### `initialize()`
|
|
197
|
+
|
|
198
|
+
Initialize session by calling bootstrap API.
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
await sessionManager.initialize();
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
##### `refreshToken()`
|
|
205
|
+
|
|
206
|
+
Manually refresh the session token.
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
await sessionManager.refreshToken();
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
##### `getSessionStatus()`
|
|
213
|
+
|
|
214
|
+
Get current session status.
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
const status = sessionManager.getSessionStatus();
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
##### `subscribe(listener)`
|
|
221
|
+
|
|
222
|
+
Subscribe to session state changes.
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
const unsubscribe = sessionManager.subscribe((state) => {
|
|
226
|
+
console.log('Session state:', state);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Later: unsubscribe()
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
##### `destroy()`
|
|
233
|
+
|
|
234
|
+
Clean up and reset the session manager.
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
sessionManager.destroy();
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Interceptors (Optional)
|
|
241
|
+
|
|
242
|
+
#### `fetchInterceptor(url, options)`
|
|
243
|
+
|
|
244
|
+
Fetch wrapper with automatic token refresh on 401/403.
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
import { fetchInterceptor } from 'gateway-web-sdk';
|
|
248
|
+
|
|
249
|
+
const response = await fetchInterceptor('/api/data', {
|
|
250
|
+
method: 'GET',
|
|
251
|
+
headers: { 'Content-Type': 'application/json' }
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### `setupAxiosInterceptor(axiosInstance)`
|
|
256
|
+
|
|
257
|
+
Configure Axios instance with automatic token refresh.
|
|
258
|
+
|
|
259
|
+
```javascript
|
|
260
|
+
import axios from 'axios';
|
|
261
|
+
import { setupAxiosInterceptor } from 'gateway-web-sdk';
|
|
262
|
+
|
|
263
|
+
const api = setupAxiosInterceptor(axios.create({
|
|
264
|
+
baseURL: 'https://api.example.com'
|
|
265
|
+
}));
|
|
266
|
+
```
|
|
267
|
+
|
|
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
|
|
307
|
+
|
|
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
|
|
315
|
+
|
|
316
|
+
## Troubleshooting
|
|
317
|
+
|
|
318
|
+
### Cookies not being set
|
|
319
|
+
|
|
320
|
+
- Verify your API returns `Set-Cookie` header with `HttpOnly` flag
|
|
321
|
+
- Check CORS configuration allows credentials
|
|
322
|
+
- Ensure `credentials: true` in configuration
|
|
323
|
+
|
|
324
|
+
### Automatic refresh not working
|
|
325
|
+
|
|
326
|
+
- Check browser console for errors
|
|
327
|
+
- Verify `refreshInterval` is set correctly
|
|
328
|
+
- Ensure timer isn't being cleared prematurely
|
|
329
|
+
|
|
330
|
+
### Multiple initializations
|
|
331
|
+
|
|
332
|
+
- The SDK uses singleton pattern, but ensure you're not calling `initialize()` multiple times
|
|
333
|
+
- Use `autoInitialize: false` in `useSession()` if you want manual control
|
|
334
|
+
|
|
335
|
+
### Next.js SSR errors
|
|
336
|
+
|
|
337
|
+
- The SDK automatically detects SSR environments and prevents initialization
|
|
338
|
+
- Always wrap initialization in `useEffect` or client components (`'use client'`)
|
|
339
|
+
- Do not call `initialize()` during server-side rendering
|
|
340
|
+
|
|
341
|
+
## TypeScript Support
|
|
342
|
+
|
|
343
|
+
Full TypeScript definitions are included:
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
import { SessionManager, useSession } from 'gateway-web-sdk';
|
|
347
|
+
import type { SessionConfig, SessionState, UseSessionOptions } from 'gateway-web-sdk';
|
|
348
|
+
|
|
349
|
+
const config: SessionConfig = {
|
|
350
|
+
bootstrapUrl: '/api/session',
|
|
351
|
+
refreshInterval: 25 * 60 * 1000
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const manager = SessionManager.getInstance();
|
|
355
|
+
manager.configure(config);
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Changelog
|
|
359
|
+
|
|
360
|
+
### [1.0.0] - 2024-01-12
|
|
361
|
+
|
|
362
|
+
#### Added
|
|
363
|
+
- Initial production release
|
|
364
|
+
- TypeScript definitions for full type safety
|
|
365
|
+
- Build process with Rollup (CJS + ESM outputs)
|
|
366
|
+
- SSR detection for Next.js compatibility
|
|
367
|
+
- Configuration validation
|
|
368
|
+
- Secure session ID generation using crypto.randomUUID()
|
|
369
|
+
- URL encoding for cookie values
|
|
370
|
+
|
|
371
|
+
#### Security
|
|
372
|
+
- Added warnings for client-side cookie limitations
|
|
373
|
+
- Improved session ID generation with Web Crypto API
|
|
374
|
+
- Added SSR environment checks to prevent runtime errors
|
|
375
|
+
- URL-encoded cookie values to prevent injection
|
|
376
|
+
|
|
377
|
+
## License
|
|
378
|
+
|
|
379
|
+
MIT
|
|
380
|
+
|
|
381
|
+
## Contributing
|
|
382
|
+
|
|
383
|
+
Contributions are welcome! Please open an issue or submit a pull request.
|
|
384
|
+
|
|
385
|
+
## Support
|
|
386
|
+
|
|
387
|
+
For issues and questions:
|
|
388
|
+
- GitHub Issues: [Report a bug](https://github.com/yourusername/gateway-web-sdk/issues)
|
|
389
|
+
- Documentation: [Full API Reference](#api-reference)
|
|
@@ -0,0 +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}};
|
|
2
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +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":"2FAIO,MAAMA,UAAqBC,MAChC,WAAAC,CAAYC,EAASC,EAAMC,EAAU,CAAA,GACnCC,MAAMH,GACNI,KAAKC,KAAO,eACZD,KAAKH,KAAOA,EACZG,KAAKF,QAAUA,EAGXJ,MAAMQ,mBACRR,MAAMQ,kBAAkBF,KAAMA,KAAKL,aAIrCQ,OAAOC,eAAeJ,KAAMP,EAAaY,UAC3C,EAGK,MAAMC,UAA2Bb,EACtC,WAAAE,CAAYC,EAASE,GACnBC,MAAMH,EAAS,sBAAuBE,GACtCE,KAAKC,KAAO,oBACd,EAGK,MAAMM,UAAuBd,EAClC,WAAAE,CAAYC,EAASE,GACnBC,MAAMH,EAAS,kBAAmBE,GAClCE,KAAKC,KAAO,gBACd,EAGK,MAAMO,UAAqBf,EAChC,WAAAE,CAAYC,EAASE,GACnBC,MAAMH,EAAS,gBAAiBE,GAChCE,KAAKC,KAAO,cACd,EAGK,MAAMQ,UAAiBhB,EAC5B,WAAAE,CAAYC,EAASE,EAAU,IAC7BC,MAAMH,EAAS,YAAaE,GAC5BE,KAAKC,KAAO,UACd,EC1CG,MAACS,EAAa,CACjBC,KAAM,EACNC,MAAO,EACPC,KAAM,EACNC,KAAM,EACNC,MAAO,GAyCG,MAACC,EAAS,IAtCtB,MACE,WAAArB,GACEK,KAAKiB,MAAQP,EAAWG,IAC1B,CAEA,QAAAK,CAASD,GAELjB,KAAKiB,MADc,iBAAVA,EACIP,EAAWO,EAAME,gBAAkBT,EAAWG,KAE9CI,CAEjB,CAEA,KAAAG,IAASC,GACHrB,KAAKiB,OAASP,EAAWE,OAC3BU,QAAQF,MAAM,sBAAuBC,EAEzC,CAEA,IAAAE,IAAQF,GACFrB,KAAKiB,OAASP,EAAWG,MAC3BS,QAAQC,KAAK,sBAAuBF,EAExC,CAEA,IAAAG,IAAQH,GACFrB,KAAKiB,OAASP,EAAWI,MAC3BQ,QAAQE,KAAK,sBAAuBH,EAExC,CAEA,KAAAI,IAASJ,GACHrB,KAAKiB,OAASP,EAAWK,OAC3BO,QAAQG,MAAM,sBAAuBJ,EAEzC,GCxCF,MAAMK,EACJ,WAAA/B,GACE,GAAI+B,EAAeC,SACjB,OAAOD,EAAeC,SAGxB3B,KAAK4B,OAAS,CACZC,aAAc,qBACdC,gBAAiB,KACjBC,YAAa,KACbC,WAAY,EACZC,gBAAiB,SAGnBjC,KAAKkC,MAAQ,CACXC,eAAe,EACfC,WAAW,EACXC,gBAAiB,KACjBjB,MAAO,KACPkB,UAAW,KACXC,gBAAiB,KACjBR,YAAa,KACbS,YAAa,KACbC,sBAAsB,GAGxBzC,KAAK0C,eAAiB,KACtB1C,KAAK2C,UAAY,IAAIC,IACrB5C,KAAK6C,sBAAwB,KAE7BnB,EAAeC,SAAW3B,IAC5B,CAaA,SAAA8C,CAAUlB,EAAS,IACjB,IAAKA,EAAOC,cAA+C,iBAAxBD,EAAOC,eAA8BD,EAAOC,aAAakB,OAC1F,MAAM,IAAIzC,EAAmB,0DAA2D,CAAEuB,aAAcD,EAAOC,eAGjH,QAA+BmB,IAA3BpB,EAAOE,gBAA+B,CACxC,GAAsC,iBAA3BF,EAAOE,kBAAiCmB,SAASrB,EAAOE,iBACjE,MAAM,IAAIxB,EAAmB,0CAA2C,CAAEwB,gBAAiBF,EAAOE,kBAEpG,GAAIF,EAAOE,iBAAmB,EAC5B,MAAM,IAAIxB,EAAmB,mCAAoC,CAAEwB,gBAAiBF,EAAOE,iBAE/F,CAEA,QAA2BkB,IAAvBpB,EAAOG,YAA2B,CACpC,GAAkC,iBAAvBH,EAAOG,cAA6BkB,SAASrB,EAAOG,aAC7D,MAAM,IAAIzB,EAAmB,sCAAuC,CAAEyB,YAAaH,EAAOG,cAE5F,GAAIH,EAAOG,aAAe,EACxB,MAAM,IAAIzB,EAAmB,+BAAgC,CAAEyB,YAAaH,EAAOG,aAEvF,CAEA/B,KAAK4B,OAAS,IACT5B,KAAK4B,UACLA,EACHsB,iBAAoCF,IAAvBpB,EAAOsB,aAA4BtB,EAAOsB,YACvDC,SAAUvB,EAAOuB,UAAY,QAG/BnC,EAAOE,SAASlB,KAAK4B,OAAOuB,UAExBnD,KAAK4B,OAAOE,iBAAmB9B,KAAK4B,OAAOG,aAC3C/B,KAAK4B,OAAOE,iBAAmB9B,KAAK4B,OAAOG,aAC7Cf,EAAOO,KAAK,6EAEhB,CAMA,gBAAM6B,GACJ,GAAsB,oBAAXC,OACT,MAAM,IAAI5C,EAAS,sDAGrB,GAAIT,KAAKkC,MAAMO,qBAEb,MADAzB,EAAOO,KAAK,2DACN,IAAIhB,EAAe,kEAI3B,GAAIP,KAAK6C,sBAEP,OADA7B,EAAOS,MAAM,kDACNzB,KAAK6C,sBAGd7C,KAAKkC,MAAME,WAAY,EACvBpC,KAAKkC,MAAMd,MAAQ,KACnBpB,KAAKsD,kBAELtD,KAAK6C,sBAAwB,WAC3B,IAAIU,EAEJ,IAAK,IAAIC,EAAU,EAAGA,GAAWxD,KAAK4B,OAAOI,WAAYwB,IACvD,IAEE,MAAMC,EAAezD,KAAK0D,oBAC1B1D,KAAK2D,eAAeF,GAEpBzC,EAAOQ,KAAK,kCAAkCgC,KAAWxD,KAAK4B,OAAOI,eAAgBhC,KAAK4B,OAAOC,cAEjG,MAAM+B,QAAiBC,MAAM7D,KAAK4B,OAAOC,aAAc,CACrDiC,OAAQ,MACRZ,YAAalD,KAAK4B,OAAOsB,YAAc,UAAY,cACnDa,QAAS,CACP,eAAgB,mBAChB,gBAAiB/D,KAAKkC,MAAMM,eACzBxC,KAAK4B,OAAOmC,WAInB,IAAKH,EAASI,GACZ,MAAM,IAAIzD,EAAe,qBAAqBqD,EAASK,UAAUL,EAASM,aAAc,CACtFD,OAAQL,EAASK,OACjBC,WAAYN,EAASM,WACrBC,IAAKnE,KAAK4B,OAAOC,eAIrB,IAAIuC,EACJ,IACEA,QAAaR,EAASS,MACxB,CAAE,MAAOC,GACP,MAAM,IAAI/D,EAAe,uCAAwC,CAAEgE,cAAeD,EAAU1E,SAC9F,CACAoB,EAAOQ,KAAK,wBAGZ,MAAMgD,EAAkBZ,EAASG,QAAQU,IAAI,cACvCC,EAAkBF,GAAmBA,EAAgBG,SAAS,SAG9D7C,EAAkBsC,EAAKQ,aACL,IAApBR,EAAKQ,aACL5E,KAAK4B,OAAOE,gBAGVC,EAAcqC,EAAKS,OACP,IAAdT,EAAKS,OACL7E,KAAK4B,OAAOG,YAoBhB,OAlBIqC,EAAKU,QAAUJ,GACjB1D,EAAOS,MAAM,+CACbzB,KAAK+E,UAAU,QAASX,EAAKU,MAAO/C,EAAc,MACzC2C,GACT1D,EAAOS,MAAM,gDAGfzB,KAAKkC,MAAMC,eAAgB,EAC3BnC,KAAKkC,MAAME,WAAY,EACvBpC,KAAKkC,MAAMG,gBAAkB2C,KAAKC,MAClCjF,KAAKkC,MAAMK,gBAAkByC,KAAKC,MAAQnD,EAC1C9B,KAAKkC,MAAMH,YAAcA,EACzB/B,KAAKkC,MAAMd,MAAQ,KAGnBpB,KAAKkF,kBAAkBpD,GAEvB9B,KAAKsD,kBACEc,CACT,CAAE,MAAOhD,GAIP,GAHAmC,EAAYnC,aAAiB1B,MAAQ0B,EAAQ,IAAIZ,EAAa,yBAA0B,CAAEY,UAC1FJ,EAAOI,MAAM,qBAAqBoC,YAAmBD,EAAU3D,SAE3D4D,EAAUxD,KAAK4B,OAAOI,WAAY,CACpC,MAAMmD,EAAQC,KAAKC,IAAI,IAAOD,KAAKE,IAAI,EAAG9B,EAAU,GAAI,KACxDxC,EAAOQ,KAAK,eAAe2D,gBACrB,IAAII,QAAQC,GAAWC,WAAWD,EAASL,GACnD,CACF,CASF,MANAnE,EAAOI,MAAM,iCACbpB,KAAKkC,MAAME,WAAY,EACvBpC,KAAKkC,MAAMd,MAAQmC,GAAW3D,SAAW,gBACzCI,KAAKkC,MAAMI,UAAYiB,GAAW1D,MAAQ,gBAC1CG,KAAKkC,MAAMO,sBAAuB,EAClCzC,KAAKsD,kBACCC,CACP,EAzF4B,GA2F7B,IACE,aAAavD,KAAK6C,qBACpB,CAAC,QACC7C,KAAK6C,sBAAwB,IAC/B,CACF,CAMA,YAAA6C,GACE,OAAsC,OAA/B1F,KAAK6C,qBACd,CAMA,oBAAM8C,GACJ,OAAI3F,KAAK6C,uBACP7B,EAAOS,MAAM,2CACNzB,KAAK6C,uBAEP0C,QAAQC,SACjB,CAMA,cAAA7B,CAAeiC,GACb5F,KAAKkC,MAAMM,YAAcoD,EACK,oBAAnBC,gBACTA,eAAeC,QAAQ,gBAAiBF,EAE5C,CAMA,iBAAAlC,GACE,MAAsB,oBAAXqC,QAA0BA,OAAOC,WACnCD,OAAOC,aAET,GAAGhB,KAAKC,SAASG,KAAKa,SAASC,SAAS,IAAIC,OAAO,EAAG,IAC/D,CAMA,YAAAC,GACE,OAAOpG,KAAKkC,MAAMM,WACpB,CAMA,oBAAA6D,GACE,MAAsB,oBAAXhD,QACyB,UAA7BA,OAAOiD,SAASC,QACzB,CAOA,QAAAC,CAASvG,EAAO,SACd,GAAwB,oBAAbwG,SAA0B,OAAO,KAC5C,MACMC,EADQ,KAAKD,SAASE,SACRC,MAAM,KAAK3G,MAC/B,GAAqB,IAAjByG,EAAMG,OACR,IACE,OAAOC,mBAAmBJ,EAAMK,MAAMH,MAAM,KAAKI,QACnD,CAAE,MAAO5F,GAEP,OADAJ,EAAOI,MAAM,iCAAkCA,aAAiB1B,MAAQ0B,EAAMxB,QAAUqH,OAAO7F,IACxF,IACT,CAEF,OAAO,IACT,CAQA,SAAA2D,CAAU9E,EAAMiH,EAAOC,GACrB,GAAwB,oBAAbV,SAET,YADAzF,EAAOO,KAAK,gDAKd,MAAM6F,EAAgBnH,EAAKoH,QAAQ,kBAAmB,IACtD,IAAKD,EAEH,YADApG,EAAOI,MAAM,gCAIf,MAAMkG,EAAU,IAAItC,KAAKA,KAAKC,MAAiB,IAATkC,GAAeI,cAC/CC,EAA6B,oBAAXnE,QAAuD,WAA7BA,OAAOiD,SAASC,SAC5DkB,EAAaD,EAAW,WAAa,GACrCE,EAAeC,mBAAmBT,GACxCT,SAASE,OAAS,GAAGS,KAAiBM,sBAAiCJ,kBAAwBG,IAC/FzG,EAAOS,MAAM,eAAe2F,iBAA6BD,KAAUK,EAAW,YAAc,6BAC9F,CAMA,iBAAAtC,CAAkB0C,EAAW5H,KAAK4B,OAAOE,iBACvC9B,KAAK6H,mBACL7G,EAAOQ,KAAK,+BAA+BoG,EAAW,eAEtD5H,KAAK0C,eAAiB+C,WAAWqC,UAC/B9G,EAAOQ,KAAK,4BACZ,UACQxB,KAAK+H,cACb,CAAE,MAAO3G,GACP,MAAM4G,EAAe5G,aAAiB1B,MAAQ0B,EAAMxB,QAAUqH,OAAO7F,GAC/DkB,EAAYlB,GAAOvB,MAAQ,gBACjCmB,EAAOI,MAAM,uBAAwB4G,GACrChI,KAAKkC,MAAMd,MAAQ4G,EACnBhI,KAAKkC,MAAMI,UAAYA,EACvBtC,KAAKsD,iBACP,GACCsE,EACL,CAKA,gBAAAC,GACM7H,KAAK0C,iBACPuF,aAAajI,KAAK0C,gBAClB1C,KAAK0C,eAAiB,KACtB1B,EAAOS,MAAM,+BAEjB,CAMA,kBAAMsG,GAGJ,OAFA/G,EAAOQ,KAAK,kCACZxB,KAAKkC,MAAMO,sBAAuB,EAC3BzC,KAAKoD,YACd,CAMA,gBAAA8E,GACE,MAAO,CACL/F,cAAenC,KAAKkC,MAAMC,cAC1BC,UAAWpC,KAAKkC,MAAME,UACtBC,gBAAiBrC,KAAKkC,MAAMG,gBAC5BE,gBAAiBvC,KAAKkC,MAAMK,gBAC5BR,YAAa/B,KAAKkC,MAAMH,YACxBX,MAAOpB,KAAKkC,MAAMd,MAClBkB,UAAWtC,KAAKkC,MAAMI,UACtBG,qBAAsBzC,KAAKkC,MAAMO,qBACjC0F,iBAAkBnI,KAAKkC,MAAMK,gBACzB6C,KAAKgD,IAAI,EAAGpI,KAAKkC,MAAMK,gBAAkByC,KAAKC,OAC9C,KAER,CAOA,SAAAoD,CAAUC,GACR,GAAwB,mBAAbA,EACT,MAAM,IAAIC,UAAU,+BAGtB,OADAvI,KAAK2C,UAAU6F,IAAIF,GACZ,KACLtI,KAAK2C,UAAU8F,OAAOH,GAE1B,CAKA,eAAAhF,GACE,MAAMW,EAASjE,KAAKkI,mBAEpBQ,MAAMC,KAAK3I,KAAK2C,WAAWiG,QAAQN,IACjC,IACEA,EAASrE,EACX,CAAE,MAAO7C,GACP,MAAM4G,EAAe5G,aAAiB1B,MAAQ0B,EAAMxB,QAAUqH,OAAO7F,GACrEJ,EAAOI,MAAM,kBAAmB4G,EAClC,GAEJ,CAKA,OAAAa,GACE7I,KAAK6H,mBACL7H,KAAK2C,UAAUmG,QACf9I,KAAKkC,MAAQ,CACXC,eAAe,EACfC,WAAW,EACXC,gBAAiB,KACjBjB,MAAO,KACPkB,UAAW,KACXC,gBAAiB,KACjBR,YAAa,KACbS,YAAa,KACbC,sBAAsB,GAEM,oBAAnBoD,gBACTA,eAAekD,WAAW,iBAE5B/H,EAAOQ,KAAK,YACd,CAMA,kBAAOwH,GAIL,OAHKtH,EAAeC,WAClBD,EAAeC,SAAW,IAAID,GAEzBA,EAAeC,QACxB,kNCnbKmG,eAAgC3D,EAAK8E,EAAU,IACpD,MAAMC,EAAiBxH,EAAesH,cAStC,GANAC,EAAQlF,QAAU,IACbkF,EAAQlF,QACX,gBAAiBmF,EAAe9C,gBAI9B8C,EAAe7C,uBAAwB,CACzC,MAAMvB,EAAQoE,EAAe1C,SAAS0C,EAAetH,OAAOK,iBACxD6C,IACFmE,EAAQlF,QAAQmF,EAAetH,OAAOK,iBAAmB6C,EAE7D,CAEA,IAAIlB,QAAiBC,MAAMM,EAAK8E,GAGhC,GAAwB,MAApBrF,EAASK,QAAsC,MAApBL,EAASK,OAAgB,CAQtD,GANIiF,EAAexD,qBACXwD,EAAevD,uBAEfuD,EAAenB,eAEvBkB,EAAQlF,QAAQ,iBAAmBmF,EAAe9C,eAC9C8C,EAAe7C,uBAAwB,CACzC,MAAM8C,EAAWD,EAAe1C,SAAS0C,EAAetH,OAAOK,iBAC3DkH,IACFF,EAAQlF,QAAQmF,EAAetH,OAAOK,iBAAmBkH,EAE7D,CACAvF,QAAiBC,MAAMM,EAAK8E,EAC9B,CAEA,OAAOrF,CACT,iDAMO,SAA+BwF,GACpC,MAAMF,EAAiBxH,EAAesH,cAgDtC,OA7CAI,EAAcC,aAAaC,QAAQC,IAChC3H,IAEC,GADAA,EAAOmC,QAAQ,iBAAmBmF,EAAe9C,eAC7C8C,EAAe7C,uBAAwB,CACzC,MAAMvB,EAAQoE,EAAe1C,SAAS0C,EAAetH,OAAOK,iBACxD6C,IACFlD,EAAOmC,QAAQmF,EAAetH,OAAOK,iBAAmB6C,EAE5D,CACA,OAAOlD,GAERR,GAAUmE,QAAQiE,OAAOpI,IAI5BgI,EAAcC,aAAazF,SAAS2F,IACjC3F,GAAaA,EACdkE,MAAO1G,IACL,MAAMqI,EAAkBrI,EAAMQ,OAG9B,IAAgC,MAA3BR,EAAMwC,UAAUK,QAA6C,MAA3B7C,EAAMwC,UAAUK,UAAoBwF,EAAgBC,OAAQ,CAWjG,GAVAD,EAAgBC,QAAS,EAGrBR,EAAexD,qBACXwD,EAAevD,uBAEfuD,EAAenB,eAGvB0B,EAAgB1F,QAAQ,iBAAmBmF,EAAe9C,eACtD8C,EAAe7C,uBAAwB,CACzC,MAAM8C,EAAWD,EAAe1C,SAAS0C,EAAetH,OAAOK,iBAC3DkH,IACFM,EAAgB1F,QAAQmF,EAAetH,OAAOK,iBAAmBkH,EAErE,CACA,OAAOC,EAAcK,EACvB,CAEA,OAAOlE,QAAQiE,OAAOpI,KAInBgI,CACT,qBC3FO,SAAoBH,EAAU,IACjC,MAAMU,eAAEA,GAAiB,GAASV,EAE5BC,EAAiBxH,EAAesH,eAE/BY,EAAcC,GAAmBC,WAAS,IAC7CZ,EAAehB,oBAInB6B,EAAAA,UAAU,IACcb,EAAeb,UAAW2B,IAC1CH,EAAgBG,KAIrB,IAGHD,EAAAA,UAAU,MACFJ,GAAmBC,EAAazH,eAAkByH,EAAaxH,WAAcwH,EAAanH,sBAC1FyG,EAAe9F,aAAa6G,MAAM7I,IAC9BJ,EAAOI,MAAM,8BAA+BA,MAGrD,CAACuI,EAAgBC,EAAazH,cAAeyH,EAAaxH,UAAWwH,EAAanH,uBAGrF,MAAMyH,EAAUC,EAAAA,YAAYrC,UACxB,UACUoB,EAAenB,cACzB,CAAE,MAAO3G,GAEL,MADAJ,EAAOI,MAAM,yBAA0BA,GACjCA,CACV,GACD,IAGGgC,EAAa+G,EAAAA,YAAYrC,UAC3B,UACUoB,EAAe9F,YACzB,CAAE,MAAOhC,GAEL,MADAJ,EAAOI,MAAM,gCAAiCA,GACxCA,CACV,GACD,IAEH,MAAO,CAEHe,cAAeyH,EAAazH,cAC5BC,UAAWwH,EAAaxH,UACxBhB,MAAOwI,EAAaxI,MACpBiB,gBAAiBuH,EAAavH,gBAC9BE,gBAAiBqH,EAAarH,gBAC9B4F,iBAAkByB,EAAazB,iBAC/B1F,qBAAsBmH,EAAanH,qBAGnCyH,UACA9G,aAER"}
|
|
@@ -0,0 +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};
|
|
2
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +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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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mapnests/gateway-web-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Session token management SDK with automatic refresh for React/Next.js applications",
|
|
5
|
+
"main": "dist/index.cjs.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"types": "src/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"src/**/*.d.ts",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./src/index.d.ts",
|
|
18
|
+
"import": "./dist/index.esm.js",
|
|
19
|
+
"require": "./dist/index.cjs.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "rollup -c",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"test:ui": "vitest --ui",
|
|
27
|
+
"test:coverage": "vitest run --coverage",
|
|
28
|
+
"prepublishOnly": "npm run build",
|
|
29
|
+
"dev": "cd example-react-app && npm run dev"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"session",
|
|
33
|
+
"token",
|
|
34
|
+
"authentication",
|
|
35
|
+
"react",
|
|
36
|
+
"nextjs",
|
|
37
|
+
"sdk"
|
|
38
|
+
],
|
|
39
|
+
"author": "Your Name <your.email@example.com>",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=14.0.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"react": ">=16.8.0",
|
|
46
|
+
"react-dom": ">=16.8.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"axios": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@rollup/plugin-commonjs": "^25.0.8",
|
|
55
|
+
"@rollup/plugin-node-resolve": "^15.3.1",
|
|
56
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
57
|
+
"@vitest/ui": "^4.0.16",
|
|
58
|
+
"jsdom": "^27.4.0",
|
|
59
|
+
"rollup": "^4.55.1",
|
|
60
|
+
"vitest": "^4.0.16"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface BootstrapResponse {
|
|
2
|
+
refresh_time?: number;
|
|
3
|
+
expire?: number;
|
|
4
|
+
token?: string;
|
|
5
|
+
[key: string]: any;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface SessionConfig {
|
|
9
|
+
bootstrapUrl: string;
|
|
10
|
+
refreshInterval?: number;
|
|
11
|
+
tokenExpiry?: number;
|
|
12
|
+
maxRetries?: number;
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
credentials?: boolean;
|
|
15
|
+
logLevel?: 'NONE' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SessionState {
|
|
19
|
+
isInitialized: boolean;
|
|
20
|
+
isLoading: boolean;
|
|
21
|
+
lastRefreshTime: number | null;
|
|
22
|
+
nextRefreshTime: number | null;
|
|
23
|
+
tokenExpiry: number | null;
|
|
24
|
+
error: string | null;
|
|
25
|
+
errorCode: string | null;
|
|
26
|
+
timeUntilRefresh: number | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type SessionListener = (state: SessionState) => void;
|
|
30
|
+
|
|
31
|
+
export default class SessionManager {
|
|
32
|
+
static getInstance(): SessionManager;
|
|
33
|
+
configure(config: SessionConfig): void;
|
|
34
|
+
initialize(): Promise<BootstrapResponse>;
|
|
35
|
+
refreshToken(): Promise<BootstrapResponse>;
|
|
36
|
+
getSessionStatus(): SessionState;
|
|
37
|
+
subscribe(listener: SessionListener): () => void;
|
|
38
|
+
isRefreshing(): boolean;
|
|
39
|
+
waitForRefresh(): Promise<BootstrapResponse | void>;
|
|
40
|
+
destroy(): void;
|
|
41
|
+
getSessionId(): string | null;
|
|
42
|
+
}
|
package/src/errors.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export class SessionError extends Error {
|
|
2
|
+
code: string;
|
|
3
|
+
details: Record<string, unknown>;
|
|
4
|
+
constructor(message: string, code: string, details?: Record<string, unknown>);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class ConfigurationError extends SessionError {
|
|
8
|
+
constructor(message: string, details?: Record<string, unknown>);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class BootstrapError extends SessionError {
|
|
12
|
+
constructor(message: string, details?: Record<string, unknown>);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class NetworkError extends SessionError {
|
|
16
|
+
constructor(message: string, details?: Record<string, unknown>);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class SSRError extends SessionError {
|
|
20
|
+
constructor(message: string, details?: Record<string, unknown>);
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SessionState } from '../SessionManager';
|
|
2
|
+
|
|
3
|
+
export interface UseSessionOptions {
|
|
4
|
+
autoInitialize?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface UseSessionReturn extends SessionState {
|
|
8
|
+
refresh: () => Promise<void>;
|
|
9
|
+
initialize: () => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useSession(options?: UseSessionOptions): UseSessionReturn;
|
|
13
|
+
export default useSession;
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as SessionManager } from './SessionManager';
|
|
2
|
+
export type { SessionConfig, SessionState, SessionListener } from './SessionManager';
|
|
3
|
+
export { useSession } from './hooks/useSession';
|
|
4
|
+
export type { UseSessionOptions, UseSessionReturn } from './hooks/useSession';
|
|
5
|
+
export { fetchInterceptor, setupAxiosInterceptor } from './interceptors';
|
|
6
|
+
export { SessionError, ConfigurationError, BootstrapError, NetworkError, SSRError } from './errors';
|
|
7
|
+
export { logger, LOG_LEVELS } from './logger';
|
|
8
|
+
export type { LogLevel } from './logger';
|
|
9
|
+
export { default } from './SessionManager';
|
package/src/logger.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const LOG_LEVELS: {
|
|
2
|
+
NONE: 0;
|
|
3
|
+
ERROR: 1;
|
|
4
|
+
WARN: 2;
|
|
5
|
+
INFO: 3;
|
|
6
|
+
DEBUG: 4;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type LogLevel = keyof typeof LOG_LEVELS | (typeof LOG_LEVELS)[keyof typeof LOG_LEVELS];
|
|
10
|
+
|
|
11
|
+
export class Logger {
|
|
12
|
+
setLevel(level: LogLevel): void;
|
|
13
|
+
error(...args: unknown[]): void;
|
|
14
|
+
warn(...args: unknown[]): void;
|
|
15
|
+
info(...args: unknown[]): void;
|
|
16
|
+
debug(...args: unknown[]): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const logger: Logger;
|