42ff3e8f33
Build & Deploy / Test & Lint (push) Has been cancelled
Build & Deploy / Security Scan (push) Has been cancelled
Code Quality / Code Quality Checks (push) Has been cancelled
Code Quality / Dependency Vulnerabilities (push) Has been cancelled
Code Quality / Performance Testing (push) Has been cancelled
Build & Deploy / Build Docker Images (push) Has been cancelled
Build & Deploy / Deploy to Kubernetes (push) Has been cancelled
209 lines
5.9 KiB
JavaScript
209 lines
5.9 KiB
JavaScript
/**
|
|
* SERVICE WORKER: Token Refresh
|
|
*
|
|
* PURPOSE: Handle automatic token refresh in background
|
|
* FEATURES:
|
|
* - Periodically check if token needs refresh
|
|
* - Refresh token before expiry
|
|
* - Intercept requests to add auth headers
|
|
* - Handle token expiry gracefully
|
|
*
|
|
* INSTALLED: Before app loads
|
|
* ACTIVATED: On page load
|
|
* RUNS: Periodically in background
|
|
*/
|
|
|
|
// Token check interval (every 60 seconds)
|
|
const TOKEN_CHECK_INTERVAL = 60 * 1000;
|
|
|
|
// Token refresh buffer (refresh 5 minutes before expiry)
|
|
const REFRESH_BUFFER = 5 * 60 * 1000;
|
|
|
|
// ============================================================================
|
|
// INSTALLATION
|
|
// ============================================================================
|
|
|
|
/**
|
|
* EVENT: install
|
|
*
|
|
* Called when service worker is registered
|
|
* Used to cache critical assets
|
|
*/
|
|
self.addEventListener('install', (event: ExtendEvent) => {
|
|
console.log('[ServiceWorker] Installing...');
|
|
event.waitUntil(
|
|
Promise.resolve().then(() => {
|
|
console.log('[ServiceWorker] Installed');
|
|
// Skip waiting - activate immediately
|
|
self.skipWaiting();
|
|
})
|
|
);
|
|
});
|
|
|
|
// ============================================================================
|
|
// ACTIVATION
|
|
// ============================================================================
|
|
|
|
/**
|
|
* EVENT: activate
|
|
*
|
|
* Called when service worker takes control
|
|
* Used to clean up old caches
|
|
*/
|
|
self.addEventListener('activate', (event: ExtendEvent) => {
|
|
console.log('[ServiceWorker] Activating...');
|
|
event.waitUntil(
|
|
Promise.resolve().then(() => {
|
|
console.log('[ServiceWorker] Activated');
|
|
// Take control of all pages
|
|
return (self as any).clients.claim();
|
|
})
|
|
);
|
|
});
|
|
|
|
// ============================================================================
|
|
// TOKEN REFRESH LOOP
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Periodically check and refresh token
|
|
*/
|
|
async function startTokenRefreshLoop() {
|
|
console.log('[ServiceWorker] Starting token refresh loop');
|
|
|
|
setInterval(async () => {
|
|
try {
|
|
// Get token expiry from session storage
|
|
// (accessed via message from client since SW can't access sessionStorage directly)
|
|
// This is handled via postMessage from client
|
|
|
|
console.log('[ServiceWorker] Checking token...');
|
|
|
|
// Token refresh is initiated from client via postMessage
|
|
// because ServiceWorker can't access sessionStorage directly
|
|
} catch (error) {
|
|
console.error('[ServiceWorker] Token check error:', error);
|
|
}
|
|
}, TOKEN_CHECK_INTERVAL);
|
|
}
|
|
|
|
// Start the loop when activated
|
|
startTokenRefreshLoop();
|
|
|
|
// ============================================================================
|
|
// MESSAGE HANDLING
|
|
// ============================================================================
|
|
|
|
/**
|
|
* EVENT: message
|
|
*
|
|
* Called when client (page) sends message to ServiceWorker
|
|
* Used to:
|
|
* - Check if token needs refresh
|
|
* - Perform token refresh
|
|
* - Get current token status
|
|
*/
|
|
self.addEventListener('message', async (event: ExtendMessageEvent) => {
|
|
const { type, payload } = event.data;
|
|
|
|
console.log('[ServiceWorker] Received message:', type);
|
|
|
|
try {
|
|
if (type === 'REFRESH_TOKEN') {
|
|
// Client asking us to refresh token
|
|
const { refreshToken, apiUrl } = payload;
|
|
|
|
console.log('[ServiceWorker] Refreshing token...');
|
|
|
|
// Call backend to refresh token
|
|
const response = await fetch(`${apiUrl}/api/auth/refresh`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ refreshToken }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Token refresh failed');
|
|
}
|
|
|
|
const data = await response.json() as any;
|
|
|
|
console.log('[ServiceWorker] Token refreshed successfully');
|
|
|
|
// Send new token back to client
|
|
event.ports[0].postMessage({
|
|
success: true,
|
|
accessToken: data.accessToken,
|
|
expiresIn: data.expiresIn,
|
|
});
|
|
} else if (type === 'CHECK_TOKEN') {
|
|
// Client asking if token needs refresh
|
|
const { tokenExpiry } = payload;
|
|
const now = Date.now();
|
|
const timeUntilExpiry = tokenExpiry - now;
|
|
const needsRefresh = timeUntilExpiry < REFRESH_BUFFER;
|
|
|
|
console.log(`[ServiceWorker] Token expires in ${Math.round(timeUntilExpiry / 1000)}s`);
|
|
|
|
event.ports[0].postMessage({
|
|
needsRefresh,
|
|
timeUntilExpiry,
|
|
});
|
|
} else if (type === 'GET_STATUS') {
|
|
// Client asking for ServiceWorker status
|
|
event.ports[0].postMessage({
|
|
active: true,
|
|
version: '1.0.0',
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('[ServiceWorker] Message handling error:', error);
|
|
event.ports[0].postMessage({
|
|
error: (error as Error).message,
|
|
});
|
|
}
|
|
});
|
|
|
|
// ============================================================================
|
|
// REQUEST INTERCEPTION (future use)
|
|
// ============================================================================
|
|
|
|
/**
|
|
* EVENT: fetch
|
|
*
|
|
* Called for every network request
|
|
* Could be used to:
|
|
* - Intercept API requests
|
|
* - Add auth headers
|
|
* - Retry on auth failure
|
|
*
|
|
* Disabled for now since we handle auth in the app
|
|
* Enables when needed for request interception
|
|
*/
|
|
self.addEventListener('fetch', (event: ExtendFetchEvent) => {
|
|
// Currently, just pass through
|
|
// In future, could intercept API calls to add auth headers
|
|
// or retry on 401 by refreshing token
|
|
});
|
|
|
|
// ============================================================================
|
|
// TYPES
|
|
// ============================================================================
|
|
|
|
interface ExtendEvent extends Event {
|
|
waitUntil(promise: Promise<any>): void;
|
|
}
|
|
|
|
interface ExtendMessageEvent extends ExtendEvent {
|
|
data: {
|
|
type: string;
|
|
payload?: any;
|
|
};
|
|
ports: MessagePort[];
|
|
}
|
|
|
|
interface ExtendFetchEvent extends ExtendEvent {
|
|
request: Request;
|
|
respondWith(response: Response | Promise<Response>): void;
|
|
}
|