Implement webpush from server

This commit is contained in:
Nicolas Mowen 2024-07-20 12:34:22 -06:00
parent 5398a661ff
commit f946690520
5 changed files with 54 additions and 13 deletions

View File

@ -39,3 +39,4 @@ ollama == 0.2.*
openai == 1.30.*
# push notifications
py-vapid == 1.9.*
pywebpush == 2.0.*

View File

@ -41,13 +41,13 @@ def get_vapid_pub_key():
def register_notifications():
username = request.headers.get("remote-user", type=str) or "admin"
json: dict[str, any] = request.get_json(silent=True) or {}
token = json["token"]
sub = json.get("sub")
if not token:
return jsonify({"success": False, "message": "Token must be provided."}), 400
if not sub:
return jsonify({"success": False, "message": "Subscription must be provided."}), 400
try:
User.update(notification_tokens=User.notification_tokens.append(token)).where(
User.update(notification_tokens=User.notification_tokens.append(sub)).where(
User.username == username
).execute()
return make_response(

View File

@ -1,11 +1,13 @@
"""Handle sending notifications for Frigate via Firebase."""
import datetime
import json
import logging
import os
from typing import Any, Callable
from py_vapid import Vapid01
from pywebpush import WebPusher
from frigate.comms.dispatcher import Communicator
from frigate.config import FrigateConfig
@ -20,17 +22,17 @@ class WebPushClient(Communicator): # type: ignore[misc]
def __init__(self, config: FrigateConfig) -> None:
self.config = config
self.claim = None
self.claim_headers = None
self.web_pushers: list[WebPusher] = []
# Pull keys from PEM or generate if they do not exist
self.key = Vapid01.from_file(os.path.join(CONFIG_DIR, "notifications.pem"))
self.tokens = []
self.invalid_tokens = []
self.vapid = Vapid01.from_file(os.path.join(CONFIG_DIR, "notifications.pem"))
users: list[User] = User.select(User.notification_tokens).dicts().iterator()
for user in users:
self.tokens.extend(user["notification_tokens"])
for sub in user["notification_tokens"]:
self.web_pushers.append(WebPusher(sub))
def subscribe(self, receiver: Callable) -> None:
"""Wrapper for allowing dispatcher to subscribe."""
@ -42,6 +44,20 @@ class WebPushClient(Communicator): # type: ignore[misc]
self.send_message(json.loads(payload))
def send_message(self, payload: dict[str, any]) -> None:
# check for valid claim or create new one
now = datetime.datetime.now().timestamp()
if self.claim is None or self.claim["exp"] < now:
# create new claim
self.claim = {
"sub": "mailto:test@example.com",
"aud": "https://fcm.googleapis.com",
"exp": (
datetime.datetime.now() + datetime.timedelta(hours=1)
).timestamp(),
}
self.claim_headers = self.vapid.sign(self.claim)
logger.info(f"Updated claim with new headers {self.claim_headers}")
# Only notify for alerts
if payload["after"]["severity"] != "alert":
return
@ -72,5 +88,17 @@ class WebPushClient(Communicator): # type: ignore[misc]
direct_url = f"{self.config.notifications.base_url}/review?id={reviewId}"
image = f'{self.config.notifications.base_url}{payload["after"]["thumb_path"].replace("/media/frigate", "")}'
for pusher in self.web_pushers:
pusher.send(
headers=self.claim_headers,
ttl=0,
data=json.dumps({
"title": title,
"message": message,
"direct_url": direct_url,
"image": image,
}),
)
def stop(self) -> None:
pass

6
package-lock.json generated Normal file
View File

@ -0,0 +1,6 @@
{
"name": "frigate",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

View File

@ -70,7 +70,10 @@ export default function NotificationView() {
// TODO make the notifications button show enable / disable depending on current state
}
<Button
disabled={notificationsSubscribed == undefined}
disabled={
notificationsSubscribed == undefined ||
publicKey == undefined
}
onClick={() => {
Notification.requestPermission().then((permission) => {
console.log("notification permissions are ", permission);
@ -79,7 +82,10 @@ export default function NotificationView() {
.register(NOTIFICATION_SERVICE_WORKER)
.then((registration) => {
registration.pushManager
.subscribe()
.subscribe({
userVisibleOnly: true,
applicationServerKey: publicKey,
})
.then((pushSubscription) => {
console.log(pushSubscription.endpoint);
axios.post("notifications/register", {