diff --git a/app/components/RootLayoutClient.tsx b/app/components/RootLayoutClient.tsx index a299f95..6f1983e 100644 --- a/app/components/RootLayoutClient.tsx +++ b/app/components/RootLayoutClient.tsx @@ -15,16 +15,18 @@ import { LoginForm } from "./start-screen"; import Image from "next/image"; import PageTransition from "./PageTransition"; -// Service Worker registration -if (typeof window !== 'undefined' && 'serviceWorker' in navigator) { - navigator.serviceWorker.register('/sw.js') - .then((registration) => { - console.log('Service Worker registered successfully:', registration); - }) - .catch((error) => { - console.error('Service Worker registration failed:', error); - }); -} +// Service Worker registration - moved to useEffect to ensure it only runs client-side +React.useEffect(() => { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/sw.js') + .then((registration) => { + console.log('Service Worker registered successfully:', registration); + }) + .catch((error) => { + console.error('Service Worker registration failed:', error); + }); + } +}, []); function NavidromeErrorBoundary({ children }: { children: React.ReactNode }) { // For now, since we're switching to offline-first, we'll handle errors differently diff --git a/hooks/use-offline-downloads.ts b/hooks/use-offline-downloads.ts index 78505b8..644c9df 100644 --- a/hooks/use-offline-downloads.ts +++ b/hooks/use-offline-downloads.ts @@ -394,7 +394,95 @@ class DownloadManager { } } -const downloadManager = new DownloadManager(); +// Create a singleton instance that will be initialized on the client side +let downloadManagerInstance: DownloadManager | null = null; + +// Only create the download manager instance on the client side +if (typeof window !== 'undefined') { + downloadManagerInstance = new DownloadManager(); +} + +// Create a safe wrapper around the download manager +const downloadManager = { + initialize: async () => { + if (!downloadManagerInstance) return false; + return downloadManagerInstance.initialize(); + }, + getOfflineStats: async () => { + if (!downloadManagerInstance) return { + totalSize: 0, + audioSize: 0, + imageSize: 0, + metaSize: 0, + downloadedAlbums: 0, + downloadedSongs: 0, + lastDownload: null, + downloadErrors: 0, + remainingStorage: null, + autoDownloadEnabled: false, + downloadQuality: 'original' as const, + downloadOnWifiOnly: true, + priorityContent: [] + }; + return downloadManagerInstance.getOfflineStats(); + }, + downloadAlbum: async (album: Album, songs: Song[], progressCallback: (progress: DownloadProgress) => void) => { + if (!downloadManagerInstance) return; + return downloadManagerInstance.downloadAlbum(album, songs, progressCallback); + }, + downloadAlbumFallback: async (album: Album, songs: Song[]) => { + if (!downloadManagerInstance) return; + return downloadManagerInstance.downloadAlbumFallback(album, songs); + }, + downloadSong: async (song: Song) => { + if (!downloadManagerInstance) return; + return downloadManagerInstance.downloadSong(song); + }, + getOfflineData: () => { + if (!downloadManagerInstance) return { albums: {}, songs: {} }; + return downloadManagerInstance.getOfflineData(); + }, + saveOfflineData: (data: any) => { + if (!downloadManagerInstance) return; + return downloadManagerInstance.saveOfflineData(data); + }, + checkOfflineStatus: async (id: string, type: 'album' | 'song') => { + if (!downloadManagerInstance) return false; + return downloadManagerInstance.checkOfflineStatus(id, type); + }, + checkOfflineStatusFallback: (id: string, type: 'album' | 'song') => { + if (!downloadManagerInstance) return false; + return downloadManagerInstance.checkOfflineStatusFallback(id, type); + }, + deleteOfflineContent: async (id: string, type: 'album' | 'song') => { + if (!downloadManagerInstance) return; + return downloadManagerInstance.deleteOfflineContent(id, type); + }, + deleteOfflineContentFallback: async (id: string, type: 'album' | 'song') => { + if (!downloadManagerInstance) return; + return downloadManagerInstance.deleteOfflineContentFallback(id, type); + }, + getOfflineItems: async () => { + if (!downloadManagerInstance) return { albums: [], songs: [] }; + return downloadManagerInstance.getOfflineItems(); + }, + getOfflineAlbums: () => { + if (!downloadManagerInstance) return []; + return downloadManagerInstance.getOfflineAlbums(); + }, + getOfflineSongs: () => { + if (!downloadManagerInstance) return []; + return downloadManagerInstance.getOfflineSongs(); + }, + downloadQueue: async (songs: Song[]) => { + if (!downloadManagerInstance) return; + return downloadManagerInstance.downloadQueue(songs); + }, + enableOfflineMode: async (settings: any) => { + if (!downloadManagerInstance) return; + return downloadManagerInstance.enableOfflineMode(settings); + } +}; export function useOfflineDownloads() { const [isSupported, setIsSupported] = useState(false); @@ -424,6 +512,13 @@ export function useOfflineDownloads() { useEffect(() => { const initializeDownloadManager = async () => { + // Skip initialization on server-side + if (!downloadManager) { + setIsSupported(false); + setIsInitialized(true); + return; + } + const supported = await downloadManager.initialize(); setIsSupported(supported); setIsInitialized(true); diff --git a/hooks/use-offline-library-sync.ts b/hooks/use-offline-library-sync.ts index 2bd4961..dfc2f68 100644 --- a/hooks/use-offline-library-sync.ts +++ b/hooks/use-offline-library-sync.ts @@ -86,7 +86,10 @@ export function useOfflineLibrarySync() { window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); - setIsOnline(navigator.onLine); + // Check if navigator is available (client-side only) + if (typeof navigator !== 'undefined') { + setIsOnline(navigator.onLine); + } return () => { window.removeEventListener('online', handleOnline); diff --git a/hooks/use-offline-library.ts b/hooks/use-offline-library.ts index ac4e93b..b5f6cae 100644 --- a/hooks/use-offline-library.ts +++ b/hooks/use-offline-library.ts @@ -19,9 +19,12 @@ export interface OfflineLibraryState { } export function useOfflineLibrary() { + // Check if we're on the client side + const isClient = typeof window !== 'undefined'; + const [state, setState] = useState({ isInitialized: false, - isOnline: navigator.onLine, + isOnline: isClient ? navigator.onLine : true, // Default to true during SSR isSyncing: false, lastSync: null, stats: {