fix: add generation guard to refresh, preserve image on transient failure, document env vars

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-24 01:50:08 -04:00
parent 9ac236575c
commit 1cf2a855b5
2 changed files with 26 additions and 9 deletions

View File

@@ -137,6 +137,8 @@ The database will be automatically initialized and populated with games from `ga
DB_PATH=./data/jackbox.db
JWT_SECRET=your-secret-jwt-key
ADMIN_KEY=admin123
VOTE_APP_URL=https://vote-app.example.com
VOTE_APP_API_KEY=your-vote-app-api-key
```
4. **Start the backend server**
@@ -184,6 +186,13 @@ If `admins.json` is not found, the app falls back to the `ADMIN_KEY` environment
To use a custom path for the admins file, set `ADMIN_CONFIG_PATH` in your environment.
### Sticker Wall Preview
The backend proxies sticker wall images from the vote-app. Set these in `backend/.env`:
- `VOTE_APP_URL` — base URL of the vote-app (e.g. `https://vote-app.example.com`)
- `VOTE_APP_API_KEY` — API key for the sticker wall render endpoint
For Docker, uncomment the volume mount in `docker-compose.yml`:
```yaml
- ./backend/config/admins.json:/app/config/admins.json:ro

View File

@@ -6,6 +6,7 @@ export function useStickerWall(sessionId) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const urlRef = useRef(null);
const generationRef = useRef(0);
const revokeUrl = useCallback(() => {
if (urlRef.current) {
@@ -23,20 +24,28 @@ export function useStickerWall(sessionId) {
const refresh = useCallback(async () => {
if (!sessionId) return;
const generation = generationRef.current;
setLoading(true);
setError(false);
try {
const res = await api.post(`/stickerwalls/${sessionId}/render`, null, { responseType: 'blob' });
if (generation !== generationRef.current) return;
setBlobUrl(res.data);
} catch {
if (generation !== generationRef.current) return;
if (!urlRef.current) {
setError(true);
}
} finally {
if (generation === generationRef.current) {
setLoading(false);
}
}
}, [sessionId, setBlobUrl]);
useEffect(() => {
let stale = false;
generationRef.current += 1;
const generation = generationRef.current;
if (!sessionId) {
revokeUrl();
@@ -50,17 +59,17 @@ export function useStickerWall(sessionId) {
setError(false);
try {
const res = await api.get(`/stickerwalls/${id}`, { responseType: 'blob' });
if (stale) return;
if (generation !== generationRef.current) return;
setBlobUrl(res.data);
} catch (err) {
if (stale) return;
if (generation !== generationRef.current) return;
if (err.response?.status === 404) {
try {
const renderRes = await api.post(`/stickerwalls/${id}/render`, null, { responseType: 'blob' });
if (stale) return;
if (generation !== generationRef.current) return;
setBlobUrl(renderRes.data);
} catch {
if (stale) return;
if (generation !== generationRef.current) return;
revokeUrl();
setImageUrl(null);
setError(true);
@@ -71,7 +80,7 @@ export function useStickerWall(sessionId) {
setError(true);
}
} finally {
if (!stale) {
if (generation === generationRef.current) {
setLoading(false);
}
}
@@ -80,7 +89,6 @@ export function useStickerWall(sessionId) {
fetchWall(sessionId);
return () => {
stale = true;
revokeUrl();
};
}, [sessionId, setBlobUrl, revokeUrl]);