Fix CORS configuration for PWA offline caching

- Add CORS_ALLOWED_ORIGINS environment variable support in Django settings
- Update docker-compose.yml to include CORS origins (HTTP, HTTPS, custom domain)
- Add comprehensive PWA offline debugging guide
This commit is contained in:
Iulian 2025-12-24 01:20:41 +00:00
parent 446125cc76
commit 0be38ca945
3 changed files with 381 additions and 14 deletions

358
DEBUG_PWA_OFFLINE.md Normal file
View file

@ -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.

View file

@ -146,19 +146,27 @@ REST_FRAMEWORK = {
} }
# CORS settings # CORS settings
CORS_ALLOWED_ORIGINS = [ 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://localhost:8889",
"http://127.0.0.1:8889", "http://127.0.0.1:8889",
"http://192.168.50.71:8889", "http://192.168.50.71:8889",
] ]
CORS_ALLOW_CREDENTIALS = True CORS_ALLOW_CREDENTIALS = True
# CSRF settings for development cross-origin access # CSRF settings for development cross-origin access
CSRF_TRUSTED_ORIGINS = [ 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://localhost:8889",
"http://127.0.0.1:8889", "http://127.0.0.1:8889",
"http://192.168.50.71:8889", "http://192.168.50.71:8889",
] ]
CSRF_COOKIE_SAMESITE = 'Lax' CSRF_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SECURE = False CSRF_COOKIE_SECURE = False
SESSION_COOKIE_SAMESITE = 'Lax' SESSION_COOKIE_SAMESITE = 'Lax'

View file

@ -4,7 +4,7 @@ services:
soundwave: soundwave:
container_name: soundwave container_name: soundwave
# Option 1: Pull from GitHub Container Registry (recommended for users) # 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) # Option 2: Build locally (uncomment if pulling fails or for development)
build: build:
context: . context: .
@ -24,6 +24,7 @@ services:
- REDIS_HOST=soundwave-redis - REDIS_HOST=soundwave-redis
- TZ=UTC - TZ=UTC
- ES_URL=http://soundwave-es:9200 - ES_URL=http://soundwave-es:9200
- CORS_ALLOWED_ORIGINS=http://localhost:8889,https://localhost:8889,https://sound.iulian.uk
depends_on: depends_on:
- soundwave-es - soundwave-es
- soundwave-redis - soundwave-redis