Skip to content

Commit

Permalink
Implement push notification handling
Browse files Browse the repository at this point in the history
  • Loading branch information
NickM-27 committed Jul 20, 2024
1 parent 4989f6e commit e35fd00
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 41 deletions.
4 changes: 3 additions & 1 deletion frigate/api/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ def register_notifications():
sub = json.get("sub")

if not sub:
return jsonify({"success": False, "message": "Subscription must be provided."}), 400
return jsonify(
{"success": False, "message": "Subscription must be provided."}
), 400

try:
User.update(notification_tokens=User.notification_tokens.append(sub)).where(
Expand Down
21 changes: 13 additions & 8 deletions frigate/comms/webpush.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,24 @@ def send_message(self, payload: dict[str, any]) -> None:

title = f"{', '.join(sorted_objects).replace('_', ' ').title()}{' was' if state == 'end' else ''} detected in {', '.join(payload['after']['data']['zones']).replace('_', ' ').title()}"
message = f"Detected on {payload['after']['camera'].replace('_', ' ').title()}"
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", "")}'
direct_url = f"/review?id={reviewId}"
image = f'{payload["after"]["thumb_path"].replace("/media/frigate", "")}'

logger.info(f"the image for testing is {image}")

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,
}),
data=json.dumps(
{
"title": title,
"message": message,
"direct_url": direct_url,
"image": image,
"id": reviewId,
}
),
)

def stop(self) -> None:
Expand Down
53 changes: 51 additions & 2 deletions web/public/notifications-worker.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,58 @@
// Notifications Worker

self.addEventListener("push", function (event) {
// @ts-expect-error we know this exists
if (event.data) {
console.log("This push event has data: ", event.data.text());
// @ts-expect-error we know this exists
const data = event.data.json();
// @ts-expect-error we know this exists
self.registration.showNotification(data.title, {
body: data.message,
icon: data.image,
image: data.image,
tag: data.id,
data: { id: data.id, link: data.direct_url },
actions: [
{
action: `view-${data.id}`,
title: "View",
},
],
});
} else {
console.log("This push event has no data.");
// pass
// This push event has no data
}
});

self.addEventListener("notificationclick", (event) => {
// @ts-expect-error we know this exists
if (event.notification) {
// @ts-expect-error we know this exists
event.notification.close();

// @ts-expect-error we know this exists
if (event.notification.data) {
// @ts-expect-error we know this exists
const url = event.notification.data.link;

// @ts-expect-error we know this exists
clients.matchAll({ type: "window" }).then((windowClients) => {
// Check if there is already a window/tab open with the target URL
for (let i = 0; i < windowClients.length; i++) {
const client = windowClients[i];
// If so, just focus it.
if (client.url === url && "focus" in client) {
return client.focus();
}
}
// If not, then open the target URL in a new window/tab.
// @ts-expect-error we know this exists
if (clients.openWindow) {
// @ts-expect-error we know this exists
return clients.openWindow(url);
}
});
}
}
});
88 changes: 58 additions & 30 deletions web/src/views/settings/NotificationsSettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,58 @@ import { Toaster } from "@/components/ui/sonner";
import { Switch } from "@/components/ui/switch";
import { FrigateConfig } from "@/types/frigateConfig";
import axios from "axios";
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import useSWR from "swr";

const NOTIFICATION_SERVICE_WORKER = "notifications-worker.ts";

export default function NotificationView() {
const { data: config } = useSWR<FrigateConfig>("config");
const { data: config } = useSWR<FrigateConfig>("config", {
revalidateOnFocus: false,
});

// notification key handling

const { data: publicKey } = useSWR(
config?.notifications?.enabled ? "notifications/pubkey" : null,
{ revalidateOnFocus: false },
);

const subscribeToNotifications = useCallback(
(registration: ServiceWorkerRegistration) => {
if (registration) {
registration.pushManager
.subscribe({
userVisibleOnly: true,
applicationServerKey: publicKey,
})
.then((pushSubscription) => {
axios.post("notifications/register", {
sub: pushSubscription,
});
});
}
},
[publicKey],
);

// notification state

const [notificationsSubscribed, setNotificationsSubscribed] =
useState<boolean>();
const [registration, setRegistration] =
useState<ServiceWorkerRegistration | null>();

useEffect(() => {
navigator.serviceWorker
.getRegistration(NOTIFICATION_SERVICE_WORKER)
.then((worker) => {
setNotificationsSubscribed(worker != null);
if (worker) {
setRegistration(worker);
} else {
setRegistration(null);
}
})
.catch(() => {
setRegistration(null);
});
}, []);

Expand Down Expand Up @@ -70,34 +98,34 @@ export default function NotificationView() {
// TODO make the notifications button show enable / disable depending on current state
}
<Button
disabled={
notificationsSubscribed == undefined ||
publicKey == undefined
}
disabled={publicKey == undefined}
onClick={() => {
Notification.requestPermission().then((permission) => {
console.log("notification permissions are ", permission);
if (permission === "granted") {
navigator.serviceWorker
.register(NOTIFICATION_SERVICE_WORKER)
.then((registration) => {
registration.pushManager
.subscribe({
userVisibleOnly: true,
applicationServerKey: publicKey,
})
.then((pushSubscription) => {
console.log(pushSubscription.endpoint);
axios.post("notifications/register", {
sub: pushSubscription,
});
});
});
}
});
if (registration == null) {
Notification.requestPermission().then((permission) => {
if (permission === "granted") {
navigator.serviceWorker
.register(NOTIFICATION_SERVICE_WORKER)
.then((registration) => {
setRegistration(registration);

if (registration.active) {
subscribeToNotifications(registration);
} else {
setTimeout(
() => subscribeToNotifications(registration),
1000,
);
}
});
}
});
} else {
registration.unregister();
setRegistration(null);
}
}}
>
{`${notificationsSubscribed ? "Disable" : "Enable"} Notifications`}
{`${registration != null ? "Unregister" : "Register"} for Notifications`}
</Button>
</div>
</div>
Expand Down

0 comments on commit e35fd00

Please sign in to comment.