@mapnests/gateway-web-sdk
v1.0.2
Published
Session token management SDK with automatic refresh for React/Next.js applications
Maintainers
Readme
Gateway Session SDK
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.
Features
- 🔄 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
Security Notice
⚠️ 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-Cookieheader withHttpOnlyflag - 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
Installation
npm install gateway-web-sdkQuick Start
Simple Implementation (Copy-Paste)
This setup covers bootstrap on app start and three API patterns:
- Users → Fetch interceptor
- Products → Axios interceptor
- Orders → Manual implementation
- Configure and initialize once on app start
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, REFRESH_INTERVAL, TOKEN_EXPIRY, CREDENTIALS } from './config.js';
import App from './App';
const sessionManager = SessionManager.getInstance();
try {
sessionManager.configure({
bootstrapUrl: `${API_BASE_URL}${BOOTSTRAP_PATH}`,
tokenCookieName: TOKEN_COOKIE_NAME,
refreshInterval: (REFRESH_INTERVAL ? parseInt(REFRESH_INTERVAL) : 25) * 60 * 1000,
tokenExpiry: (TOKEN_EXPIRY ? parseInt(TOKEN_EXPIRY) : 30) * 60 * 1000,
credentials: CREDENTIALS === 'true',
});
} 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(
<React.StrictMode>
<App />
</React.StrictMode>
);- API layer with three patterns
// 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();
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();
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`);- Usage in a component
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 <p>Loading session…</p>;
if (error) return <p>Session error, limited mode.</p>;
if (!data) return <p>Loading data…</p>;
return (
<div>
<h3>User</h3>
<pre>{JSON.stringify(data.user, null, 2)}</pre>
<h3>Products</h3>
<pre>{JSON.stringify(data.products, null, 2)}</pre>
<h3>Orders</h3>
<pre>{JSON.stringify(data.orders, null, 2)}</pre>
</div>
);
}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.
React Application
import React from 'react';
import { useSession } from 'gateway-web-sdk';
function App() {
const { isInitialized, isLoading, error, timeUntilRefresh, refresh } = useSession();
if (isLoading) {
return <div>Loading session...</div>;
}
if (error) {
return <div>Error: {error}</div>;
}
if (!isInitialized) {
return <div>Session not initialized</div>;
}
return (
<div>
<h1>Session Active</h1>
<p>Next refresh in: {Math.floor(timeUntilRefresh / 1000)} seconds</p>
<button onClick={refresh}>Refresh Now</button>
</div>
);
}
export default App;Configuration
Configure the session manager before your app renders:
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',
refreshInterval: 25 * 60 * 1000, // 25 minutes
tokenExpiry: 30 * 60 * 1000, // 30 minutes
headers: {
'X-Custom-Header': 'value',
},
credentials: true, // Include cookies in requests
});
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
);Next.js Application
Using App Router (app/layout.js)
'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',
refreshInterval: 25 * 60 * 1000,
});
sessionManager.initialize();
}, []);
return (
<html lang="en">
<body>{children}</body>
</html>
);
}Using Pages Router (pages/_app.js)
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',
refreshInterval: 25 * 60 * 1000,
});
sessionManager.initialize();
}, []);
return <Component {...pageProps} />;
}
export default MyApp;API Reference
useSession(options)
React hook for session management.
Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| options.autoInitialize | boolean | true | Automatically initialize session on mount |
Return Value
{
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<void>; // Manual refresh function
initialize: () => Promise<void>; // Manual initialize function
}SessionManager
Core session management class (Singleton).
Methods
configure(config)
Configure the session manager.
sessionManager.configure({
bootstrapUrl: '/session/bootstrap', // Required: Bootstrap API endpoint
tokenCookieName: 'stoken', // Optional: Custom token cookie name
refreshInterval: 25 * 60 * 1000, // Optional: Refresh interval (ms)
tokenExpiry: 30 * 60 * 1000, // Optional: Token expiry (ms)
maxRetries: 3, // Optional: Max retry attempts
headers: {}, // Optional: Additional headers
credentials: true, // Optional: Include credentials
});initialize()
Initialize session by calling bootstrap API.
await sessionManager.initialize();refreshToken()
Manually refresh the session token.
await sessionManager.refreshToken();getSessionStatus()
Get current session status.
const status = sessionManager.getSessionStatus();subscribe(listener)
Subscribe to session state changes.
const unsubscribe = sessionManager.subscribe((state) => {
console.log('Session state:', state);
});
// Later: unsubscribe()destroy()
Clean up and reset the session manager.
sessionManager.destroy();Interceptors (Optional)
fetchInterceptor(url, options)
Fetch wrapper with automatic token refresh on 401/403.
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.
import axios from 'axios';
import { setupAxiosInterceptor } from 'gateway-web-sdk';
const api = setupAxiosInterceptor(axios.create({
baseURL: 'https://api.example.com'
}));Client-Side Best Practices
- Single Instance: Call configure() once at app startup and reuse the singleton.
- Token Timing: Set refreshInterval about 5 minutes before tokenExpiry to avoid race conditions.
- Error Handling: Handle errors and provide user feedback for limited mode.
- HTTPS: Use secure, production-grade origins (https) in production environments.
- CORS/Credentials: If cross-origin, ensure credentials are enabled in client config and server CORS.
- Initialization: Initialize after configure() and before issuing business API calls.
Troubleshooting
Cookies not being set
- Verify your API returns
Set-Cookieheader withHttpOnlyflag - Check CORS configuration allows credentials
- Ensure
credentials: truein configuration
Automatic refresh not working
- Check browser console for errors
- Verify
refreshIntervalis set correctly - Ensure timer isn't being cleared prematurely
Multiple initializations
- The SDK uses singleton pattern, but ensure you're not calling
initialize()multiple times - Use
autoInitialize: falseinuseSession()if you want manual control
Next.js SSR errors
- The SDK automatically detects SSR environments and prevents initialization
- Always wrap initialization in
useEffector client components ('use client') - Do not call
initialize()during server-side rendering
TypeScript Support
Full TypeScript definitions are included:
import { SessionManager, useSession } from 'gateway-web-sdk';
import type { SessionConfig, SessionState, UseSessionOptions } from 'gateway-web-sdk';
const config: SessionConfig = {
bootstrapUrl: '/api/session',
refreshInterval: 25 * 60 * 1000
};
const manager = SessionManager.getInstance();
manager.configure(config);Changelog
[1.0.0] - 2024-01-12
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
License
MIT
Contributing
Contributions are welcome! Please open an issue or submit a pull request.
Support
For issues and questions:
- GitHub Issues: Report a bug
- Documentation: Full API Reference
