@explorins/pers-sdk
Version:
Platform-agnostic SDK for PERS (Phygital Experience Rewards System)
137 lines (121 loc) • 4.47 kB
text/typescript
import { AuthProvider } from './auth-provider.interface';
import { SimpleAuthConfig } from './simple-auth-config.interface';
/**
* Creates a platform-agnostic AuthProvider from simple configuration
*
* This factory function is completely platform-agnostic and can be used
* across Angular, React, Vue, Node.js, or any other JavaScript environment.
*
* Features:
* - Token caching with refresh support
* - Automatic token refresh on expiration
* - Configurable token providers
* - Platform-independent (no localStorage assumptions)
*
* @param config - Simple auth configuration
* @returns AuthProvider implementation
*/
export function createAuthProvider(config: SimpleAuthConfig): AuthProvider {
// Store current token for refresh scenarios and caching
let currentToken: string | null = config.token || null;
let isRefreshing = false; // Prevent concurrent refresh attempts
let refreshPromise: Promise<void> | null = null;
return {
authType: config.authType || 'user',
async getToken(): Promise<string | null> {
// If currently refreshing, wait for it to complete
if (isRefreshing && refreshPromise) {
await refreshPromise;
return currentToken;
}
// Use cached current token (updated after refresh)
if (currentToken) {
return currentToken;
}
// Custom token provider function (always fresh)
if (config.tokenProvider) {
const token = await config.tokenProvider();
currentToken = token; // Cache for future calls
return token;
}
// No token available
return null;
},
async getProjectKey(): Promise<string | null> {
return config.projectKey || null;
},
async onTokenExpired(): Promise<void> {
// Prevent concurrent refresh attempts
if (isRefreshing) {
if (refreshPromise) {
await refreshPromise;
}
return;
}
// No refresh logic provided
if (!config.onTokenExpired) {
console.warn('Token expired but no refresh logic provided');
currentToken = null; // Clear expired token
return;
}
// Start refresh process
isRefreshing = true;
refreshPromise = (async () => {
try {
// Execute refresh logic (should update token source)
await config.onTokenExpired!();
// After refresh, get the new token
if (config.tokenProvider) {
const newToken = await config.tokenProvider();
if (newToken && newToken !== currentToken) {
currentToken = newToken;
// Notify about successful token refresh
if (config.onTokenRefreshed) {
config.onTokenRefreshed(newToken);
}
} else {
console.warn('Token refresh completed but no new token received');
currentToken = null;
}
} else {
// For static token configs, clear the token since we can't refresh
console.warn('Token expired for static token config - clearing token');
currentToken = null;
}
} catch (error) {
console.error('Token refresh failed:', error);
currentToken = null; // Clear token on refresh failure
throw error; // Re-throw to let SDK handle the error
} finally {
isRefreshing = false;
refreshPromise = null;
}
})();
await refreshPromise;
}
};
}
/**
* Platform-specific localStorage token provider for browsers
* This is a convenience function for browser environments
*/
export function createBrowserTokenProvider(tokenKey: string = 'userJwt'): () => Promise<string | null> {
return async () => {
if (typeof localStorage !== 'undefined') {
return localStorage.getItem(tokenKey);
}
return null;
};
}
/**
* Platform-specific environment variable token provider for Node.js
* This is a convenience function for Node.js environments
*/
export function createNodeTokenProvider(envVar: string = 'JWT_TOKEN'): () => Promise<string | null> {
return async () => {
if (typeof process !== 'undefined' && process.env) {
return process.env[envVar] || null;
}
return null;
};
}