64 lines
2.4 KiB
Python
64 lines
2.4 KiB
Python
|
|
"""
|
||
|
|
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()
|