Phase 4: Web Push notifications (pywebpush + VAPID)

Backend: PushSubscription model/migration, VAPID config + `cli.py
generate-vapid-keys`, push_service.send_push_to_user (upsert-by-endpoint
subscribe/unsubscribe, auto-cleanup of expired 404/410 subscriptions),
/api/push/* router, and ConnectionManager now tracks connected user IDs
per room so chat.py can push only to offline members after broadcasting
to online ones.

Two test-infra bugs found and fixed along the way: send_push_to_user
takes the caller's AsyncSession and is awaited inline rather than fired
via asyncio.create_task with its own session (background tasks were
outliving the test event loop); and the ws_client fixture now uses
NullPool to eliminate a connection-pool checkout race that was failing
WS tests intermittently.

Frontend: service worker rebuilt with vite-plugin-pwa's injectManifest
strategy (custom src/sw.ts) so it can add push/notificationclick
handlers alongside the existing precaching and StaleWhileRevalidate
routes ported over from generateSW. New subscribe/unsubscribe flow
(lib/push.ts, api/push.ts) with a toggle in the account menu.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 06:53:07 -06:00
co-authored by Claude Sonnet 5
parent aeb2f3f6a5
commit d09bf4a30a
27 changed files with 876 additions and 95 deletions
+2 -1
View File
@@ -2,7 +2,7 @@ from fastapi import FastAPI
from starlette.middleware.sessions import SessionMiddleware
from app.config import settings
from app.routers import auth, health, invites, rooms
from app.routers import auth, health, invites, push, rooms
from app.ws.chat import router as ws_router
from app.ws.connection_manager import ConnectionManager
@@ -24,6 +24,7 @@ def create_app() -> FastAPI:
app.include_router(auth.router)
app.include_router(rooms.router)
app.include_router(invites.router)
app.include_router(push.router)
app.include_router(ws_router)
return app