Spaces:
Running
Running
Add PWA support to BattleWords
Browse filesIntegrated PWA support by adding a Bash script to inject meta tags into Streamlit's `index.html` during Docker build. Created documentation and guides for PWA functionality and installation. Added necessary assets including icons, manifest, and service worker for offline capabilities and improved performance.
- LOCALHOST_PWA_README.md +267 -0
- PWA_INSTALL_GUIDE.mdx +208 -0
- inject-pwa-head.sh +49 -0
- pwa-head-inject.html +8 -0
- static/icon-192.png +0 -0
- static/icon-512.png +0 -0
- static/manifest.json +27 -0
- static/service-worker.js +99 -0
LOCALHOST_PWA_README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# PWA on Localhost - Important Information
|
| 2 |
+
|
| 3 |
+
## Summary
|
| 4 |
+
|
| 5 |
+
**The PWA files were created successfully**, but they **won't work fully on `localhost:8501`** due to Streamlit's static file serving limitations.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## What You're Seeing (or Not Seeing)
|
| 10 |
+
|
| 11 |
+
### ✅ What DOES Work on Localhost:
|
| 12 |
+
|
| 13 |
+
1. **Game functionality**: Everything works normally
|
| 14 |
+
2. **Challenge Mode**: Loading `?game_id=...` works (if HF credentials configured)
|
| 15 |
+
3. **PWA meta tags**: Injected into HTML (check page source)
|
| 16 |
+
4. **Service worker registration attempt**: Runs in browser console
|
| 17 |
+
|
| 18 |
+
### ❌ What DOESN'T Work on Localhost:
|
| 19 |
+
|
| 20 |
+
1. **`manifest.json` not accessible**:
|
| 21 |
+
```
|
| 22 |
+
http://localhost:8501/app/static/manifest.json
|
| 23 |
+
→ Returns HTML instead of JSON (Streamlit doesn't serve /app/static/)
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
2. **Icons not accessible**:
|
| 27 |
+
```
|
| 28 |
+
http://localhost:8501/app/static/icon-192.png
|
| 29 |
+
→ Returns 404 or HTML
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
3. **Service worker fails to register**:
|
| 33 |
+
```javascript
|
| 34 |
+
// Browser console shows:
|
| 35 |
+
Failed to register service worker: 404 Not Found
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
4. **No PWA install prompt**:
|
| 39 |
+
- No banner at bottom of screen
|
| 40 |
+
- No install icon in address bar
|
| 41 |
+
- PWA features disabled
|
| 42 |
+
|
| 43 |
+
---
|
| 44 |
+
|
| 45 |
+
## Why This Happens
|
| 46 |
+
|
| 47 |
+
**Streamlit's Static File Serving:**
|
| 48 |
+
|
| 49 |
+
- Streamlit only serves files from:
|
| 50 |
+
- `/.streamlit/static/` (internal Streamlit assets)
|
| 51 |
+
- Component assets via `declare_component()`
|
| 52 |
+
- NOT from arbitrary `battlewords/static/` directories
|
| 53 |
+
|
| 54 |
+
- On HuggingFace Spaces:
|
| 55 |
+
- `/app/static/` is mapped by HF infrastructure
|
| 56 |
+
- Files in `battlewords/static/` are accessible at `/app/static/`
|
| 57 |
+
- ✅ PWA works perfectly
|
| 58 |
+
|
| 59 |
+
- On localhost:
|
| 60 |
+
- No `/app/static/` mapping exists
|
| 61 |
+
- Streamlit returns HTML for all unrecognized paths
|
| 62 |
+
- ❌ PWA files return 404
|
| 63 |
+
|
| 64 |
+
---
|
| 65 |
+
|
| 66 |
+
## How to Test PWA Locally
|
| 67 |
+
|
| 68 |
+
### Option 1: Use ngrok (HTTPS Tunnel) ⭐ **RECOMMENDED**
|
| 69 |
+
|
| 70 |
+
This is the **best way** to test PWA locally with full functionality:
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
# Terminal 1: Run Streamlit
|
| 74 |
+
streamlit run app.py
|
| 75 |
+
|
| 76 |
+
# Terminal 2: Expose with HTTPS
|
| 77 |
+
ngrok http 8501
|
| 78 |
+
|
| 79 |
+
# Output shows:
|
| 80 |
+
# Forwarding https://abc123.ngrok-free.app -> http://localhost:8501
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
**Then visit the HTTPS URL on your phone or desktop:**
|
| 84 |
+
- ✅ Full PWA functionality
|
| 85 |
+
- ✅ Install prompt appears
|
| 86 |
+
- ✅ manifest.json loads
|
| 87 |
+
- ✅ Service worker registers
|
| 88 |
+
- ✅ Icons display correctly
|
| 89 |
+
|
| 90 |
+
**ngrok Setup:**
|
| 91 |
+
1. Download: https://ngrok.com/download
|
| 92 |
+
2. Sign up for free account
|
| 93 |
+
3. Install: `unzip /path/to/ngrok.zip` (or chocolatey on Windows: `choco install ngrok`)
|
| 94 |
+
4. Authenticate: `ngrok config add-authtoken <your-token>`
|
| 95 |
+
5. Run: `ngrok http 8501`
|
| 96 |
+
|
| 97 |
+
---
|
| 98 |
+
|
| 99 |
+
### Option 2: Deploy to HuggingFace Spaces ⭐ **PRODUCTION**
|
| 100 |
+
|
| 101 |
+
PWA works out-of-the-box on HF Spaces:
|
| 102 |
+
|
| 103 |
+
```bash
|
| 104 |
+
git add battlewords/static/ battlewords/ui.py
|
| 105 |
+
git commit -m "Add PWA support"
|
| 106 |
+
git push
|
| 107 |
+
|
| 108 |
+
# HF Spaces auto-deploys
|
| 109 |
+
# Visit: https://surn-battlewords.hf.space
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
**Then test PWA:**
|
| 113 |
+
- Android Chrome: "Add to Home Screen" prompt appears
|
| 114 |
+
- iOS Safari: Share → "Add to Home Screen"
|
| 115 |
+
- Desktop Chrome: Install icon in address bar
|
| 116 |
+
|
| 117 |
+
✅ **This is where PWA is meant to work!**
|
| 118 |
+
|
| 119 |
+
---
|
| 120 |
+
|
| 121 |
+
###Option 3: Manual Static File Server (Advanced)
|
| 122 |
+
|
| 123 |
+
You can serve the static files separately:
|
| 124 |
+
|
| 125 |
+
```bash
|
| 126 |
+
# Terminal 1: Run Streamlit
|
| 127 |
+
streamlit run app.py
|
| 128 |
+
|
| 129 |
+
# Terminal 2: Serve static files
|
| 130 |
+
cd battlewords/static
|
| 131 |
+
python3 -m http.server 8502
|
| 132 |
+
|
| 133 |
+
# Then access:
|
| 134 |
+
# Streamlit: http://localhost:8501
|
| 135 |
+
# Static files: http://localhost:8502/manifest.json
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
**Then modify the PWA paths in `ui.py`:**
|
| 139 |
+
```python
|
| 140 |
+
pwa_meta_tags = """
|
| 141 |
+
<link rel="manifest" href="http://localhost:8502/manifest.json">
|
| 142 |
+
<link rel="apple-touch-icon" href="http://localhost:8502/icon-192.png">
|
| 143 |
+
<!-- etc -->
|
| 144 |
+
"""
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
❌ **Not recommended**: Too complex, defeats the purpose
|
| 148 |
+
|
| 149 |
+
---
|
| 150 |
+
|
| 151 |
+
## What About Challenge Mode?
|
| 152 |
+
|
| 153 |
+
**Question:** "I loaded `localhost:8501/?game_id=hDjsB_dl` but don't see anything"
|
| 154 |
+
|
| 155 |
+
**Answer:** Challenge Mode is **separate from PWA**. You should see a blue banner at the top if:
|
| 156 |
+
|
| 157 |
+
### ✅ Requirements for Challenge Mode to Work:
|
| 158 |
+
|
| 159 |
+
1. **Environment variables configured** (`.env` file):
|
| 160 |
+
```bash
|
| 161 |
+
HF_API_TOKEN=hf_xxxxxxxxxxxxx
|
| 162 |
+
HF_REPO_ID=Surn/Storage
|
| 163 |
+
SPACE_NAME=Surn/BattleWords
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
2. **Valid game_id exists** in the HF repo:
|
| 167 |
+
- `hDjsB_dl` must be a real challenge created previously
|
| 168 |
+
- Check HuggingFace dataset repo: https://huggingface.co/datasets/Surn/Storage
|
| 169 |
+
- Look for: `games/<uid>/settings.json`
|
| 170 |
+
- Verify `shortener.json` has entry for `hDjsB_dl`
|
| 171 |
+
|
| 172 |
+
3. **Internet connection** (to fetch challenge data)
|
| 173 |
+
|
| 174 |
+
### If Challenge Mode ISN'T Working:
|
| 175 |
+
|
| 176 |
+
**Check browser console (F12 → Console):**
|
| 177 |
+
```javascript
|
| 178 |
+
// Look for errors:
|
| 179 |
+
"[game_storage] Could not resolve sid: hDjsB_dl" ← Challenge not found
|
| 180 |
+
"Failed to load game from sid" ← HF API error
|
| 181 |
+
"HF_API_TOKEN not configured" ← Missing credentials
|
| 182 |
+
```
|
| 183 |
+
|
| 184 |
+
**If you see errors:**
|
| 185 |
+
1. Verify `.env` file exists with correct variables
|
| 186 |
+
2. Restart Streamlit (`Ctrl+C` and `streamlit run app.py` again)
|
| 187 |
+
3. Try a different `game_id` from a known challenge
|
| 188 |
+
4. Check HF repo has the challenge data
|
| 189 |
+
|
| 190 |
+
---
|
| 191 |
+
|
| 192 |
+
## Summary Table
|
| 193 |
+
|
| 194 |
+
| Feature | Localhost | Localhost + ngrok | HF Spaces (Production) |
|
| 195 |
+
|---------|-----------|-------------------|------------------------|
|
| 196 |
+
| **Game works** | ✅ | ✅ | ✅ |
|
| 197 |
+
| **Challenge Mode** | ✅ (if .env configured) | ✅ | ✅ |
|
| 198 |
+
| **PWA manifest loads** | ❌ | ✅ | ✅ |
|
| 199 |
+
| **Service worker registers** | ❌ | ✅ | ✅ |
|
| 200 |
+
| **Install prompt** | ❌ | ✅ | ✅ |
|
| 201 |
+
| **Icons display** | ❌ | ✅ | ✅ |
|
| 202 |
+
| **Full-screen mode** | ❌ | ✅ | ✅ |
|
| 203 |
+
|
| 204 |
+
---
|
| 205 |
+
|
| 206 |
+
## What You Should Do
|
| 207 |
+
|
| 208 |
+
### For Development:
|
| 209 |
+
✅ **Just develop normally on localhost**
|
| 210 |
+
- Game features work fine
|
| 211 |
+
- Challenge Mode works (if .env configured)
|
| 212 |
+
- PWA features won't work, but that's okay
|
| 213 |
+
- Test PWA when you deploy
|
| 214 |
+
|
| 215 |
+
### For PWA Testing:
|
| 216 |
+
✅ **Use ngrok for quick local PWA testing**
|
| 217 |
+
- 5 minutes to setup
|
| 218 |
+
- Full PWA functionality
|
| 219 |
+
- Test on real phone
|
| 220 |
+
|
| 221 |
+
### For Production:
|
| 222 |
+
✅ **Deploy to HuggingFace Spaces**
|
| 223 |
+
- PWA works automatically
|
| 224 |
+
- No configuration needed
|
| 225 |
+
- `/app/static/` path works out-of-the-box
|
| 226 |
+
|
| 227 |
+
---
|
| 228 |
+
|
| 229 |
+
## Bottom Line
|
| 230 |
+
|
| 231 |
+
**Your question:** "Should I see something at the bottom of the screen?"
|
| 232 |
+
|
| 233 |
+
**Answer:**
|
| 234 |
+
|
| 235 |
+
1. **PWA install prompt**: ❌ Not on `localhost:8501` (Streamlit limitation)
|
| 236 |
+
- **Will work** on HF Spaces production deployment ✅
|
| 237 |
+
- **Will work** with ngrok HTTPS tunnel ✅
|
| 238 |
+
|
| 239 |
+
2. **Challenge Mode banner**: ✅ Should appear at TOP (not bottom)
|
| 240 |
+
- Check if `?game_id=hDjsB_dl` exists in your HF repo
|
| 241 |
+
- Check browser console for errors
|
| 242 |
+
- Verify `.env` has `HF_API_TOKEN` configured
|
| 243 |
+
|
| 244 |
+
The PWA implementation is **correct** and **ready for production**. It just won't work on bare localhost due to Streamlit's static file serving limitations. Once you deploy to HuggingFace Spaces, everything will work perfectly!
|
| 245 |
+
|
| 246 |
+
---
|
| 247 |
+
|
| 248 |
+
## Quick Test Command
|
| 249 |
+
|
| 250 |
+
```bash
|
| 251 |
+
# Check if .env is configured:
|
| 252 |
+
cat .env | grep HF_
|
| 253 |
+
|
| 254 |
+
# Should show:
|
| 255 |
+
# HF_API_TOKEN=hf_xxxxx
|
| 256 |
+
# HF_REPO_ID=Surn/Storage
|
| 257 |
+
# SPACE_NAME=Surn/BattleWords
|
| 258 |
+
|
| 259 |
+
# If missing, Challenge Mode won't work locally
|
| 260 |
+
```
|
| 261 |
+
|
| 262 |
+
---
|
| 263 |
+
|
| 264 |
+
**Next Steps:**
|
| 265 |
+
1. Test game functionality on localhost ✅
|
| 266 |
+
2. Deploy to HF Spaces for PWA testing ✅
|
| 267 |
+
3. Or install ngrok for local PWA testing ✅
|
PWA_INSTALL_GUIDE.mdx
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# BattleWords PWA Installation Guide
|
| 2 |
+
|
| 3 |
+
BattleWords can now be installed as a Progressive Web App (PWA) on your mobile device or desktop, giving you a native app experience directly from your browser!
|
| 4 |
+
|
| 5 |
+
## What is a PWA?
|
| 6 |
+
|
| 7 |
+
A Progressive Web App allows you to:
|
| 8 |
+
- ✅ Install BattleWords on your home screen (Android/iOS)
|
| 9 |
+
- ✅ Run in full-screen mode without browser UI
|
| 10 |
+
- ✅ Access the app quickly from your app drawer
|
| 11 |
+
- ✅ Get automatic updates (always the latest version)
|
| 12 |
+
- ✅ Basic offline functionality (cached assets)
|
| 13 |
+
|
| 14 |
+
## Installation Instructions
|
| 15 |
+
|
| 16 |
+
### Android (Chrome, Edge, Samsung Internet)
|
| 17 |
+
|
| 18 |
+
1. **Visit the app**: Open https://surn-battlewords.hf.space in Chrome
|
| 19 |
+
2. **Look for the install prompt**: A banner will appear at the bottom saying "Add BattleWords to Home screen"
|
| 20 |
+
3. **Tap "Add"** or **"Install"**
|
| 21 |
+
4. **Alternative method** (if no prompt):
|
| 22 |
+
- Tap the **three-dot menu** (⋮) in the top-right
|
| 23 |
+
- Select **"Install app"** or **"Add to Home screen"**
|
| 24 |
+
- Tap **"Install"**
|
| 25 |
+
5. **Launch**: Find the BattleWords icon on your home screen and tap to open!
|
| 26 |
+
|
| 27 |
+
**Result**: The app opens full-screen without the browser address bar, just like a native app.
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
### iOS (Safari)
|
| 32 |
+
|
| 33 |
+
**Note**: iOS requires using Safari browser (Chrome/Firefox won't work for PWA installation)
|
| 34 |
+
|
| 35 |
+
1. **Visit the app**: Open https://surn-battlewords.hf.space in Safari
|
| 36 |
+
2. **Tap the Share button**: The square with an arrow pointing up (at the bottom of the screen)
|
| 37 |
+
3. **Scroll down** and tap **"Add to Home Screen"**
|
| 38 |
+
4. **Edit the name** (optional): You can rename it from "BattleWords" if desired
|
| 39 |
+
5. **Tap "Add"** in the top-right corner
|
| 40 |
+
6. **Launch**: Find the BattleWords icon on your home screen and tap to open!
|
| 41 |
+
|
| 42 |
+
**Result**: The app opens in standalone mode, similar to a native iOS app.
|
| 43 |
+
|
| 44 |
+
---
|
| 45 |
+
|
| 46 |
+
### Desktop (Chrome, Edge, Brave)
|
| 47 |
+
|
| 48 |
+
1. **Visit the app**: Open https://surn-battlewords.hf.space
|
| 49 |
+
2. **Look for the install icon**:
|
| 50 |
+
- Chrome/Edge: Click the **install icon** (⊕) in the address bar
|
| 51 |
+
- Or click the **three-dot menu** → **"Install BattleWords"**
|
| 52 |
+
3. **Click "Install"** in the confirmation dialog
|
| 53 |
+
4. **Launch**:
|
| 54 |
+
- Windows: Find BattleWords in Start Menu or Desktop
|
| 55 |
+
- Mac: Find BattleWords in Applications folder
|
| 56 |
+
- Linux: Find in application launcher
|
| 57 |
+
|
| 58 |
+
**Result**: BattleWords opens in its own window, separate from your browser.
|
| 59 |
+
|
| 60 |
+
---
|
| 61 |
+
|
| 62 |
+
## Features of the PWA
|
| 63 |
+
|
| 64 |
+
### Works Immediately ✅
|
| 65 |
+
- Full game functionality (reveal cells, guess words, scoring)
|
| 66 |
+
- Challenge Mode (create and play shared challenges)
|
| 67 |
+
- Sound effects and background music
|
| 68 |
+
- Ocean-themed animated background
|
| 69 |
+
- All current features preserved
|
| 70 |
+
|
| 71 |
+
### Offline Support 🌐
|
| 72 |
+
- App shell cached for faster loading
|
| 73 |
+
- Icons and static assets available offline
|
| 74 |
+
- **Note**: Challenge Mode requires internet connection (needs to fetch/save from HuggingFace)
|
| 75 |
+
|
| 76 |
+
### Updates 🔄
|
| 77 |
+
- Automatic updates when you open the app
|
| 78 |
+
- Always get the latest features and bug fixes
|
| 79 |
+
- No manual update process needed
|
| 80 |
+
|
| 81 |
+
### Privacy & Security 🔒
|
| 82 |
+
- No new data collection (same as web version)
|
| 83 |
+
- Environment variables stay on server (never exposed to PWA)
|
| 84 |
+
- Service worker only caches public assets
|
| 85 |
+
- All game data in Challenge Mode handled server-side
|
| 86 |
+
|
| 87 |
+
---
|
| 88 |
+
|
| 89 |
+
## Uninstalling the PWA
|
| 90 |
+
|
| 91 |
+
### Android
|
| 92 |
+
1. Long-press the BattleWords icon
|
| 93 |
+
2. Tap "Uninstall" or drag to "Remove"
|
| 94 |
+
|
| 95 |
+
### iOS
|
| 96 |
+
1. Long-press the BattleWords icon
|
| 97 |
+
2. Tap "Remove App"
|
| 98 |
+
3. Confirm "Delete App"
|
| 99 |
+
|
| 100 |
+
### Desktop
|
| 101 |
+
- **Chrome/Edge**: Go to `chrome://apps` or `edge://apps`, right-click BattleWords, select "Uninstall"
|
| 102 |
+
- **Windows**: Settings → Apps → BattleWords → Uninstall
|
| 103 |
+
- **Mac**: Delete from Applications folder
|
| 104 |
+
|
| 105 |
+
---
|
| 106 |
+
|
| 107 |
+
## Troubleshooting
|
| 108 |
+
|
| 109 |
+
### "Install" option doesn't appear
|
| 110 |
+
- **Android**: Make sure you're using Chrome, Edge, or Samsung Internet (not Firefox)
|
| 111 |
+
- **iOS**: Must use Safari browser
|
| 112 |
+
- **Desktop**: Check if you're using a supported browser (Chrome, Edge, Brave)
|
| 113 |
+
- Try refreshing the page (the install prompt may take a moment to appear)
|
| 114 |
+
|
| 115 |
+
### App won't open after installation
|
| 116 |
+
- Try uninstalling and reinstalling
|
| 117 |
+
- Clear browser cache and try again
|
| 118 |
+
- Make sure you have internet connection for first launch
|
| 119 |
+
|
| 120 |
+
### Service worker errors in console
|
| 121 |
+
- This is normal during development
|
| 122 |
+
- The app will still function without the service worker
|
| 123 |
+
- Full offline support requires the service worker to register successfully
|
| 124 |
+
|
| 125 |
+
### Icons don't show up correctly
|
| 126 |
+
- Wait a moment after installation (icons may take time to download)
|
| 127 |
+
- Try force-refreshing the PWA (close and reopen)
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
## Technical Details
|
| 132 |
+
|
| 133 |
+
### Files Added for PWA Support
|
| 134 |
+
|
| 135 |
+
```
|
| 136 |
+
battlewords/
|
| 137 |
+
├── static/
|
| 138 |
+
│ ├── manifest.json # PWA configuration
|
| 139 |
+
│ ├── service-worker.js # Offline caching logic
|
| 140 |
+
│ ├── icon-192.png # App icon (small)
|
| 141 |
+
│ └── icon-512.png # App icon (large)
|
| 142 |
+
└── ui.py # Added PWA meta tags
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
### What's Cached Offline
|
| 146 |
+
|
| 147 |
+
- App shell (HTML structure)
|
| 148 |
+
- Icons (192x192, 512x512)
|
| 149 |
+
- Manifest file
|
| 150 |
+
- Previous game states (if you were playing before going offline)
|
| 151 |
+
|
| 152 |
+
### What Requires Internet
|
| 153 |
+
|
| 154 |
+
- Creating new challenges
|
| 155 |
+
- Submitting results to leaderboards
|
| 156 |
+
- Loading shared challenges
|
| 157 |
+
- Downloading word lists (first time)
|
| 158 |
+
- Fetching game updates
|
| 159 |
+
|
| 160 |
+
---
|
| 161 |
+
|
| 162 |
+
## Comparison: PWA vs Native App
|
| 163 |
+
|
| 164 |
+
| Feature | PWA | Native App |
|
| 165 |
+
|---------|-----|------------|
|
| 166 |
+
| Installation | Quick (1 tap) | Slow (app store) |
|
| 167 |
+
| Size | ~5-10 MB | ~15-30 MB |
|
| 168 |
+
| Updates | Automatic | Manual |
|
| 169 |
+
| Platform support | Android, iOS, Desktop | Separate builds |
|
| 170 |
+
| Offline mode | Partial | Full |
|
| 171 |
+
| Performance | 90% of native | 100% |
|
| 172 |
+
| App store presence | No | Yes |
|
| 173 |
+
| Development time | 2-4 hours ✅ | 40-60 hours per platform |
|
| 174 |
+
|
| 175 |
+
---
|
| 176 |
+
|
| 177 |
+
## Feedback
|
| 178 |
+
|
| 179 |
+
If you encounter issues installing or using the PWA, please:
|
| 180 |
+
1. Check the browser console for errors (F12 → Console tab)
|
| 181 |
+
2. Report issues at: https://github.com/Oncorporation/BattleWords/issues
|
| 182 |
+
3. Include: Device type, OS version, browser version, and error messages
|
| 183 |
+
|
| 184 |
+
---
|
| 185 |
+
|
| 186 |
+
## For Developers
|
| 187 |
+
|
| 188 |
+
To regenerate the PWA icons:
|
| 189 |
+
```bash
|
| 190 |
+
python3 generate_pwa_icons.py
|
| 191 |
+
```
|
| 192 |
+
|
| 193 |
+
To modify PWA behavior:
|
| 194 |
+
- Edit `battlewords/static/manifest.json` (app metadata)
|
| 195 |
+
- Edit `battlewords/static/service-worker.js` (caching logic)
|
| 196 |
+
- Edit `battlewords/ui.py` (PWA meta tags, lines 34-86)
|
| 197 |
+
|
| 198 |
+
To test PWA locally:
|
| 199 |
+
```bash
|
| 200 |
+
streamlit run app.py
|
| 201 |
+
# Open http://localhost:8501 in Chrome
|
| 202 |
+
# Chrome DevTools → Application → Manifest (verify manifest.json loads)
|
| 203 |
+
# Chrome DevTools → Application → Service Workers (verify registration)
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
---
|
| 207 |
+
|
| 208 |
+
**Enjoy BattleWords as a native-like app experience! 🎮🌊**
|
inject-pwa-head.sh
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Inject PWA meta tags into Streamlit's index.html head section
|
| 3 |
+
# This script modifies the Streamlit index.html during Docker build
|
| 4 |
+
|
| 5 |
+
set -e
|
| 6 |
+
|
| 7 |
+
echo "[PWA] Injecting PWA meta tags into Streamlit's index.html..."
|
| 8 |
+
|
| 9 |
+
# Find Streamlit's index.html
|
| 10 |
+
STREAMLIT_INDEX=$(python3 -c "import streamlit; import os; print(os.path.join(os.path.dirname(streamlit.__file__), 'static', 'index.html'))")
|
| 11 |
+
|
| 12 |
+
if [ ! -f "$STREAMLIT_INDEX" ]; then
|
| 13 |
+
echo "[PWA] ERROR: Streamlit index.html not found at: $STREAMLIT_INDEX"
|
| 14 |
+
exit 1
|
| 15 |
+
fi
|
| 16 |
+
|
| 17 |
+
echo "[PWA] Found Streamlit index.html at: $STREAMLIT_INDEX"
|
| 18 |
+
|
| 19 |
+
# Check if already injected (to make script idempotent)
|
| 20 |
+
if grep -q "PWA (Progressive Web App) Meta Tags" "$STREAMLIT_INDEX"; then
|
| 21 |
+
echo "[PWA] PWA tags already injected, skipping..."
|
| 22 |
+
exit 0
|
| 23 |
+
fi
|
| 24 |
+
|
| 25 |
+
# Read the injection content
|
| 26 |
+
INJECT_FILE="/app/pwa-head-inject.html"
|
| 27 |
+
if [ ! -f "$INJECT_FILE" ]; then
|
| 28 |
+
echo "[PWA] ERROR: Injection file not found at: $INJECT_FILE"
|
| 29 |
+
exit 1
|
| 30 |
+
fi
|
| 31 |
+
|
| 32 |
+
# Create backup
|
| 33 |
+
cp "$STREAMLIT_INDEX" "${STREAMLIT_INDEX}.backup"
|
| 34 |
+
|
| 35 |
+
# Use awk to inject after <head> tag
|
| 36 |
+
awk -v inject_file="$INJECT_FILE" '
|
| 37 |
+
/<head>/ {
|
| 38 |
+
print
|
| 39 |
+
while ((getline line < inject_file) > 0) {
|
| 40 |
+
print line
|
| 41 |
+
}
|
| 42 |
+
close(inject_file)
|
| 43 |
+
next
|
| 44 |
+
}
|
| 45 |
+
{ print }
|
| 46 |
+
' "${STREAMLIT_INDEX}.backup" > "$STREAMLIT_INDEX"
|
| 47 |
+
|
| 48 |
+
echo "[PWA] PWA meta tags successfully injected!"
|
| 49 |
+
echo "[PWA] Backup saved as: ${STREAMLIT_INDEX}.backup"
|
pwa-head-inject.html
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- PWA (Progressive Web App) Meta Tags -->
|
| 2 |
+
<link rel="manifest" href="/app/static/manifest.json">
|
| 3 |
+
<meta name="theme-color" content="#165ba8">
|
| 4 |
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
| 5 |
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
| 6 |
+
<meta name="apple-mobile-web-app-title" content="BattleWords">
|
| 7 |
+
<link rel="apple-touch-icon" href="/app/static/icon-192.png">
|
| 8 |
+
<meta name="mobile-web-app-capable" content="yes">
|
static/icon-192.png
ADDED
|
|
static/icon-512.png
ADDED
|
|
static/manifest.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "BattleWords",
|
| 3 |
+
"short_name": "BattleWords",
|
| 4 |
+
"description": "Vocabulary learning game inspired by Battleship mechanics. Discover hidden words on a 12x12 grid and earn points for strategic guessing.",
|
| 5 |
+
"start_url": "/",
|
| 6 |
+
"scope": "/",
|
| 7 |
+
"display": "standalone",
|
| 8 |
+
"orientation": "portrait",
|
| 9 |
+
"background_color": "#0b2a4a",
|
| 10 |
+
"theme_color": "#165ba8",
|
| 11 |
+
"icons": [
|
| 12 |
+
{
|
| 13 |
+
"src": "/app/static/icon-192.png",
|
| 14 |
+
"sizes": "192x192",
|
| 15 |
+
"type": "image/png",
|
| 16 |
+
"purpose": "any maskable"
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"src": "/app/static/icon-512.png",
|
| 20 |
+
"sizes": "512x512",
|
| 21 |
+
"type": "image/png",
|
| 22 |
+
"purpose": "any maskable"
|
| 23 |
+
}
|
| 24 |
+
],
|
| 25 |
+
"categories": ["games", "education"],
|
| 26 |
+
"screenshots": []
|
| 27 |
+
}
|
static/service-worker.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* BattleWords Service Worker
|
| 3 |
+
* Enables PWA functionality: offline caching, install prompt, etc.
|
| 4 |
+
*
|
| 5 |
+
* Security Note: This file contains no secrets or sensitive data.
|
| 6 |
+
* It only caches public assets for offline access.
|
| 7 |
+
*/
|
| 8 |
+
|
| 9 |
+
const CACHE_NAME = 'battlewords-v0.2.29';
|
| 10 |
+
const RUNTIME_CACHE = 'battlewords-runtime';
|
| 11 |
+
|
| 12 |
+
// Assets to cache on install (minimal for faster install)
|
| 13 |
+
const PRECACHE_URLS = [
|
| 14 |
+
'/',
|
| 15 |
+
'/app/static/manifest.json',
|
| 16 |
+
'/app/static/icon-192.png',
|
| 17 |
+
'/app/static/icon-512.png'
|
| 18 |
+
];
|
| 19 |
+
|
| 20 |
+
// Install event - cache essential files
|
| 21 |
+
self.addEventListener('install', event => {
|
| 22 |
+
console.log('[ServiceWorker] Installing...');
|
| 23 |
+
event.waitUntil(
|
| 24 |
+
caches.open(CACHE_NAME)
|
| 25 |
+
.then(cache => {
|
| 26 |
+
console.log('[ServiceWorker] Precaching app shell');
|
| 27 |
+
return cache.addAll(PRECACHE_URLS);
|
| 28 |
+
})
|
| 29 |
+
.then(() => self.skipWaiting()) // Activate immediately
|
| 30 |
+
);
|
| 31 |
+
});
|
| 32 |
+
|
| 33 |
+
// Activate event - clean up old caches
|
| 34 |
+
self.addEventListener('activate', event => {
|
| 35 |
+
console.log('[ServiceWorker] Activating...');
|
| 36 |
+
event.waitUntil(
|
| 37 |
+
caches.keys().then(cacheNames => {
|
| 38 |
+
return Promise.all(
|
| 39 |
+
cacheNames.map(cacheName => {
|
| 40 |
+
if (cacheName !== CACHE_NAME && cacheName !== RUNTIME_CACHE) {
|
| 41 |
+
console.log('[ServiceWorker] Deleting old cache:', cacheName);
|
| 42 |
+
return caches.delete(cacheName);
|
| 43 |
+
}
|
| 44 |
+
})
|
| 45 |
+
);
|
| 46 |
+
}).then(() => self.clients.claim()) // Take control immediately
|
| 47 |
+
);
|
| 48 |
+
});
|
| 49 |
+
|
| 50 |
+
// Fetch event - network first, fall back to cache
|
| 51 |
+
self.addEventListener('fetch', event => {
|
| 52 |
+
// Skip non-GET requests
|
| 53 |
+
if (event.request.method !== 'GET') {
|
| 54 |
+
return;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
// Skip chrome-extension and other non-http requests
|
| 58 |
+
if (!event.request.url.startsWith('http')) {
|
| 59 |
+
return;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
event.respondWith(
|
| 63 |
+
caches.open(RUNTIME_CACHE).then(cache => {
|
| 64 |
+
return fetch(event.request)
|
| 65 |
+
.then(response => {
|
| 66 |
+
// Cache successful responses for future offline access
|
| 67 |
+
if (response.status === 200) {
|
| 68 |
+
cache.put(event.request, response.clone());
|
| 69 |
+
}
|
| 70 |
+
return response;
|
| 71 |
+
})
|
| 72 |
+
.catch(() => {
|
| 73 |
+
// Network failed, try cache
|
| 74 |
+
return caches.match(event.request).then(cachedResponse => {
|
| 75 |
+
if (cachedResponse) {
|
| 76 |
+
console.log('[ServiceWorker] Serving from cache:', event.request.url);
|
| 77 |
+
return cachedResponse;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
// No cache available, return offline page or error
|
| 81 |
+
return new Response('Offline - Please check your connection', {
|
| 82 |
+
status: 503,
|
| 83 |
+
statusText: 'Service Unavailable',
|
| 84 |
+
headers: new Headers({
|
| 85 |
+
'Content-Type': 'text/plain'
|
| 86 |
+
})
|
| 87 |
+
});
|
| 88 |
+
});
|
| 89 |
+
});
|
| 90 |
+
})
|
| 91 |
+
);
|
| 92 |
+
});
|
| 93 |
+
|
| 94 |
+
// Message event - handle commands from the app
|
| 95 |
+
self.addEventListener('message', event => {
|
| 96 |
+
if (event.data.action === 'skipWaiting') {
|
| 97 |
+
self.skipWaiting();
|
| 98 |
+
}
|
| 99 |
+
});
|