diff --git a/docker/main/requirements-wheels.txt b/docker/main/requirements-wheels.txt index ab3152090..4b4e13850 100644 --- a/docker/main/requirements-wheels.txt +++ b/docker/main/requirements-wheels.txt @@ -36,6 +36,4 @@ chromadb == 0.5.0 # Generative AI google-generativeai == 0.6.* ollama == 0.2.* -openai == 1.30.* -# Notifications -firebase_admin == 6.5.0 \ No newline at end of file +openai == 1.30.* \ No newline at end of file diff --git a/frigate/api/app.py b/frigate/api/app.py index 0e3b0fecd..79a78f454 100644 --- a/frigate/api/app.py +++ b/frigate/api/app.py @@ -19,6 +19,7 @@ from frigate.api.auth import AuthBp, get_jwt_secret, limiter from frigate.api.event import EventBp from frigate.api.export import ExportBp from frigate.api.media import MediaBp +from frigate.api.notification import NotificationBp from frigate.api.preview import PreviewBp from frigate.api.review import ReviewBp from frigate.config import FrigateConfig @@ -48,6 +49,7 @@ bp.register_blueprint(MediaBp) bp.register_blueprint(PreviewBp) bp.register_blueprint(ReviewBp) bp.register_blueprint(AuthBp) +bp.register_blueprint(NotificationBp) def create_app( diff --git a/frigate/api/notification.py b/frigate/api/notification.py new file mode 100644 index 000000000..63cff9d08 --- /dev/null +++ b/frigate/api/notification.py @@ -0,0 +1,34 @@ +"""Notification apis.""" + +import logging + +from flask import ( + Blueprint, + jsonify, + request, +) +from peewee import DoesNotExist + +from frigate.models import User + +logger = logging.getLogger(__name__) + +NotificationBp = Blueprint("notifications", __name__) + + +@NotificationBp.route("/notifications/register", methods=["POST"]) +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"] + + if not token: + return jsonify({"success": False, "message": "Token must be provided."}), 400 + + try: + User.update(notification_tokens=User.notification_tokens.append(token)).where( + User.username == username + ).execute() + return jsonify({"success": True, "message": "Successfully saved token."}), 200 + except DoesNotExist: + return jsonify({"success": False, "message": "Could not find user."}), 404 diff --git a/frigate/app.py b/frigate/app.py index a0782c850..dcc61a58c 100644 --- a/frigate/app.py +++ b/frigate/app.py @@ -23,9 +23,9 @@ from frigate.api.app import create_app from frigate.api.auth import hash_password from frigate.comms.config_updater import ConfigPublisher from frigate.comms.dispatcher import Communicator, Dispatcher -from frigate.comms.firebase import FirebaseClient from frigate.comms.inter_process import InterProcessCommunicator from frigate.comms.mqtt import MqttClient +from frigate.comms.webpush import WebPushClient from frigate.comms.ws import WebSocketClient from frigate.comms.zmq_proxy import ZmqProxy from frigate.config import FrigateConfig @@ -403,7 +403,7 @@ class FrigateApp: comms.append(MqttClient(self.config)) if self.config.notifications.enabled: - comms.append(FirebaseClient(self.config, self.stop_event)) + comms.append(WebPushClient(self.config)) comms.append(WebSocketClient(self.config)) comms.append(self.inter_process_communicator) diff --git a/frigate/comms/firebase.py b/frigate/comms/firebase.py deleted file mode 100644 index 5934d1c2c..000000000 --- a/frigate/comms/firebase.py +++ /dev/null @@ -1,97 +0,0 @@ -"""Handle sending notifications for Frigate via Firebase.""" - -import json -import logging -import threading -from multiprocessing.synchronize import Event as MpEvent -from typing import Any, Callable - -import firebase_admin -from firebase_admin import credentials, messaging - -from frigate.comms.dispatcher import Communicator -from frigate.config import FrigateConfig - -logger = logging.getLogger(__name__) - - -class FirebaseClient(Communicator): # type: ignore[misc] - """Frigate wrapper for firebase client.""" - - def __init__(self, config: FrigateConfig, stop_event: MpEvent) -> None: - self.messenger = FirebaseMessenger(config, stop_event) - self.messenger.start() - - def subscribe(self, receiver: Callable) -> None: - """Wrapper for allowing dispatcher to subscribe.""" - pass - - def publish(self, topic: str, payload: Any, retain: bool = False) -> None: - """Wrapper for publishing when client is in valid state.""" - if topic == "reviews": - self.messenger.send_message(json.loads(payload)) - - def stop(self) -> None: - pass - - -class FirebaseMessenger(threading.Thread): - def __init__(self, config: FrigateConfig, stop_event: MpEvent) -> None: - threading.Thread.__init__(self) - self.name = "firebase_messenger" - self.config = config - self.stop_event = stop_event - - def send_message(self, payload: dict[str, any]) -> None: - # Only notify for alerts - if payload["after"]["severity"] != "alert": - return - - state = payload["type"] - - # Don't notify if message is an update and important fields don't have an update - if ( - state == "update" - and len(payload["before"]["data"]["objects"]) - == len(payload["after"]["data"]["objects"]) - and len(payload["before"]["data"]["zones"]) - == len(payload["after"]["data"]["zones"]) - ): - return - - reviewId = payload["after"]["id"] - sorted_objects: set[str] = set() - - for obj in payload["after"]["data"]["objects"]: - if "-verified" not in obj: - sorted_objects.add(obj) - - sorted_objects.update(payload["after"]["data"]["sub_labels"]) - - message = messaging.MulticastMessage( - notification=messaging.Notification( - title=f"{', '.join(sorted_objects).replace('_', ' ').title()}{' was' if state == 'end' else ''} detected in {', '.join(payload['after']['data']['zones']).replace('_', ' ').title()}", - body=f"Detected on {payload['after']['camera'].replace('_', ' ').title()}", - ), - webpush=messaging.WebpushConfig( - fcm_options=messaging.WebpushFCMOptions( - link=f"{self.config.notifications.base_url}/review?id={reviewId}" - ) - ), - data={"id": reviewId, "imageUrl": f'{self.config.notifications.base_url}{payload["after"]["thumb_path"].replace("/media/frigate", "")}'}, - tokens=[ - "cNNicZp6S92qn4kAVJnzd7:APA91bGv-MvDmNoZ2xqJTkPyCTmyv2WG0tfwIqWUuNtq3SXlpQJpdPCCjTEehOLDa0Yphv__KdxOQYEfaFvYfTW2qQevX-tSnRCVa_sJazQ_rfTervpo_zBVJD1T5GfYaY6kr41Wr_fP" - ], - ) - messaging.send_multicast(message) - - def run(self) -> None: - try: - firebase_admin.get_app() - except ValueError: - cred = credentials.Certificate("/config/firebase-priv-key.json") - firebase_admin.initialize_app(credential=cred) - - while self.stop_event.wait(0.1): - # TODO check for a delete invalid tokens - pass diff --git a/frigate/comms/webpush.py b/frigate/comms/webpush.py new file mode 100644 index 000000000..d2cf6ffd0 --- /dev/null +++ b/frigate/comms/webpush.py @@ -0,0 +1,70 @@ +"""Handle sending notifications for Frigate via Firebase.""" + +import json +import logging +from typing import Any, Callable + +from frigate.comms.dispatcher import Communicator +from frigate.config import FrigateConfig +from frigate.models import User + +logger = logging.getLogger(__name__) + + +class WebPushClient(Communicator): # type: ignore[misc] + """Frigate wrapper for firebase client.""" + + def __init__(self, config: FrigateConfig) -> None: + self.config = config + # TODO check for VAPID key + + self.tokens = [] + self.invalid_tokens = [] + + users: list[User] = User.select(User.notification_tokens).dicts().iterator() + + for user in users: + self.tokens.extend(user["notification_tokens"]) + + def subscribe(self, receiver: Callable) -> None: + """Wrapper for allowing dispatcher to subscribe.""" + pass + + 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)) + + def send_message(self, payload: dict[str, any]) -> None: + # Only notify for alerts + if payload["after"]["severity"] != "alert": + return + + state = payload["type"] + + # Don't notify if message is an update and important fields don't have an update + if ( + state == "update" + and len(payload["before"]["data"]["objects"]) + == len(payload["after"]["data"]["objects"]) + and len(payload["before"]["data"]["zones"]) + == len(payload["after"]["data"]["zones"]) + ): + return + + reviewId = payload["after"]["id"] + sorted_objects: set[str] = set() + + for obj in payload["after"]["data"]["objects"]: + if "-verified" not in obj: + sorted_objects.add(obj) + + sorted_objects.update(payload["after"]["data"]["sub_labels"]) + + 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", "")}' + + def stop(self) -> None: + pass diff --git a/frigate/config.py b/frigate/config.py index 114ce5a6d..e06b0baa2 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -171,7 +171,9 @@ class AuthConfig(FrigateBaseModel): class NotificationConfig(FrigateBaseModel): enabled: bool = Field(default=False, title="Enable notifications") - base_url: Optional[str] = Field(default=None, title="Base url for notification link and image.") + base_url: Optional[str] = Field( + default=None, title="Base url for notification link and image." + ) class StatsConfig(FrigateBaseModel): @@ -1366,7 +1368,9 @@ class FrigateConfig(FrigateBaseModel): default_factory=dict, title="Frigate environment variables." ) ui: UIConfig = Field(default_factory=UIConfig, title="UI configuration.") - notifications: NotificationConfig = Field(default_factory=NotificationConfig, title="Notification Config") + notifications: NotificationConfig = Field( + default_factory=NotificationConfig, title="Notification Config" + ) telemetry: TelemetryConfig = Field( default_factory=TelemetryConfig, title="Telemetry configuration." ) diff --git a/migrations/026_add_notification_tokens.py b/migrations/026_add_notification_tokens.py index 54c37c2ab..08d95cbf7 100644 --- a/migrations/026_add_notification_tokens.py +++ b/migrations/026_add_notification_tokens.py @@ -32,7 +32,7 @@ SQL = pw.SQL def migrate(migrator, database, fake=False, **kwargs): migrator.add_fields( User, - notification_tokens=JSONField(default={}), + notification_tokens=JSONField(default=[]), ) diff --git a/web/package-lock.json b/web/package-lock.json index bcc5cfa70..b49d6ad83 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -36,7 +36,6 @@ "clsx": "^2.1.1", "copy-to-clipboard": "^3.3.3", "date-fns": "^3.6.0", - "firebase": "^10.12.3", "hls.js": "^1.5.13", "idb-keyval": "^6.2.1", "immer": "^10.1.1", @@ -688,562 +687,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/@firebase/analytics": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.5.tgz", - "integrity": "sha512-d0X2ksTOKHMf5zFAMKFZWXa8hSbgohsG507xFsGhF4Uet2b8uEUL/YLrEth67jXEbGEi1UQZX4AaGBxKNiDzjw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/installations": "0.6.8", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/analytics-compat": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.11.tgz", - "integrity": "sha512-wmXxJ49pEY7H549Pa4CDPOTzkPJnfG2Yolptg72ntTgSrbKVq+Eg9cAQY6Z5Kn9ATSQRX5oGXKlNfEk5DJBvvA==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/analytics": "0.10.5", - "@firebase/analytics-types": "0.8.2", - "@firebase/component": "0.6.8", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/analytics-types": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.2.tgz", - "integrity": "sha512-EnzNNLh+9/sJsimsA/FGqzakmrAUKLeJvjRHlg8df1f97NLUlFidk9600y0ZgWOp3CAxn6Hjtk+08tixlUOWyw==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/app": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.10.6.tgz", - "integrity": "sha512-/r8Ikp7TOrIIdp7v2adD2kg9SqIXMGOoJXJB1HsX7LjpjWdsoy1fMkP0HlI7GQqqRxDueHNhETx5Zn5E8HyVAQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.9.7", - "idb": "7.1.1", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-check": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.5.tgz", - "integrity": "sha512-WyIckkVYAfnzsPIw6EAt/qBUANkUAVl6irF0xuJ1R9ISNyUT1h7dPAwvs/g3rsx0fpBWaHRAH0IFiN6zO6yLqQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/app-check-compat": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.12.tgz", - "integrity": "sha512-p/5w3pMih3JVT6u7g04KXgSZr6HDsQXyeWZkIe0+r71dPOlcKyUooe9/feTc8BWpjha3rUOkqQ7+JXZObwvYoQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-check": "0.8.5", - "@firebase/app-check-types": "0.5.2", - "@firebase/component": "0.6.8", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/app-check-interop-types": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.2.tgz", - "integrity": "sha512-LMs47Vinv2HBMZi49C09dJxp0QT5LwDzFaVGf/+ITHe3BlIhUiLNttkATSXplc89A2lAaeTqjgqVkiRfUGyQiQ==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/app-check-types": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.2.tgz", - "integrity": "sha512-FSOEzTzL5bLUbD2co3Zut46iyPWML6xc4x+78TeaXMSuJap5QObfb+rVvZJtla3asN4RwU7elaQaduP+HFizDA==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/app-compat": { - "version": "0.2.36", - "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.36.tgz", - "integrity": "sha512-qsf+pllpgy1IGe2f5vfenOHSX8Cs58sVR5L6h/zBlNy9Yo54B2jy61KxLpSOgyRZb18IlnLLGjo7VtGU1CHvHA==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app": "0.10.6", - "@firebase/component": "0.6.8", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/app-types": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.2.tgz", - "integrity": "sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/auth-compat": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.10.tgz", - "integrity": "sha512-epDhgNIXmhl9DPuTW9Ec5NDJJKMFIdXBXiQI9O0xNHveow/ETtBCY86srzF7iCacqsd30CcpLwwXlhk8Y19Olg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/auth": "1.7.5", - "@firebase/auth-types": "0.12.2", - "@firebase/component": "0.6.8", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0", - "undici": "5.28.4" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/auth-compat/node_modules/@firebase/auth": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.7.5.tgz", - "integrity": "sha512-DMFR1OA/f1/voeuFbSORg9AP36pMgOoSb/DRgiDalLmIJsDTlQNMCu+givjMP4s/XL85+tBk2MerYnK/AscJjw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0", - "undici": "5.28.4" - }, - "peerDependencies": { - "@firebase/app": "0.x", - "@react-native-async-storage/async-storage": "^1.18.1" - }, - "peerDependenciesMeta": { - "@react-native-async-storage/async-storage": { - "optional": true - } - } - }, - "node_modules/@firebase/auth-interop-types": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.3.tgz", - "integrity": "sha512-Fc9wuJGgxoxQeavybiuwgyi+0rssr76b+nHpj+eGhXFYAdudMWyfBHvFL/I5fEHniUM/UQdFzi9VXJK2iZF7FQ==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/auth-types": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.2.tgz", - "integrity": "sha512-qsEBaRMoGvHO10unlDJhaKSuPn4pyoTtlQuP1ghZfzB6rNQPuhp/N/DcFZxm9i4v0SogjCbf9reWupwIvfmH6w==", - "license": "Apache-2.0", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/component": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.8.tgz", - "integrity": "sha512-LcNvxGLLGjBwB0dJUsBGCej2fqAepWyBubs4jt1Tiuns7QLbXHuyObZ4aMeBjZjWx4m8g1LoVI9QFpSaq/k4/g==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.6.tgz", - "integrity": "sha512-nrexUEG/fpVlHtWKkyfhTC3834kZ1WS7voNyqbBsBCqHXQOvznN5Z0L3nxBqdXSJyltNAf4ndFlQqm5gZiEczQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-check-interop-types": "0.3.2", - "@firebase/auth-interop-types": "0.2.3", - "@firebase/component": "0.6.8", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.9.7", - "faye-websocket": "0.11.4", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-compat": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.6.tgz", - "integrity": "sha512-1OGA0sLY47mkXjhICCrUTXEYFnSSXoiXWm1SHsN62b+Lzs5aKA3aWTjTUmYIoK93kDAMPkYpulSv8jcbH4Hwew==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/database": "1.0.6", - "@firebase/database-types": "1.0.4", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-types": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.4.tgz", - "integrity": "sha512-mz9ZzbH6euFXbcBo+enuJ36I5dR5w+enJHHjy9Y5ThCdKUseqfDjW3vCp1YxE9zygFCSjJJ/z1cQ+zodvUcwPQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-types": "0.9.2", - "@firebase/util": "1.9.7" - } - }, - "node_modules/@firebase/firestore": { - "version": "4.6.4", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.6.4.tgz", - "integrity": "sha512-vk2MoH5HxYEhiNg1l+yBXq1Fkhue/11bFg4HdlTv6BJHcTnnAj2a+/afPpatcW4MOdYA3Tv+d5nGzWbbOC1SHw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.9.7", - "@firebase/webchannel-wrapper": "1.0.1", - "@grpc/grpc-js": "~1.9.0", - "@grpc/proto-loader": "^0.7.8", - "tslib": "^2.1.0", - "undici": "5.28.4" - }, - "engines": { - "node": ">=10.10.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/firestore-compat": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.33.tgz", - "integrity": "sha512-i42a2l31N95CwYEB7zmfK0FS1mrO6pwOLwxavCrwu1BCFrVVVQhUheTPIda/iGguK/2Nog0RaIR1bo7QkZEz3g==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/firestore": "4.6.4", - "@firebase/firestore-types": "3.0.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/firestore-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.2.tgz", - "integrity": "sha512-wp1A+t5rI2Qc/2q7r2ZpjUXkRVPtGMd6zCLsiWurjsQpqPgFin3AhNibKcIzoF2rnToNa/XYtyWXuifjOOwDgg==", - "license": "Apache-2.0", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/functions": { - "version": "0.11.6", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.11.6.tgz", - "integrity": "sha512-GPfIBPtpwQvsC7SQbgaUjLTdja0CsNwMoKSgrzA1FGGRk4NX6qO7VQU6XCwBiAFWbpbQex6QWkSMsCzLx1uibQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-check-interop-types": "0.3.2", - "@firebase/auth-interop-types": "0.2.3", - "@firebase/component": "0.6.8", - "@firebase/messaging-interop-types": "0.2.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0", - "undici": "5.28.4" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/functions-compat": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.12.tgz", - "integrity": "sha512-r3XUb5VlITWpML46JymfJPkK6I9j4SNlO7qWIXUc0TUmkv0oAfVoiIt1F83/NuMZXaGr4YWA/794nVSy4GV8tw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/functions": "0.11.6", - "@firebase/functions-types": "0.6.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/functions-types": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.2.tgz", - "integrity": "sha512-0KiJ9lZ28nS2iJJvimpY4nNccV21rkQyor5Iheu/nq8aKXJqtJdeSlZDspjPSBBiHRzo7/GMUttegnsEITqR+w==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/installations": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.8.tgz", - "integrity": "sha512-57V374qdb2+wT5v7+ntpLXBjZkO6WRgmAUbVkRfFTM/4t980p0FesbqTAcOIiM8U866UeuuuF8lYH70D3jM/jQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/util": "1.9.7", - "idb": "7.1.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/installations-compat": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.8.tgz", - "integrity": "sha512-pI2q8JFHB7yIq/szmhzGSWXtOvtzl6tCUmyykv5C8vvfOVJUH6mP4M4iwjbK8S1JotKd/K70+JWyYlxgQ0Kpyw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/installations": "0.6.8", - "@firebase/installations-types": "0.5.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/installations-types": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.2.tgz", - "integrity": "sha512-que84TqGRZJpJKHBlF2pkvc1YcXrtEDOVGiDjovP/a3s6W4nlbohGXEsBJo0JCeeg/UG9A+DEZVDUV9GpklUzA==", - "license": "Apache-2.0", - "peerDependencies": { - "@firebase/app-types": "0.x" - } - }, - "node_modules/@firebase/logger": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.2.tgz", - "integrity": "sha512-Q1VuA5M1Gjqrwom6I6NUU4lQXdo9IAQieXlujeHZWvRt1b7qQ0KwBaNAjgxG27jgF9/mUwsNmO8ptBCGVYhB0A==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/messaging": { - "version": "0.12.10", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.10.tgz", - "integrity": "sha512-fGbxJPKpl2DIKNJGhbk4mYPcM+qE2gl91r6xPoiol/mN88F5Ym6UeRdMVZah+pijh9WxM55alTYwXuW40r1Y2Q==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/installations": "0.6.8", - "@firebase/messaging-interop-types": "0.2.2", - "@firebase/util": "1.9.7", - "idb": "7.1.1", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/messaging-compat": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.10.tgz", - "integrity": "sha512-FXQm7rcowkDm8kFLduHV35IRYCRo+Ng0PIp/t1+EBuEbyplaKkGjZ932pE+owf/XR+G/60ku2QRBptRGLXZydg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/messaging": "0.12.10", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/messaging-interop-types": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.2.tgz", - "integrity": "sha512-l68HXbuD2PPzDUOFb3aG+nZj5KA3INcPwlocwLZOzPp9rFM9yeuI9YLl6DQfguTX5eAGxO0doTR+rDLDvQb5tA==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/performance": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.8.tgz", - "integrity": "sha512-F+alziiIZ6Yn8FG47mxwljq+4XkgkT2uJIFRlkyViUQRLzrogaUJW6u/+6ZrePXnouKlKIwzqos3PVJraPEcCA==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/installations": "0.6.8", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/performance-compat": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.8.tgz", - "integrity": "sha512-o7TFClRVJd3VIBoY7KZQqtCeW0PC6v9uBzM6Lfw3Nc9D7hM6OonqecYvh7NwJ6R14k+xM27frLS4BcCvFHKw2A==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/logger": "0.4.2", - "@firebase/performance": "0.6.8", - "@firebase/performance-types": "0.2.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/performance-types": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.2.tgz", - "integrity": "sha512-gVq0/lAClVH5STrIdKnHnCo2UcPLjJlDUoEB/tB4KM+hAeHUxWKnpT0nemUPvxZ5nbdY/pybeyMe8Cs29gEcHA==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/remote-config": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.8.tgz", - "integrity": "sha512-AMLqe6wfIRnjc6FkCWOSUjhc1fSTEf8o+cv1NolFvbiJ/tU+TqN4pI7pT+MIKQzNiq5fxLehkOx+xtAQBxPJKQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/installations": "0.6.8", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/remote-config-compat": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.8.tgz", - "integrity": "sha512-UxSFOp6dzFj2AHB8Bq/BYtbq5iFyizKx4Rd6WxAdaKYM8cnPMeK+l2v+Oogtjae+AeyHRI+MfL2acsfVe5cd2A==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/logger": "0.4.2", - "@firebase/remote-config": "0.4.8", - "@firebase/remote-config-types": "0.3.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/remote-config-types": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.2.tgz", - "integrity": "sha512-0BC4+Ud7y2aPTyhXJTMTFfrGGLqdYXrUB9sJVAB8NiqJswDTc4/2qrE/yfUbnQJhbSi6ZaTTBKyG3n1nplssaA==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/storage": { - "version": "0.12.6", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.12.6.tgz", - "integrity": "sha512-Zgb9WuehJxzhj7pGXUvkAEaH+3HvLjD9xSZ9nepuXf5f8378xME7oGJtREr/RnepdDA5YW0XIxe0QQBNHpe1nw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0", - "undici": "5.28.4" - }, - "peerDependencies": { - "@firebase/app": "0.x" - } - }, - "node_modules/@firebase/storage-compat": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.9.tgz", - "integrity": "sha512-WWgAp5bTW961oIsCc9+98m4MIVKpEqztAlIngfHfwO/x3DYoBPRl/awMRG3CAXyVxG+7B7oHC5IsnqM+vTwx2A==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/storage": "0.12.6", - "@firebase/storage-types": "0.8.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - }, - "peerDependencies": { - "@firebase/app-compat": "0.x" - } - }, - "node_modules/@firebase/storage-types": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.2.tgz", - "integrity": "sha512-0vWu99rdey0g53lA7IShoA2Lol1jfnPovzLDUBuon65K7uKG9G+L5uO05brD9pMw+l4HRFw23ah3GwTGpEav6g==", - "license": "Apache-2.0", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" - } - }, - "node_modules/@firebase/util": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.7.tgz", - "integrity": "sha512-fBVNH/8bRbYjqlbIhZ+lBtdAAS4WqZumx03K06/u7fJSpz1TGjEMm1ImvKD47w+xaFKIP2ori6z8BrbakRfjJA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/vertexai-preview": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@firebase/vertexai-preview/-/vertexai-preview-0.0.3.tgz", - "integrity": "sha512-KVtUWLp+ScgiwkDKAvNkVucAyhLVQp6C6lhnVEuIg4mWhWcS3oerjAeVhZT4uNofKwWxRsOaB2Yec7DMTXlQPQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-check-interop-types": "0.3.2", - "@firebase/component": "0.6.8", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "@firebase/app": "0.x", - "@firebase/app-types": "0.x" - } - }, - "node_modules/@firebase/webchannel-wrapper": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.1.tgz", - "integrity": "sha512-jmEnr/pk0yVkA7mIlHNnxCi+wWzOFUg0WyIotgkKAb2u1J7fAeDBcVNSTjTihbAYNusCLQdW5s9IJ5qwnEufcQ==", - "license": "Apache-2.0" - }, "node_modules/@floating-ui/core": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.4.tgz", @@ -1282,37 +725,6 @@ "integrity": "sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==", "license": "MIT" }, - "node_modules/@grpc/grpc-js": { - "version": "1.9.15", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", - "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", - "license": "Apache-2.0", - "dependencies": { - "@grpc/proto-loader": "^0.7.8", - "@types/node": ">=12.12.47" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.7.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", - "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", - "license": "Apache-2.0", - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@hookform/resolvers": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.0.tgz", @@ -1685,70 +1097,6 @@ "url": "https://opencollective.com/unts" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause" - }, "node_modules/@radix-ui/number": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", @@ -3895,6 +3243,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -3903,6 +3252,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -4327,6 +3677,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -4340,6 +3691,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -4364,6 +3716,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -4374,7 +3727,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/combined-stream": { "version": "1.0.8", @@ -4727,7 +4081,8 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "node_modules/entities": { "version": "4.5.0", @@ -4784,6 +4139,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, "engines": { "node": ">=6" } @@ -5174,18 +4530,6 @@ "reusify": "^1.0.4" } }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "license": "Apache-2.0", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -5250,63 +4594,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/firebase": { - "version": "10.12.3", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.12.3.tgz", - "integrity": "sha512-dO2cQ8eP6RnM2wcGzbxnoljjjMBf1suUrHYFftjSpbPn/8bEx959cwTRDHqBx3MwSzNsg6zZV/wiWydJPhUKgw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/analytics": "0.10.5", - "@firebase/analytics-compat": "0.2.11", - "@firebase/app": "0.10.6", - "@firebase/app-check": "0.8.5", - "@firebase/app-check-compat": "0.3.12", - "@firebase/app-compat": "0.2.36", - "@firebase/app-types": "0.9.2", - "@firebase/auth": "1.7.5", - "@firebase/auth-compat": "0.5.10", - "@firebase/database": "1.0.6", - "@firebase/database-compat": "1.0.6", - "@firebase/firestore": "4.6.4", - "@firebase/firestore-compat": "0.3.33", - "@firebase/functions": "0.11.6", - "@firebase/functions-compat": "0.3.12", - "@firebase/installations": "0.6.8", - "@firebase/installations-compat": "0.2.8", - "@firebase/messaging": "0.12.10", - "@firebase/messaging-compat": "0.2.10", - "@firebase/performance": "0.6.8", - "@firebase/performance-compat": "0.2.8", - "@firebase/remote-config": "0.4.8", - "@firebase/remote-config-compat": "0.2.8", - "@firebase/storage": "0.12.6", - "@firebase/storage-compat": "0.3.9", - "@firebase/util": "1.9.7", - "@firebase/vertexai-preview": "0.0.3" - } - }, - "node_modules/firebase/node_modules/@firebase/auth": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.7.5.tgz", - "integrity": "sha512-DMFR1OA/f1/voeuFbSORg9AP36pMgOoSb/DRgiDalLmIJsDTlQNMCu+givjMP4s/XL85+tBk2MerYnK/AscJjw==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0", - "undici": "5.28.4" - }, - "peerDependencies": { - "@firebase/app": "0.x", - "@react-native-async-storage/async-storage": "^1.18.1" - }, - "peerDependenciesMeta": { - "@react-native-async-storage/async-storage": { - "optional": true - } - } - }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -5421,6 +4708,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -5595,12 +4883,6 @@ "dev": true, "license": "MIT" }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", - "license": "MIT" - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -5638,12 +4920,6 @@ "node": ">=16.17.0" } }, - "node_modules/idb": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", - "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", - "license": "ISC" - }, "node_modules/idb-keyval": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", @@ -5775,6 +5051,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "engines": { "node": ">=8" } @@ -6172,24 +5449,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", - "license": "Apache-2.0" - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -7191,30 +6456,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/protobufjs": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.2.tgz", - "integrity": "sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/proxy-compare": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.0.tgz", @@ -7673,6 +6914,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -7932,26 +7174,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -8115,6 +7337,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -8144,6 +7367,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -8745,7 +7969,8 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true }, "node_modules/universalify": { "version": "0.2.0", @@ -9099,29 +8324,6 @@ "node": ">=12" } }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "license": "Apache-2.0", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", @@ -9278,6 +8480,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "engines": { "node": ">=10" } @@ -9294,6 +8497,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -9311,6 +8515,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "engines": { "node": ">=12" } diff --git a/web/package.json b/web/package.json index ed2a35846..328e995b7 100644 --- a/web/package.json +++ b/web/package.json @@ -42,7 +42,6 @@ "clsx": "^2.1.1", "copy-to-clipboard": "^3.3.3", "date-fns": "^3.6.0", - "firebase": "^10.12.3", "hls.js": "^1.5.13", "idb-keyval": "^6.2.1", "immer": "^10.1.1", diff --git a/web/public/firebase-messaging-sw.js b/web/public/firebase-messaging-sw.js deleted file mode 100644 index f83a93227..000000000 --- a/web/public/firebase-messaging-sw.js +++ /dev/null @@ -1,36 +0,0 @@ -// Give the service worker access to Firebase Messaging. -// Note that you can only use Firebase Messaging here. Other Firebase libraries -// are not available in the service worker. -importScripts("https://www.gstatic.com/firebasejs/8.10.1/firebase-app.js"); -importScripts("https://www.gstatic.com/firebasejs/8.10.1/firebase-messaging.js"); - -// Initialize the Firebase app in the service worker by passing in -// your app's Firebase config object. -// https://firebase.google.com/docs/web/setup#config-object -const fbConfig = await ( - await fetch(`${window.location.href}/firebase-config.json`) -).json(); - -firebase.initializeApp(fbConfig); - -// Retrieve an instance of Firebase Messaging so that it can handle background -// messages. -const messaging = firebase.messaging(); - -messaging.onBackgroundMessage((payload) => { - console.log( - "[firebase-messaging-sw.js] Received background message ", - payload - ); - // Customize notification here - const notificationOptions = { - body: payload.notification.body, - icon: payload.data.imageUrl, - tag: payload.data.id, // ensure that the notifications for same items are written over - }; - - self.registration.showNotification( - payload.notification.title, - notificationOptions - ); -}); diff --git a/web/public/notifications-worker.ts b/web/public/notifications-worker.ts new file mode 100644 index 000000000..60ca960ed --- /dev/null +++ b/web/public/notifications-worker.ts @@ -0,0 +1,9 @@ +// Notifications Worker + +self.addEventListener("push", function (event) { + if (event.data) { + console.log("This push event has data: ", event.data.text()); + } else { + console.log("This push event has no data."); + } +}); diff --git a/web/src/hooks/use-firebase.ts b/web/src/hooks/use-firebase.ts deleted file mode 100644 index a0cee9d83..000000000 --- a/web/src/hooks/use-firebase.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { getMessaging } from "firebase/messaging"; -import { initializeApp } from "firebase/app"; -import { useEffect, useMemo, useState } from "react"; - -export function useFirebaseApp() { - const [firebaseConfig, setFirebaseConfig] = useState(); - - useEffect(() => { - if (!firebaseConfig) { - fetch(`${window.location.href}/firebase-config.json`).then( - async (resp) => { - setFirebaseConfig(await resp.json()); - }, - ); - } - }, [firebaseConfig]); - - return useMemo(() => { - if (!firebaseConfig) { - return; - } - - const app = initializeApp(firebaseConfig); - app.automaticDataCollectionEnabled = false; - return app; - }, [firebaseConfig]); -} - -export function useFirebaseMessaging() { - return useMemo(() => getMessaging(), []); -} diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx index daaa4c0f4..385379dfc 100644 --- a/web/src/pages/Settings.tsx +++ b/web/src/pages/Settings.tsx @@ -37,24 +37,37 @@ import MasksAndZonesView from "@/views/settings/MasksAndZonesView"; import AuthenticationView from "@/views/settings/AuthenticationView"; import NotificationView from "@/views/settings/NotificationsSettingsView"; -export default function Settings() { - const settingsViews = [ - "general", - "camera settings", - "masks / zones", - "motion tuner", - "debug", - "users", - "notifications", - ] as const; +const allSettingsViews = [ + "general", + "camera settings", + "masks / zones", + "motion tuner", + "debug", + "users", + "notifications", +] as const; +type SettingsType = (typeof allSettingsViews)[number]; - type SettingsType = (typeof settingsViews)[number]; +export default function Settings() { const [page, setPage] = useState("general"); const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100); const tabsRef = useRef(null); const { data: config } = useSWR("config"); + // available settings views + + const settingsViews = useMemo(() => { + const views = [...allSettingsViews]; + + if (!("Notification" in window) || !window.isSecureContext) { + const index = views.indexOf("notifications"); + views.splice(index, 1); + } + + return views; + }, []); + // TODO: confirm leave page const [unsavedChanges, setUnsavedChanges] = useState(false); const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false); diff --git a/web/src/types/frigateConfig.ts b/web/src/types/frigateConfig.ts index 7aa4ebd91..d861fa094 100644 --- a/web/src/types/frigateConfig.ts +++ b/web/src/types/frigateConfig.ts @@ -341,6 +341,10 @@ export interface FrigateConfig { user: string | null; }; + notifications: { + enabled: boolean; + }; + objects: { filters: { [objectName: string]: { @@ -395,7 +399,7 @@ export interface FrigateConfig { semantic_search: { enabled: boolean; - } + }; snapshots: { bounding_box: boolean; diff --git a/web/src/views/settings/NotificationsSettingsView.tsx b/web/src/views/settings/NotificationsSettingsView.tsx index 18ff2cdc4..765dbc489 100644 --- a/web/src/views/settings/NotificationsSettingsView.tsx +++ b/web/src/views/settings/NotificationsSettingsView.tsx @@ -1,27 +1,80 @@ import { Button } from "@/components/ui/button"; -import { useFirebaseApp, useFirebaseMessaging } from "@/hooks/use-firebase"; -import { getToken } from "firebase/messaging"; +import Heading from "@/components/ui/heading"; +import { Label } from "@/components/ui/label"; +import { Toaster } from "@/components/ui/sonner"; +import { Switch } from "@/components/ui/switch"; +import { FrigateConfig } from "@/types/frigateConfig"; +import axios from "axios"; +import useSWR from "swr"; export default function NotificationView() { - useFirebaseApp(); - const firebaseMessaging = useFirebaseMessaging(); + const { data: config } = useSWR("config"); return ( -
- -
+ <> +
+ +
+ + Notification Settings + + +
+
+
+ {}} + /> + +
+
+

+ Enable notifications for Frigate alerts. This requires Frigate + to be externally accessible. +

+
+
+
+ + {config?.notifications.enabled && ( +
+
+ { + // TODO need to register the worker before enabling the notifications button + // TODO make the notifications button show enable / disable depending on current state + } + +
+
+ )} +
+
+ ); }