mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-13 06:35:24 +03:00
Implement push notification handling
This commit is contained in:
parent
f946690520
commit
6fbab846af
@ -44,7 +44,9 @@ def register_notifications():
|
|||||||
sub = json.get("sub")
|
sub = json.get("sub")
|
||||||
|
|
||||||
if not 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:
|
try:
|
||||||
User.update(notification_tokens=User.notification_tokens.append(sub)).where(
|
User.update(notification_tokens=User.notification_tokens.append(sub)).where(
|
||||||
|
|||||||
@ -85,19 +85,24 @@ class WebPushClient(Communicator): # type: ignore[misc]
|
|||||||
|
|
||||||
title = f"{', '.join(sorted_objects).replace('_', ' ').title()}{' was' if state == 'end' else ''} detected in {', '.join(payload['after']['data']['zones']).replace('_', ' ').title()}"
|
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()}"
|
message = f"Detected on {payload['after']['camera'].replace('_', ' ').title()}"
|
||||||
direct_url = f"{self.config.notifications.base_url}/review?id={reviewId}"
|
direct_url = f"/review?id={reviewId}"
|
||||||
image = f'{self.config.notifications.base_url}{payload["after"]["thumb_path"].replace("/media/frigate", "")}'
|
image = f'{payload["after"]["thumb_path"].replace("/media/frigate", "")}'
|
||||||
|
|
||||||
|
logger.info(f"the image for testing is {image}")
|
||||||
|
|
||||||
for pusher in self.web_pushers:
|
for pusher in self.web_pushers:
|
||||||
pusher.send(
|
pusher.send(
|
||||||
headers=self.claim_headers,
|
headers=self.claim_headers,
|
||||||
ttl=0,
|
ttl=0,
|
||||||
data=json.dumps({
|
data=json.dumps(
|
||||||
|
{
|
||||||
"title": title,
|
"title": title,
|
||||||
"message": message,
|
"message": message,
|
||||||
"direct_url": direct_url,
|
"direct_url": direct_url,
|
||||||
"image": image,
|
"image": image,
|
||||||
}),
|
"id": reviewId,
|
||||||
|
}
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
|
|||||||
@ -1,9 +1,58 @@
|
|||||||
// Notifications Worker
|
// Notifications Worker
|
||||||
|
|
||||||
self.addEventListener("push", function (event) {
|
self.addEventListener("push", function (event) {
|
||||||
|
// @ts-expect-error we know this exists
|
||||||
if (event.data) {
|
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 {
|
} 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,30 +5,58 @@ 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 { useCallback, useEffect, useState } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
const NOTIFICATION_SERVICE_WORKER = "notifications-worker.ts";
|
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", {
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
});
|
||||||
|
|
||||||
// notification key handling
|
// notification key handling
|
||||||
|
|
||||||
const { data: publicKey } = useSWR(
|
const { data: publicKey } = useSWR(
|
||||||
config?.notifications?.enabled ? "notifications/pubkey" : null,
|
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
|
// notification state
|
||||||
|
|
||||||
const [notificationsSubscribed, setNotificationsSubscribed] =
|
const [registration, setRegistration] =
|
||||||
useState<boolean>();
|
useState<ServiceWorkerRegistration | null>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
.getRegistration(NOTIFICATION_SERVICE_WORKER)
|
.getRegistration(NOTIFICATION_SERVICE_WORKER)
|
||||||
.then((worker) => {
|
.then((worker) => {
|
||||||
setNotificationsSubscribed(worker != null);
|
if (worker) {
|
||||||
|
setRegistration(worker);
|
||||||
|
} else {
|
||||||
|
setRegistration(null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setRegistration(null);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -70,34 +98,34 @@ 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={
|
disabled={publicKey == undefined}
|
||||||
notificationsSubscribed == undefined ||
|
|
||||||
publicKey == undefined
|
|
||||||
}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (registration == null) {
|
||||||
Notification.requestPermission().then((permission) => {
|
Notification.requestPermission().then((permission) => {
|
||||||
console.log("notification permissions are ", permission);
|
|
||||||
if (permission === "granted") {
|
if (permission === "granted") {
|
||||||
navigator.serviceWorker
|
navigator.serviceWorker
|
||||||
.register(NOTIFICATION_SERVICE_WORKER)
|
.register(NOTIFICATION_SERVICE_WORKER)
|
||||||
.then((registration) => {
|
.then((registration) => {
|
||||||
registration.pushManager
|
setRegistration(registration);
|
||||||
.subscribe({
|
|
||||||
userVisibleOnly: true,
|
if (registration.active) {
|
||||||
applicationServerKey: publicKey,
|
subscribeToNotifications(registration);
|
||||||
})
|
} else {
|
||||||
.then((pushSubscription) => {
|
setTimeout(
|
||||||
console.log(pushSubscription.endpoint);
|
() => subscribeToNotifications(registration),
|
||||||
axios.post("notifications/register", {
|
1000,
|
||||||
sub: pushSubscription,
|
);
|
||||||
});
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
registration.unregister();
|
||||||
|
setRegistration(null);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{`${notificationsSubscribed ? "Disable" : "Enable"} Notifications`}
|
{`${registration != null ? "Unregister" : "Register"} for Notifications`}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user