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:
parent
446125cc76
commit
0be38ca945
3 changed files with 381 additions and 14 deletions
358
DEBUG_PWA_OFFLINE.md
Normal file
358
DEBUG_PWA_OFFLINE.md
Normal 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.
|
||||
|
|
@ -146,6 +146,10 @@ REST_FRAMEWORK = {
|
|||
}
|
||||
|
||||
# CORS settings
|
||||
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",
|
||||
|
|
@ -154,6 +158,10 @@ CORS_ALLOWED_ORIGINS = [
|
|||
CORS_ALLOW_CREDENTIALS = True
|
||||
|
||||
# CSRF settings for development cross-origin access
|
||||
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",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ 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: .
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue