Session token management SDK with automatic refresh for React/Next.js applications
npm install @mapnests/gateway-web-sdkA 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.
- π Automatic Token Refresh: Configurable background refresh (default: 25 minutes)
- π HttpOnly Cookie Support: Secure token storage via server-set cookies
- βοΈ React Integration: Simple React hook for seamless integration
- π― Singleton Pattern: Single session instance across your entire app
- π¦ Zero Dependencies: Lightweight with only React as a peer dependency
- π Next.js Compatible: Works with both React and Next.js applications (SSR-safe)
- β±οΈ Configurable Intervals: Customize refresh and expiry times
- π State Subscriptions: React to session state changes
- π TypeScript Support: Full TypeScript definitions included
β οΈ 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.
Recommended Setup:
- Always set cookies from your server using the Set-Cookie header with HttpOnly flag
- The SDK will automatically detect and skip client-side cookie setting when server cookies are present
- Client-side cookies should only be used for development or specific use cases where server-side setting is not possible
``bash`
npm install gateway-web-sdk
This setup covers bootstrap on app start and three API patterns:
- Users β Fetch interceptor
- Products β Axios interceptor
- Orders β Manual implementation
1) Configure and initialize once on app start
`jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { SessionManager } from 'gateway-web-sdk';
import { API_BASE_URL, BOOTSTRAP_PATH, TOKEN_COOKIE_NAME } from './config.js';
import App from './App';
const sessionManager = SessionManager.getInstance();
try {
sessionManager.configure({
bootstrapUrl: ${API_BASE_URL}${BOOTSTRAP_PATH},
tokenCookieName: TOKEN_COOKIE_NAME,
});
} catch (error) {
console.error('Failed to configure session manager:', error);
}
sessionManager.initialize().catch(err => console.error('Failed to initialize session:', err));
ReactDOM.createRoot(document.getElementById('root')).render(
);
`
2) API layer with three patterns
`js
// src/api/index.js
import axios from 'axios';
import { fetchInterceptor, setupAxiosInterceptor, SessionManager } from 'gateway-web-sdk';
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'https://your-gateway.example.com';
// Users via Fetch interceptor
export const getUser = () => fetchInterceptor(${API_BASE_URL}/api/user);
// Products via Axios interceptor
export const axiosInstance = setupAxiosInterceptor(
axios.create({ baseURL: API_BASE_URL, withCredentials: true })
);
export const getProducts = () => axiosInstance.get('/api/products');
// Orders via Manual implementation
const sm = SessionManager.getInstance();
async function manual(url, init = {}) {
const opts = { ...init, headers: { ...(init.headers || {}) }, credentials: 'include' };
opts.headers['cf-session-id'] = sm.getSessionId();
opts.headers['x-client-platform'] = 'web';
if (sm.shouldUseTokenHeader()) {
const t = sm.getToken(sm.config.tokenCookieName);
if (t) opts.headers[sm.config.tokenCookieName] = t;
}
let res = await fetch(url, opts);
if (res.status === 401) {
const cloned = res.clone();
try {
const data = await cloned.json();
if (data.error_msg === 'INVALID_SESSION') {
if (sm.isRefreshing()) {
await sm.waitForRefresh();
} else {
const existingToken = sm.getToken(sm.config.tokenCookieName);
if (!existingToken) {
await sm.refreshToken();
}
}
opts.headers['cf-session-id'] = sm.getSessionId();
opts.headers['x-client-platform'] = 'web';
if (sm.shouldUseTokenHeader()) {
const nt = sm.getToken(sm.config.tokenCookieName);
if (nt) opts.headers[sm.config.tokenCookieName] = nt;
}
res = await fetch(url, opts);
}
} catch {
// Not JSON or parsing failed
}
}
return res;
}
export const getOrders = () => manual(${API_BASE_URL}/api/orders);`
3) Usage in a component
`jsx
import { useEffect, useState } from 'react';
import { useSession } from 'gateway-web-sdk';
import { getUser, getProducts, getOrders } from './api/index.js';
export default function Dashboard() {
const { isInitialized, isLoading, error } = useSession();
const [data, setData] = useState(null);
useEffect(() => {
if (!isInitialized) return;
(async () => {
try {
const [u, p, o] = await Promise.all([getUser(), getProducts(), getOrders()]);
if (!u.ok) throw new Error('User failed');
if (!o.ok) throw new Error('Orders failed');
setData({ user: await u.json(), products: p.data, orders: await o.json() });
} catch (e) {
console.error(e);
setData(null);
}
})();
}, [isInitialized]);
if (isLoading) return
Loading sessionβ¦
;Session error, limited mode.
;Loading dataβ¦
; return (
{JSON.stringify(data.user, null, 2)}{JSON.stringify(data.products, null, 2)}{JSON.stringify(data.orders, null, 2)}Notes
- Install axios if you use the Axios pattern:
npm i axios
- Ensure your gateway exposes:
- GET /api/session/bootstrap
- GET /api/user
- GET /api/products
- GET /api/orders
- The SDK automatically sends cookies; token header fallback is added only when necessary.$3
`jsx
import React from 'react';
import { useSession } from 'gateway-web-sdk';function App() {
const { isInitialized, isLoading, error, timeUntilRefresh, refresh } = useSession();
if (isLoading) {
return
Loading session...;
} if (error) {
return
Error: {error};
} if (!isInitialized) {
return
Session not initialized;
} return (
Session Active
Next refresh in: {Math.floor(timeUntilRefresh / 1000)} seconds
);
}export default App;
`$3
Configure the session manager before your app renders:
`jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { SessionManager } from 'gateway-web-sdk';
import App from './App';// Configure session manager
const sessionManager = SessionManager.getInstance();
sessionManager.configure({
bootstrapUrl: 'https://your-api.com/session/bootstrap',
headers: {
'X-Custom-Header': 'value',
},
});
ReactDOM.createRoot(document.getElementById('root')).render(
);
`$3
#### Using App Router (
app/layout.js)`jsx
'use client';import { useEffect } from 'react';
import { SessionManager } from 'gateway-web-sdk';
export default function RootLayout({ children }) {
useEffect(() => {
const sessionManager = SessionManager.getInstance();
sessionManager.configure({
bootstrapUrl: '/api/session/bootstrap',
});
sessionManager.initialize();
}, []);
return (
{children}
);
}
`#### Using Pages Router (
pages/_app.js)`jsx
import { useEffect } from 'react';
import { SessionManager } from 'gateway-web-sdk';function MyApp({ Component, pageProps }) {
useEffect(() => {
const sessionManager = SessionManager.getInstance();
sessionManager.configure({
bootstrapUrl: '/api/session/bootstrap',
});
sessionManager.initialize();
}, []);
return ;
}
export default MyApp;
`API Reference
$3
React hook for session management.
#### Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
|
options.autoInitialize | boolean | true | Automatically initialize session on mount |#### Return Value
`typescript
{
isInitialized: boolean; // Whether session is initialized
isLoading: boolean; // Whether bootstrap is in progress
error: string | null; // Error message if any
lastRefreshTime: number; // Timestamp of last refresh
nextRefreshTime: number; // Timestamp of next scheduled refresh
timeUntilRefresh: number; // Milliseconds until next refresh
refresh: () => Promise; // Manual refresh function
initialize: () => Promise; // Manual initialize function
}
`$3
Core session management class (Singleton).
#### Methods
#####
configure(config)Configure the session manager.
`javascript
sessionManager.configure({
bootstrapUrl: '/session/bootstrap', // Required: Bootstrap API endpoint
tokenCookieName: 'stoken', // Optional: Custom token cookie name
maxRetries: 3, // Optional: Max retry attempts
headers: {}, // Optional: Additional headers
credentials: true, // Optional: Include credentials (default: true)
});
`Note:
refreshInterval and tokenExpiry are automatically set by the server's bootstrap response (refresh_time and expire fields). You don't need to configure them manually.#####
initialize()Initialize session by calling bootstrap API.
`javascript
await sessionManager.initialize();
`#####
refreshToken()Manually refresh the session token.
`javascript
await sessionManager.refreshToken();
`#####
getSessionStatus()Get current session status.
`javascript
const status = sessionManager.getSessionStatus();
`#####
subscribe(listener)Subscribe to session state changes.
`javascript
const unsubscribe = sessionManager.subscribe((state) => {
console.log('Session state:', state);
});// Later: unsubscribe()
`#####
destroy()Clean up and reset the session manager.
`javascript
sessionManager.destroy();
`$3
####
fetchInterceptor(url, options)Fetch wrapper with automatic token refresh on 401/403.
`javascript
import { fetchInterceptor } from 'gateway-web-sdk';const response = await fetchInterceptor('/api/data', {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
`####
setupAxiosInterceptor(axiosInstance)Configure Axios instance with automatic token refresh.
`javascript
import axios from 'axios';
import { setupAxiosInterceptor } from 'gateway-web-sdk';const api = setupAxiosInterceptor(axios.create({
baseURL: 'https://api.example.com'
}));
`Client-Side Best Practices
1. Single Instance: Call configure() once at app startup and reuse the singleton.
2. Token Timing: The server controls refresh and expiry timing via bootstrap response.
3. Error Handling: Handle errors and provide user feedback for limited mode.
4. HTTPS: Use secure, production-grade origins (https) in production environments.
5. CORS/Credentials: If cross-origin, ensure credentials are enabled in server CORS (SDK defaults to credentials: true).
6. Initialization: Initialize after configure() and before issuing business API calls.
Troubleshooting
$3
- Verify your API returns
Set-Cookie header with HttpOnly flag
- Check CORS configuration allows credentials
- Ensure credentials: true in configuration$3
- Check browser console for errors
- Verify server is sending
refresh_time in bootstrap response
- Ensure timer isn't being cleared prematurely$3
- The SDK uses singleton pattern, but ensure you're not calling
initialize() multiple times
- Use autoInitialize: false in useSession() if you want manual control$3
- The SDK automatically detects SSR environments and prevents initialization
- Always wrap initialization in
useEffect or client components ('use client')
- Do not call initialize() during server-side renderingTypeScript Support
Full TypeScript definitions are included:
`typescript
import { SessionManager, useSession } from 'gateway-web-sdk';
import type { SessionConfig, SessionState, UseSessionOptions } from 'gateway-web-sdk';const config: SessionConfig = {
bootstrapUrl: '/api/session',
};
const manager = SessionManager.getInstance();
manager.configure(config);
``#### Added
- Initial production release
- TypeScript definitions for full type safety
- Build process with Rollup (CJS + ESM outputs)
- SSR detection for Next.js compatibility
- Configuration validation
- Secure session ID generation using crypto.randomUUID()
- URL encoding for cookie values
#### Security
- Added warnings for client-side cookie limitations
- Improved session ID generation with Web Crypto API
- Added SSR environment checks to prevent runtime errors
- URL-encoded cookie values to prevent injection
MIT
Contributions are welcome! Please open an issue or submit a pull request.
For issues and questions:
- GitHub Issues: Report a bug
- Documentation: Full API Reference