Implement VAPID key generation

This commit is contained in:
Nicolas Mowen 2024-07-20 09:14:38 -06:00
parent d40f177b77
commit afe2032085
4 changed files with 52 additions and 6 deletions

View File

@ -37,3 +37,5 @@ chromadb == 0.5.0
google-generativeai == 0.6.* google-generativeai == 0.6.*
ollama == 0.2.* ollama == 0.2.*
openai == 1.30.* openai == 1.30.*
# push notifications
py-vapid == 1.9.*

View File

@ -1,14 +1,19 @@
"""Notification apis.""" """Notification apis."""
import logging import logging
import os
from flask import ( from flask import (
Blueprint, Blueprint,
current_app,
jsonify, jsonify,
make_response,
request, request,
) )
from peewee import DoesNotExist from peewee import DoesNotExist
from py_vapid import Vapid01
from frigate.const import CONFIG_DIR
from frigate.models import User from frigate.models import User
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -16,6 +21,18 @@ logger = logging.getLogger(__name__)
NotificationBp = Blueprint("notifications", __name__) NotificationBp = Blueprint("notifications", __name__)
@NotificationBp.route("/notifications/pubkey", methods=["GET"])
def get_vapid_pub_key():
if not current_app.frigate_config.notifications.enabled:
return make_response(
jsonify({"success": False, "message": "Notifications are not enabled."}),
400,
)
key = Vapid01.from_file(os.path.join(CONFIG_DIR, "notifications.pem"))
return jsonify(key.public_key), 200
@NotificationBp.route("/notifications/register", methods=["POST"]) @NotificationBp.route("/notifications/register", methods=["POST"])
def register_notifications(): def register_notifications():
username = request.headers.get("remote-user", type=str) or "admin" username = request.headers.get("remote-user", type=str) or "admin"
@ -29,6 +46,10 @@ def register_notifications():
User.update(notification_tokens=User.notification_tokens.append(token)).where( User.update(notification_tokens=User.notification_tokens.append(token)).where(
User.username == username User.username == username
).execute() ).execute()
return jsonify({"success": True, "message": "Successfully saved token."}), 200 return make_response(
jsonify({"success": True, "message": "Successfully saved token."}), 200
)
except DoesNotExist: except DoesNotExist:
return jsonify({"success": False, "message": "Could not find user."}), 404 return make_response(
jsonify({"success": False, "message": "Could not find user."}), 404
)

View File

@ -2,10 +2,14 @@
import json import json
import logging import logging
import os
from typing import Any, Callable from typing import Any, Callable
from py_vapid import Vapid01
from frigate.comms.dispatcher import Communicator from frigate.comms.dispatcher import Communicator
from frigate.config import FrigateConfig from frigate.config import FrigateConfig
from frigate.const import CONFIG_DIR
from frigate.models import User from frigate.models import User
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -16,7 +20,9 @@ class WebPushClient(Communicator): # type: ignore[misc]
def __init__(self, config: FrigateConfig) -> None: def __init__(self, config: FrigateConfig) -> None:
self.config = config self.config = config
# TODO check for VAPID key
# 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.tokens = []
self.invalid_tokens = [] self.invalid_tokens = []

View File

@ -5,11 +5,27 @@ import { Toaster } from "@/components/ui/sonner";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import axios from "axios"; import axios from "axios";
import { useEffect, useState } from "react";
import useSWR from "swr"; import useSWR from "swr";
const NOTIFICATION_SERVICE_WORKER = "notifications-worker.ts";
export default function NotificationView() { export default function NotificationView() {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
// notification state
const [notificationsSubscribed, setNotificationsSubscribed] =
useState<boolean>();
useEffect(() => {
navigator.serviceWorker
.getRegistration(NOTIFICATION_SERVICE_WORKER)
.then((worker) => {
setNotificationsSubscribed(worker != null);
});
}, []);
return ( return (
<> <>
<div className="flex size-full flex-col md:flex-row"> <div className="flex size-full flex-col md:flex-row">
@ -48,12 +64,13 @@ export default function NotificationView() {
// TODO make the notifications button show enable / disable depending on current state // TODO make the notifications button show enable / disable depending on current state
} }
<Button <Button
disabled={notificationsSubscribed == undefined}
onClick={() => { onClick={() => {
Notification.requestPermission().then((permission) => { Notification.requestPermission().then((permission) => {
console.log("notification permissions are ", permission); console.log("notification permissions are ", permission);
if (permission === "granted") { if (permission === "granted") {
navigator.serviceWorker navigator.serviceWorker
.register("notifications-worker.ts") .register(NOTIFICATION_SERVICE_WORKER)
.then((registration) => { .then((registration) => {
registration.pushManager registration.pushManager
.subscribe() .subscribe()
@ -68,7 +85,7 @@ export default function NotificationView() {
}); });
}} }}
> >
Enable Notifications {`${notificationsSubscribed ? "Disable" : "Enable"} Notifications`}
</Button> </Button>
</div> </div>
</div> </div>