Skip to content

Commit

Permalink
Support multiple endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
NickM-27 committed Jul 21, 2024
1 parent c92c825 commit 22afd24
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 26 deletions.
12 changes: 6 additions & 6 deletions docs/docs/configuration/notifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ title: Notifications

Frigate offers native notifications using the [WebPush Protocol](https://web.dev/articles/push-notifications-web-push-protocol) which uses the [VAPID spec](https://tools.ietf.org/html/draft-thomson-webpush-vapid) to deliver notifications to web apps using encryption.

## Configuration

To configure notifications, go to the Frigate WebUI -> Settings -> Notifications and enable, then fill out the fields and save.
In order to use notifications the following requirements must be met:

:::note
- Frigate must be accessed via a secure https connection
- A supported browser must be used. Currently Chrome, Firefox, and Safari are known to be supported.
- In order for notifications to be usable externally, Frigate must be accessible externally

Currently, notifications are only supported in Chrome and Firefox browsers.
## Configuration

:::
To configure notifications, go to the Frigate WebUI -> Settings -> Notifications and enable, then fill out the fields and save.

## Registration

Expand Down
58 changes: 38 additions & 20 deletions frigate/comms/webpush.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ class WebPushClient(Communicator): # type: ignore[misc]

def __init__(self, config: FrigateConfig) -> None:
self.config = config
self.claim = None
self.claim_headers = None
self.claim_headers: dict[str, dict[str, str]] = {}
self.refresh = 0
self.web_pushers: list[WebPusher] = []

if not self.config.notifications.email:
Expand All @@ -41,27 +41,39 @@ def subscribe(self, receiver: Callable) -> None:
"""Wrapper for allowing dispatcher to subscribe."""
pass

def check_registrations(self) -> None:
# check for valid claim or create new one
now = datetime.datetime.now().timestamp()
if len(self.claim_headers) == 0 or self.refresh < now:
self.refresh = (
datetime.datetime.now() + datetime.timedelta(hours=1)
).timestamp()
endpoints: set[str] = set()

# get a unique set of push endpoints
for push in self.web_pushers:
endpoint: str = push.subscription_info["endpoint"]
endpoints.add(endpoint[0 : endpoint.index("/", 10)])

# create new claim
for endpoint in endpoints:
claim = {
"sub": f"mailto:{self.config.notifications.email}",
"aud": endpoint,
"exp": self.refresh,
}
self.claim_headers[endpoint] = self.vapid.sign(claim)

def publish(self, topic: str, payload: Any, retain: bool = False) -> None:
"""Wrapper for publishing when client is in valid state."""
if topic == "reviews":
self.send_message(json.loads(payload))
self.send_alert(json.loads(payload))

def send_message(self, payload: dict[str, any]) -> None:
def send_alert(self, payload: dict[str, any]) -> None:
if not self.config.notifications.email:
return

# 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": f"mailto:{self.config.notifications.email}",
"aud": "https://fcm.googleapis.com",
"exp": (
datetime.datetime.now() + datetime.timedelta(hours=1)
).timestamp(),
}
self.claim_headers = self.vapid.sign(self.claim)
self.check_registrations()

# Only notify for alerts
if payload["after"]["severity"] != "alert":
Expand All @@ -88,15 +100,21 @@ def send_message(self, payload: dict[str, any]) -> None:

sorted_objects.update(payload["after"]["data"]["sub_labels"])

camera: str = payload["after"]["camera"]
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"/review?id={reviewId}"
message = f"Detected on {camera.replace('_', ' ').title()}"
image = f'{payload["after"]["thumb_path"].replace("/media/frigate", "")}'

# if event is ongoing open to live view otherwise open to recordings view
direct_url = f"/review?id={reviewId}" if state == "end" else f"/#{camera}"

for pusher in self.web_pushers:
endpoint = pusher.subscription_info["endpoint"]
pusher.send(
headers=self.claim_headers,
ttl=0,
headers=self.claim_headers[endpoint[0 : endpoint.index("/", 10)]],
ttl=3600,
urgency="high",
topic=reviewId,
data=json.dumps(
{
"title": title,
Expand Down

0 comments on commit 22afd24

Please sign in to comment.