""" Push-varsler til telefonens OS (Web Push/VAPID), FEATURE_BACKLOG.md "Varsler: push til telefon". Bevisst helt frittstående fra app/realtime.py (WebSocket-sanntid for åpne faner) -- push når appen ikke er åpen i det hele tatt, helt andre leveringsmekanisme og bibliotek. Valgfritt (PUSH_CONFIGURED, app/config.py) -- ingen nøkler satt betyr push rett og slett ikke forsøkes, resten av varslingssystemet (in-app + e-post) fungerer helt uendret. Samme grasiøs-degraderings-/feil- isolasjons-mønster som app/email.py: en driftsfeil i selve utsendingen lekker ALDRI til kalleren (create_notification skal alltid lykkes med å skrive selve in-app-varselet, uansett hva som skjer her). """ from __future__ import annotations import asyncio import json import traceback from pywebpush import WebPushException, webpush from .config import settings def _send_sync(subscription_info: dict, payload: dict) -> None: webpush( subscription_info=subscription_info, data=json.dumps(payload), vapid_private_key=settings.VAPID_PRIVATE_KEY, vapid_claims={"sub": settings.VAPID_SUBJECT}, ) async def send_push_to_user(conn, user_id: str, title: str, body: str, link_path: str) -> None: """Sender til ALLE enheter brukeren har abonnert (flere nettlesere/ telefoner er normalt). Et utløpt/tilbakekalt abonnement (404/410 fra push-tjenesten) ryddes bort her -- ikke behandlet som en feil, bare forventet livssyklus for et push-abonnement.""" if not settings.PUSH_CONFIGURED: return rows = await conn.fetch( "SELECT id::text AS id, endpoint, p256dh, auth FROM push_subscription WHERE user_id = $1", user_id, ) if not rows: return payload = {"title": title, "body": body, "link_path": link_path} for row in rows: subscription_info = { "endpoint": row["endpoint"], "keys": {"p256dh": row["p256dh"], "auth": row["auth"]}, } try: await asyncio.to_thread(_send_sync, subscription_info, payload) except WebPushException as exc: status = exc.response.status_code if exc.response is not None else None if status in (404, 410): await conn.execute("DELETE FROM push_subscription WHERE id = $1", row["id"]) else: traceback.print_exc() except Exception: traceback.print_exc()