Add Web Push (VAPID) notification framework#39
Open
radiantnode wants to merge 1 commit into
Open
Conversation
Stand up the framework for browser push notifications, end to end: a signed-in account can subscribe and a notification can be sent to any account by UUID or username from a script. No game-event triggers yet — those are an additive next step. Backend: - server/push.py: the single send path (send_to_user) + subscription storage; pywebpush encryption runs in a thread, dead subs (404/410) are pruned, failures are metered and never raised. - server/push_routes.py: GET /push/vapid-public-key, JWT-authed POST /push/subscribe + /push/unsubscribe (shared require_user dep added to server/auth.py). - migrations/008_push_subscriptions.sql: one row per device, keyed to users.id (ON DELETE CASCADE). - config flags (PUSH_ENABLED + VAPID_*), Prometheus counters, and the /sw.js root-scope route wired in main.py. Frontend: - static/sw.js: service worker (push + notificationclick). - static/js/push.js: opt-in (permission -> subscribe -> save); fired best-effort on load, no-ops unless signed in and push is enabled. Tooling/docs: - scripts/gen_vapid.py (self-validating key generation), scripts/send_push.py (send to a user), docs/PUSH.md, compose + env wiring (off by default). Verified against the docker-compose stack: migration applies, /sw.js registers at root scope, the subscribe endpoint stores a row, send_to_user encrypts + POSTs to the push service (delivered=1) and prunes on 410, and the CLI resolves username->UUID with correct exit codes. App loads clean at mobile viewport (390x844) with the push module imported. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177rY2Jfvn9po32avwRvfFB
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stands up the framework for browser push notifications, end to end. A signed-in account can subscribe, and a notification can be sent to any account — by
users.idUUID or username — from a script. No game-event triggers yet; those are an additive next step.Transport is standards-based Web Push (VAPID) — self-hosted, no third party. Everything is off by default (
PUSH_ENABLEDunset → the key endpoint 503s and the client opt-in quietly no-ops).The headline deliverable:
What's included
Backend
server/push.py— the single send path (send_to_user) + subscription storage.pywebpushencryption runs in a thread (never on a gameplay hot path), dead subs (404/410) are pruned, failures are metered and never raised.server/push_routes.py—GET /push/vapid-public-key, JWT-authedPOST /push/subscribe+/push/unsubscribe. Sharedrequire_userJWT dependency added toserver/auth.py.migrations/008_push_subscriptions.sql— one row per browser/device, keyed tousers.id(ON DELETE CASCADE).PUSH_ENABLED,VAPID_PUBLIC_KEY,VAPID_PRIVATE_KEY,VAPID_SUBJECT), Prometheus counters (tensies_push_sent_total,…_failed_total,…_pruned_total), and a root-scope/sw.jsroute inmain.py.Frontend (minimal opt-in, no UI button)
static/sw.js— service worker:push+notificationclickhandlers.static/js/push.js— opt-in (permission → subscribe → save); fired best-effort on load, no-ops unless signed in and push is enabled server-side.Tooling / docs
scripts/gen_vapid.py— self-validating VAPID key generation.docs/PUSH.md, plus compose +.envexample wiring.Verification (docker-compose stack)
/sw.jsregisters at root scope in a real browser at mobile viewport (390×844)./push/subscribestores a row (401 without JWT);send_to_userproduced a 165-byte encrypted POST to a mock push service (delivered=1) and pruned the subscription on a 410.push.jsis type-clean (0tscerrors); app boots with no real console errors and the opt-in correctly no-ops when signed out.Notes
requirements.lock(pinnedpywebpush,py-vapid,http-ece). In the sandbox a cleandocker buildfails only becausehttp-eceis sdist-only and pip's build isolation can't fetchsetuptoolsthrough the TLS-intercepting proxy — an environment quirk, not a prod issue. Pinned versions resolve and were installed into the running container to complete testing.docs/PUSH.md).🤖 Generated with Claude Code
Generated by Claude Code