# 🐛 PWA Offline Caching Debug Guide ## Issue Summary PWA fails to cache playlists for offline playing with errors like "Failed to cache playlist" and no sound plays offline. ## Root Causes Identified ### 1. CORS Configuration Missing **Problem**: Django wasn't reading `CORS_ALLOWED_ORIGINS` from environment variables. **Fixed**: Updated `backend/config/settings.py` to read from environment variable. **Verify Fix**: ```bash # Rebuild container docker compose build docker compose up -d # Check logs docker compose logs soundwave | grep CORS ``` ### 2. Service Worker Not Receiving Messages **Problem**: Service worker might not be active or registered properly. **Check**: ```javascript // In browser console navigator.serviceWorker.getRegistration().then(reg => { console.log('SW Registered:', !!reg); console.log('SW Active:', reg?.active?.state); }); ``` Expected output: ``` SW Registered: true SW Active: "activated" ``` ### 3. Audio Fetch Failing Due to CORS or Auth **Problem**: Service worker can't fetch audio files due to CORS or authentication issues. ## Step-by-Step Debugging ### Step 1: Check Service Worker Status Open DevTools (F12) → **Application** tab → **Service Workers** You should see: - ✅ `service-worker.js` registered - ✅ Status: "activated and is running" - ✅ Source: `http://localhost:8889/service-worker.js` If not registered: ```javascript // In console navigator.serviceWorker.register('/service-worker.js') .then(reg => console.log('SW Registered', reg)) .catch(err => console.error('SW Registration failed', err)); ``` ### Step 2: Test Service Worker Communication ```javascript // In browser console const testSW = async () => { const reg = await navigator.serviceWorker.ready; if (!reg.active) { console.error('❌ No active service worker'); return; } const messageChannel = new MessageChannel(); messageChannel.port1.onmessage = (event) => { console.log('✅ Response from SW:', event.data); }; reg.active.postMessage( { type: 'CACHE_PLAYLIST', playlistId: 'test123', audioUrls: ['/api/audio/test/download/'] }, [messageChannel.port2] ); }; testSW(); ``` Expected: Response from service worker within 1-2 seconds. ### Step 3: Check CORS Headers Open DevTools → **Network** tab 1. Navigate to a playlist 2. Click "Make Available Offline" 3. Look for requests to `/api/audio/{id}/download/` 4. Click on one request 5. Check **Response Headers** Required headers: ``` Access-Control-Allow-Origin: https://sound.iulian.uk Access-Control-Allow-Credentials: true Content-Type: audio/mpeg (or audio/mp4, etc.) ``` If CORS headers missing: - ❌ Backend CORS not configured correctly - ❌ Check `CORS_ALLOWED_ORIGINS` environment variable ### Step 4: Test Manual Audio Fetch ```javascript // In browser console const testAudioFetch = async () => { try { const response = await fetch('/api/audio/YOUR_YOUTUBE_ID/download/', { credentials: 'include', headers: { 'Accept': 'audio/*' } }); console.log('Status:', response.status); console.log('Headers:', [...response.headers.entries()]); console.log('Content-Type:', response.headers.get('content-type')); if (response.ok) { console.log('✅ Audio fetch successful'); const blob = await response.blob(); console.log('✅ Blob size:', (blob.size / 1024 / 1024).toFixed(2), 'MB'); } else { console.error('❌ Audio fetch failed:', response.statusText); } } catch (err) { console.error('❌ Fetch error:', err); } }; testAudioFetch(); ``` Replace `YOUR_YOUTUBE_ID` with an actual YouTube ID from your playlist. ### Step 5: Inspect Service Worker Console 1. DevTools → **Application** → **Service Workers** 2. Find the active service worker 3. Click **"inspect"** next to it 4. A new DevTools window opens (Service Worker console) 5. Try caching a playlist again 6. Watch the SW console for errors Expected logs in SW console: ``` [Service Worker] Message received: {type: 'CACHE_PLAYLIST', ...} [Service Worker] Caching playlist: PLxxx with 5 tracks [Service Worker] Cached playlist metadata [Service Worker] Fetching: /api/audio/xxx/download/ [Service Worker] ✓ Cached: /api/audio/xxx/download/ - 3.45 MB [Service Worker] ✓ Cached: /api/audio/yyy/download/ - 4.12 MB ... [Service Worker] Playlist caching complete ``` Common errors: ``` ❌ TypeError: Failed to fetch → CORS issue or network problem ❌ HTTP 401 Unauthorized → Authentication cookies not sent ❌ HTTP 403 Forbidden → CORS or CSRF issue ❌ HTTP 404 Not Found → Audio not downloaded yet or wrong URL ``` ### Step 6: Check Cache Storage DevTools → **Application** → **Cache Storage** You should see: - `soundwave-v1` - Static assets - `soundwave-api-v1` - API responses - `soundwave-audio-v1` - **Audio files** ← This is where audio should be - `soundwave-images-v1` - Images Click on `soundwave-audio-v1`: - Should show cached audio files with keys like `/api/audio/{id}/download/` - Each entry should have a size (MB) If empty after caching: - ❌ Caching failed silently - Check SW console for errors ### Step 7: Test Offline Playback 1. Cache a playlist (follow steps above to ensure it works) 2. DevTools → **Network** tab 3. Change throttling to **"Offline"** 4. Try playing a track What to check: - Network tab shows requests but they should come from "ServiceWorker" - Console should show: `[Service Worker] ✓ Serving audio from cache: /api/audio/xxx/download/` If fails: ``` ❌ [Service Worker] ✗ Cache miss for: /api/audio/xxx/download/ → Audio wasn't cached properly ❌ [Service Worker] Available cached audio: [] → Cache is empty ``` ## Quick Fixes ### Fix 1: Restart Docker Containers ```bash cd /home/iulian/projects/zi-tube/soundwave docker compose down docker compose build --no-cache docker compose up -d ``` ### Fix 2: Clear All Caches ```javascript // In browser console const clearAllCaches = async () => { const cacheNames = await caches.keys(); await Promise.all(cacheNames.map(name => caches.delete(name))); console.log('✅ All caches cleared'); // Unregister service worker const reg = await navigator.serviceWorker.getRegistration(); if (reg) { await reg.unregister(); console.log('✅ Service worker unregistered'); } console.log('🔄 Reload page now'); }; clearAllCaches(); ``` Then reload the page (hard refresh: Ctrl+Shift+R). ### Fix 3: Update Service Worker ```javascript // In browser console const updateSW = async () => { const reg = await navigator.serviceWorker.getRegistration(); if (reg) { await reg.update(); console.log('✅ Service worker updated'); } }; updateSW(); ``` ### Fix 4: Check Container Environment Variables ```bash # Check if CORS is set docker compose exec soundwave env | grep CORS # Should show: # CORS_ALLOWED_ORIGINS=http://localhost:8889,https://localhost:8889,https://sound.iulian.uk ``` If not shown, environment variable not set in docker-compose.yml. ## Known Issues & Solutions ### Issue: "Failed to cache playlist" after 60 seconds **Cause**: Timeout - too many files or slow network **Solution**: 1. Cache smaller playlists first (< 10 tracks) 2. Check network speed 3. Increase timeout in `frontend/src/utils/pwa.ts` (line ~325) ### Issue: CORS error when fetching audio **Cause**: Backend not configured for HTTPS origin **Solution**: Already fixed in `docker-compose.yml` and `settings.py` ### Issue: Service worker not updating **Cause**: Browser caching old service worker **Solution**: 1. DevTools → Application → Service Workers 2. Check "Update on reload" 3. Hard refresh (Ctrl+Shift+R) ### Issue: Audio plays online but not offline **Cause**: Audio not in cache or wrong cache key **Solution**: 1. Check Cache Storage (Step 6) 2. Verify cache keys match request URLs exactly 3. Re-cache the playlist ## Testing Checklist Before reporting the issue is fixed: - [ ] Service worker registered and active - [ ] CORS headers present on audio downloads - [ ] Can cache a small playlist (3-5 tracks) - [ ] Cache Storage shows audio files - [ ] Offline mode: can play cached tracks - [ ] Network tab shows "ServiceWorker" as source - [ ] No console errors during playback ## Advanced: Check Django CORS Settings ```bash # SSH into container docker compose exec soundwave bash # Open Django shell python manage.py shell # Check CORS settings from django.conf import settings print("CORS_ALLOWED_ORIGINS:", settings.CORS_ALLOWED_ORIGINS) print("CORS_ALLOW_CREDENTIALS:", settings.CORS_ALLOW_CREDENTIALS) print("CSRF_TRUSTED_ORIGINS:", settings.CSRF_TRUSTED_ORIGINS) ``` Expected output: ```python CORS_ALLOWED_ORIGINS: ['http://localhost:8889', 'https://localhost:8889', 'https://sound.iulian.uk'] CORS_ALLOW_CREDENTIALS: True CSRF_TRUSTED_ORIGINS: ['http://localhost:8889', 'https://localhost:8889', 'https://sound.iulian.uk'] ``` ## Need More Help? If still not working after following this guide: 1. Share output from **Step 1** (SW status) 2. Share output from **Step 4** (audio fetch test) 3. Share screenshot of **Step 5** (SW console errors) 4. Share screenshot of **Step 6** (Cache Storage) This will help identify the exact issue.