205 lines
4.8 KiB
TypeScript
205 lines
4.8 KiB
TypeScript
/**
|
|
* PWA Hook - React hook for Progressive Web App functionality
|
|
* FE-TYPE-012: Fully typed hook return
|
|
*/
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { pwaService, type PWAStatus } from '@/services/pwa';
|
|
import { logger } from '@/utils/logger';
|
|
import type {
|
|
UsePWAReturn,
|
|
UsePWAInstallBannerReturn,
|
|
UseOfflineDetectionReturn,
|
|
} from './types';
|
|
|
|
export function usePWA(): UsePWAReturn {
|
|
const [status, setStatus] = useState<PWAStatus>(pwaService.getStatus());
|
|
const [isInstalling, setIsInstalling] = useState(false);
|
|
const [isUpdating, setIsUpdating] = useState(false);
|
|
|
|
useEffect(() => {
|
|
// Subscribe to PWA status changes
|
|
const unsubscribe = pwaService.onStatusChange(setStatus);
|
|
|
|
// Initial status check
|
|
setStatus(pwaService.getStatus());
|
|
|
|
return unsubscribe;
|
|
}, []);
|
|
|
|
/**
|
|
* Install the PWA
|
|
*/
|
|
const install = async (): Promise<boolean> => {
|
|
if (!status.isInstallable || isInstalling) {
|
|
return false;
|
|
}
|
|
|
|
setIsInstalling(true);
|
|
|
|
try {
|
|
const result = await pwaService.promptInstall();
|
|
return result;
|
|
} catch (error) {
|
|
logger.error('[PWA Hook] Install failed', {
|
|
error: error instanceof Error ? error.message : String(error),
|
|
stack: error instanceof Error ? error.stack : undefined,
|
|
});
|
|
return false;
|
|
} finally {
|
|
setIsInstalling(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Update the service worker
|
|
*/
|
|
const update = async (): Promise<void> => {
|
|
if (!status.updateAvailable || isUpdating) {
|
|
return;
|
|
}
|
|
|
|
setIsUpdating(true);
|
|
|
|
try {
|
|
await pwaService.updateServiceWorker();
|
|
} catch (error) {
|
|
logger.error('[PWA Hook] Update failed', {
|
|
error: error instanceof Error ? error.message : String(error),
|
|
stack: error instanceof Error ? error.stack : undefined,
|
|
});
|
|
} finally {
|
|
setIsUpdating(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Request notification permission
|
|
*/
|
|
const requestNotifications = async (): Promise<NotificationPermission> => {
|
|
return await pwaService.requestNotificationPermission();
|
|
};
|
|
|
|
/**
|
|
* Show a notification
|
|
*/
|
|
const showNotification = async (
|
|
title: string,
|
|
options?: NotificationOptions,
|
|
): Promise<void> => {
|
|
return await pwaService.showNotification(title, options);
|
|
};
|
|
|
|
/**
|
|
* Clear all caches
|
|
*/
|
|
const clearCaches = async (): Promise<void> => {
|
|
return await pwaService.clearCaches();
|
|
};
|
|
|
|
/**
|
|
* Get service worker version
|
|
*/
|
|
const getVersion = async (): Promise<string> => {
|
|
return await pwaService.getVersion();
|
|
};
|
|
|
|
return {
|
|
// Status
|
|
...status,
|
|
hasServiceWorker: status.serviceWorkerReady,
|
|
|
|
// Loading states
|
|
isInstalling,
|
|
isUpdating,
|
|
|
|
// Actions
|
|
install,
|
|
update,
|
|
requestNotifications,
|
|
showNotification,
|
|
clearCaches,
|
|
getVersion,
|
|
|
|
// Computed properties
|
|
canInstall: status.isInstallable && !isInstalling,
|
|
canUpdate: status.updateAvailable && !isUpdating,
|
|
isOffline: !status.isOnline,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Hook for PWA install banner
|
|
* FE-TYPE-012: Fully typed hook return
|
|
*/
|
|
export function usePWAInstallBanner(): UsePWAInstallBannerReturn {
|
|
const { isInstallable, isInstalled, install, isInstalling } = usePWA();
|
|
const [showBanner, setShowBanner] = useState(false);
|
|
const [dismissed, setDismissed] = useState(false);
|
|
|
|
useEffect(() => {
|
|
// Show banner if app is installable and not dismissed
|
|
const shouldShow = isInstallable && !isInstalled && !dismissed;
|
|
setShowBanner(shouldShow);
|
|
}, [isInstallable, isInstalled, dismissed]);
|
|
|
|
const handleInstall = async () => {
|
|
const success = await install();
|
|
if (success) {
|
|
setShowBanner(false);
|
|
}
|
|
};
|
|
|
|
const handleDismiss = () => {
|
|
setDismissed(true);
|
|
setShowBanner(false);
|
|
|
|
// Remember dismissal for this session
|
|
sessionStorage.setItem('pwa-install-dismissed', 'true');
|
|
};
|
|
|
|
// Check if banner was dismissed this session
|
|
useEffect(() => {
|
|
const wasDismissed =
|
|
sessionStorage.getItem('pwa-install-dismissed') === 'true';
|
|
setDismissed(wasDismissed);
|
|
}, []);
|
|
|
|
return {
|
|
showBanner,
|
|
isInstalling,
|
|
handleInstall,
|
|
handleDismiss,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Hook for offline detection and handling
|
|
* FE-TYPE-012: Fully typed hook return
|
|
*/
|
|
export function useOfflineDetection(): UseOfflineDetectionReturn {
|
|
const { isOnline } = usePWA();
|
|
const [showOfflineBanner, setShowOfflineBanner] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!isOnline) {
|
|
setShowOfflineBanner(true);
|
|
|
|
// Auto-hide after 5 seconds
|
|
const timer = setTimeout(() => {
|
|
setShowOfflineBanner(false);
|
|
}, 5000);
|
|
|
|
return () => clearTimeout(timer);
|
|
}
|
|
setShowOfflineBanner(false);
|
|
return undefined;
|
|
}, [isOnline]);
|
|
|
|
return {
|
|
isOnline,
|
|
isOffline: !isOnline,
|
|
showOfflineBanner,
|
|
dismissOfflineBanner: () => setShowOfflineBanner(false),
|
|
};
|
|
}
|