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
195 lines
4.9 KiB
TypeScript
195 lines
4.9 KiB
TypeScript
/**
|
|
* UTILITY: Service Worker Manager
|
|
*
|
|
* PURPOSE: Register and communicate with Service Worker
|
|
* FEATURES:
|
|
* - Register Service Worker on app load
|
|
* - Send messages to SW
|
|
* - Receive messages from SW
|
|
* - Handle token refresh via SW
|
|
*/
|
|
|
|
export interface TokenCheckResult {
|
|
needsRefresh: boolean;
|
|
timeUntilExpiry: number;
|
|
}
|
|
|
|
export interface TokenRefreshResult {
|
|
success: boolean;
|
|
accessToken?: string;
|
|
expiresIn?: number;
|
|
error?: string;
|
|
}
|
|
|
|
/**
|
|
* FUNCTION: registerServiceWorker
|
|
*
|
|
* Register the service worker on app load
|
|
* Called once from App.svelte onMount
|
|
*/
|
|
export async function registerServiceWorker(): Promise<ServiceWorkerContainer | null> {
|
|
// Service Workers only available in production (HTTPS) or localhost
|
|
if (!('serviceWorker' in navigator)) {
|
|
console.log('[SW Manager] Service Workers not supported');
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
console.log('[SW Manager] Registering Service Worker...');
|
|
|
|
const registration = await navigator.serviceWorker.register('/service-worker.js', {
|
|
scope: '/',
|
|
});
|
|
|
|
console.log('[SW Manager] Service Worker registered:', registration);
|
|
|
|
// Listen for messages from Service Worker
|
|
navigator.serviceWorker.addEventListener('message', (event) => {
|
|
console.log('[SW Manager] Message from Service Worker:', event.data);
|
|
});
|
|
|
|
return registration;
|
|
} catch (error) {
|
|
console.error('[SW Manager] Failed to register Service Worker:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* FUNCTION: checkTokenExpiry
|
|
*
|
|
* Ask Service Worker if token needs refresh
|
|
*
|
|
* PARAMS:
|
|
* - tokenExpiry: number - Token expiry timestamp (ms since epoch)
|
|
*
|
|
* RETURNS: TokenCheckResult
|
|
*/
|
|
export async function checkTokenExpiry(tokenExpiry: number): Promise<TokenCheckResult> {
|
|
if (!navigator.serviceWorker.controller) {
|
|
console.log('[SW Manager] Service Worker not active');
|
|
return { needsRefresh: false, timeUntilExpiry: tokenExpiry - Date.now() };
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const channel = new MessageChannel();
|
|
|
|
channel.port1.onmessage = (event) => {
|
|
if (event.data.error) {
|
|
reject(new Error(event.data.error));
|
|
} else {
|
|
resolve(event.data);
|
|
}
|
|
};
|
|
|
|
navigator.serviceWorker.controller?.postMessage(
|
|
{
|
|
type: 'CHECK_TOKEN',
|
|
payload: { tokenExpiry },
|
|
},
|
|
[channel.port2]
|
|
);
|
|
|
|
// Timeout after 5 seconds
|
|
setTimeout(() => reject(new Error('Token check timeout')), 5000);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* FUNCTION: refreshTokenViaServiceWorker
|
|
*
|
|
* Ask Service Worker to refresh access token
|
|
*
|
|
* PARAMS:
|
|
* - refreshToken: string - Refresh token
|
|
* - apiUrl: string - Backend API URL
|
|
*
|
|
* RETURNS: TokenRefreshResult
|
|
*/
|
|
export async function refreshTokenViaServiceWorker(
|
|
refreshToken: string,
|
|
apiUrl: string
|
|
): Promise<TokenRefreshResult> {
|
|
if (!navigator.serviceWorker.controller) {
|
|
console.log('[SW Manager] Service Worker not active');
|
|
return { success: false, error: 'Service Worker not active' };
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const channel = new MessageChannel();
|
|
|
|
channel.port1.onmessage = (event) => {
|
|
if (event.data.error) {
|
|
resolve({ success: false, error: event.data.error });
|
|
} else {
|
|
resolve(event.data);
|
|
}
|
|
};
|
|
|
|
navigator.serviceWorker.controller?.postMessage(
|
|
{
|
|
type: 'REFRESH_TOKEN',
|
|
payload: { refreshToken, apiUrl },
|
|
},
|
|
[channel.port2]
|
|
);
|
|
|
|
// Timeout after 10 seconds
|
|
setTimeout(() => reject(new Error('Token refresh timeout')), 10000);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* FUNCTION: getServiceWorkerStatus
|
|
*
|
|
* Get Service Worker status and version
|
|
*
|
|
* RETURNS: { active: boolean, version?: string }
|
|
*/
|
|
export async function getServiceWorkerStatus(): Promise<{ active: boolean; version?: string }> {
|
|
if (!navigator.serviceWorker.controller) {
|
|
return { active: false };
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const channel = new MessageChannel();
|
|
|
|
channel.port1.onmessage = (event) => {
|
|
if (event.data.error) {
|
|
reject(new Error(event.data.error));
|
|
} else {
|
|
resolve(event.data);
|
|
}
|
|
};
|
|
|
|
navigator.serviceWorker.controller?.postMessage(
|
|
{ type: 'GET_STATUS' },
|
|
[channel.port2]
|
|
);
|
|
|
|
// Timeout after 5 seconds
|
|
setTimeout(() => reject(new Error('Status check timeout')), 5000);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* FUNCTION: unregisterServiceWorker
|
|
*
|
|
* Unregister the service worker (for development)
|
|
*/
|
|
export async function unregisterServiceWorker(): Promise<void> {
|
|
if (!('serviceWorker' in navigator)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const registrations = await navigator.serviceWorker.getRegistrations();
|
|
for (const registration of registrations) {
|
|
await registration.unregister();
|
|
}
|
|
console.log('[SW Manager] Service Worker unregistered');
|
|
} catch (error) {
|
|
console.error('[SW Manager] Failed to unregister Service Worker:', error);
|
|
}
|
|
}
|