diff --git a/DEBUG_PWA_OFFLINE.md b/DEBUG_PWA_OFFLINE.md new file mode 100644 index 0000000..5cf8bf9 --- /dev/null +++ b/DEBUG_PWA_OFFLINE.md @@ -0,0 +1,358 @@ +# 🐛 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. diff --git a/backend/config/settings.py b/backend/config/settings.py index ed7ff1a..a846869 100644 --- a/backend/config/settings.py +++ b/backend/config/settings.py @@ -146,19 +146,27 @@ REST_FRAMEWORK = { } # CORS settings -CORS_ALLOWED_ORIGINS = [ - "http://localhost:8889", - "http://127.0.0.1:8889", - "http://192.168.50.71:8889", -] +CORS_ALLOWED_ORIGINS_ENV = os.environ.get('CORS_ALLOWED_ORIGINS', '') +if CORS_ALLOWED_ORIGINS_ENV: + CORS_ALLOWED_ORIGINS = [origin.strip() for origin in CORS_ALLOWED_ORIGINS_ENV.split(',')] +else: + CORS_ALLOWED_ORIGINS = [ + "http://localhost:8889", + "http://127.0.0.1:8889", + "http://192.168.50.71:8889", + ] CORS_ALLOW_CREDENTIALS = True # CSRF settings for development cross-origin access -CSRF_TRUSTED_ORIGINS = [ - "http://localhost:8889", - "http://127.0.0.1:8889", - "http://192.168.50.71:8889", -] +CSRF_TRUSTED_ORIGINS_ENV = os.environ.get('CORS_ALLOWED_ORIGINS', '') +if CSRF_TRUSTED_ORIGINS_ENV: + CSRF_TRUSTED_ORIGINS = [origin.strip() for origin in CSRF_TRUSTED_ORIGINS_ENV.split(',')] +else: + CSRF_TRUSTED_ORIGINS = [ + "http://localhost:8889", + "http://127.0.0.1:8889", + "http://192.168.50.71:8889", + ] CSRF_COOKIE_SAMESITE = 'Lax' CSRF_COOKIE_SECURE = False SESSION_COOKIE_SAMESITE = 'Lax' diff --git a/docker-compose.yml b/docker-compose.yml index 9667de8..87b203b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,11 +4,11 @@ services: soundwave: container_name: soundwave # Option 1: Pull from GitHub Container Registry (recommended for users) - #image: ghcr.io/aiulian25/soundwave:latest + # image: ghcr.io/aiulian25/soundwave:main # Option 2: Build locally (uncomment if pulling fails or for development) - build: - context: . - dockerfile: Dockerfile + build: + context: . + dockerfile: Dockerfile ports: - "8889:8888" volumes: @@ -24,6 +24,7 @@ services: - REDIS_HOST=soundwave-redis - TZ=UTC - ES_URL=http://soundwave-es:9200 + - CORS_ALLOWED_ORIGINS=http://localhost:8889,https://localhost:8889,https://sound.iulian.uk depends_on: - soundwave-es - soundwave-redis