From daa3d5b2c48de319453e44a296646a1eb370158e Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 10 Jul 2024 11:25:35 -0600 Subject: [PATCH 01/30] Setup basic notification page --- web/package-lock.json | 835 +++++++++++++++++- web/package.json | 1 + web/src/hooks/use-firebase.ts | 11 + web/src/pages/Settings.tsx | 3 + web/src/types/notifications.ts | 9 + .../settings/NotificationsSettingsView.tsx | 18 + 6 files changed, 857 insertions(+), 20 deletions(-) create mode 100644 web/src/hooks/use-firebase.ts create mode 100644 web/src/types/notifications.ts create mode 100644 web/src/views/settings/NotificationsSettingsView.tsx diff --git a/web/package-lock.json b/web/package-lock.json index b49d6ad836..bcc5cfa707 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -36,6 +36,7 @@ "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", @@ -687,6 +688,562 @@ "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", @@ -725,6 +1282,37 @@ "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", @@ -1097,6 +1685,70 @@ "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", @@ -3243,7 +3895,6 @@ "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" } @@ -3252,7 +3903,6 @@ "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" }, @@ -3677,7 +4327,6 @@ "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", @@ -3691,7 +4340,6 @@ "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", @@ -3716,7 +4364,6 @@ "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" }, @@ -3727,8 +4374,7 @@ "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==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -4081,8 +4727,7 @@ "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==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/entities": { "version": "4.5.0", @@ -4139,7 +4784,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -4530,6 +5174,18 @@ "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", @@ -4594,6 +5250,63 @@ "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", @@ -4708,7 +5421,6 @@ "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.*" } @@ -4883,6 +5595,12 @@ "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", @@ -4920,6 +5638,12 @@ "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", @@ -5051,7 +5775,6 @@ "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" } @@ -5449,12 +6172,24 @@ "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", @@ -6456,6 +7191,30 @@ "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", @@ -6914,7 +7673,6 @@ "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" } @@ -7174,6 +7932,26 @@ "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", @@ -7337,7 +8115,6 @@ "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", @@ -7367,7 +8144,6 @@ "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" }, @@ -7969,8 +8745,7 @@ "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==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/universalify": { "version": "0.2.0", @@ -8324,6 +9099,29 @@ "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", @@ -8480,7 +9278,6 @@ "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" } @@ -8497,7 +9294,6 @@ "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", @@ -8515,7 +9311,6 @@ "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 328e995b79..ed2a358464 100644 --- a/web/package.json +++ b/web/package.json @@ -42,6 +42,7 @@ "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/src/hooks/use-firebase.ts b/web/src/hooks/use-firebase.ts new file mode 100644 index 0000000000..8fa04a614e --- /dev/null +++ b/web/src/hooks/use-firebase.ts @@ -0,0 +1,11 @@ +import { firebaseConfig } from "@/types/notifications"; +import { initializeApp } from "firebase/app"; +import { useMemo } from "react"; + +export function useFirebaseApp() { + return useMemo(() => { + const app = initializeApp(firebaseConfig); + app.automaticDataCollectionEnabled = false; + return app; + }, []); +} diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx index c355a97c67..daaa4c0f4d 100644 --- a/web/src/pages/Settings.tsx +++ b/web/src/pages/Settings.tsx @@ -35,6 +35,7 @@ import ObjectSettingsView from "@/views/settings/ObjectSettingsView"; import MotionTunerView from "@/views/settings/MotionTunerView"; import MasksAndZonesView from "@/views/settings/MasksAndZonesView"; import AuthenticationView from "@/views/settings/AuthenticationView"; +import NotificationView from "@/views/settings/NotificationsSettingsView"; export default function Settings() { const settingsViews = [ @@ -44,6 +45,7 @@ export default function Settings() { "motion tuner", "debug", "users", + "notifications", ] as const; type SettingsType = (typeof settingsViews)[number]; @@ -181,6 +183,7 @@ export default function Settings() { /> )} {page == "users" && } + {page == "notifications" && } {confirmationDialogOpen && ( + + + ); +} From 999d676e0a09a3bb2dbd6ea4de9491a94f92e017 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 10 Jul 2024 12:01:37 -0600 Subject: [PATCH 02/30] Add basic notification implementation --- docker/main/requirements-wheels.txt | 4 ++- frigate/comms/firebase.py | 46 +++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 frigate/comms/firebase.py diff --git a/docker/main/requirements-wheels.txt b/docker/main/requirements-wheels.txt index 4b4e13850e..ab31520901 100644 --- a/docker/main/requirements-wheels.txt +++ b/docker/main/requirements-wheels.txt @@ -36,4 +36,6 @@ chromadb == 0.5.0 # Generative AI google-generativeai == 0.6.* ollama == 0.2.* -openai == 1.30.* \ No newline at end of file +openai == 1.30.* +# Notifications +firebase_admin == 6.5.0 \ No newline at end of file diff --git a/frigate/comms/firebase.py b/frigate/comms/firebase.py new file mode 100644 index 0000000000..cb8fe461eb --- /dev/null +++ b/frigate/comms/firebase.py @@ -0,0 +1,46 @@ +import logging +from typing import Any, Callable + +import firebase_admin +from firebase_admin import 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) -> None: + firebase_admin.initialize_app( + options={ + "apiKey": "AIzaSyCoweRLtvai8iNwhsoT-GH_CH_0pckqMmA", + "authDomain": "frigate-ed674.firebaseapp.com", + "projectId": "frigate-ed674", + "storageBucket": "frigate-ed674.appspot.com", + "messagingSenderId": "76314288339", + "appId": "1:76314288339:web:090e170610d3bf0966f426", + "measurementId": "G-GZ1JKNDJZK", + } + ) + + 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 == "review": + message = messaging.MulticastMessage( + notification=messaging.Notification( + title="Something happened", + body="There is a body", + ), + tokens=[], + ) + messaging.send_multicast(message) + + def stop(self) -> None: + pass From 0fd1ad0537280a55dd5d5305de5a11cf9a9e9b0d Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 10 Jul 2024 12:17:36 -0600 Subject: [PATCH 03/30] Register for push notifications --- frigate/comms/firebase.py | 2 +- web/public/firebase-messaging-sw.ts | 34 +++++++++++++++++++ web/src/hooks/use-firebase.ts | 5 +++ .../settings/NotificationsSettingsView.tsx | 15 ++++++-- 4 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 web/public/firebase-messaging-sw.ts diff --git a/frigate/comms/firebase.py b/frigate/comms/firebase.py index cb8fe461eb..8946e95a59 100644 --- a/frigate/comms/firebase.py +++ b/frigate/comms/firebase.py @@ -32,7 +32,7 @@ def subscribe(self, receiver: Callable) -> None: def publish(self, topic: str, payload: Any, retain: bool = False) -> None: """Wrapper for publishing when client is in valid state.""" - if topic == "review": + if topic == "reviews": message = messaging.MulticastMessage( notification=messaging.Notification( title="Something happened", diff --git a/web/public/firebase-messaging-sw.ts b/web/public/firebase-messaging-sw.ts new file mode 100644 index 0000000000..8737368b69 --- /dev/null +++ b/web/public/firebase-messaging-sw.ts @@ -0,0 +1,34 @@ +// 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 +firebase.initializeApp({ + apiKey: "AIzaSyCoweRLtvai8iNwhsoT-GH_CH_0pckqMmA", + authDomain: "frigate-ed674.firebaseapp.com", + projectId: "frigate-ed674", + storageBucket: "frigate-ed674.appspot.com", + messagingSenderId: "76314288339", + appId: "1:76314288339:web:090e170610d3bf0966f426", + measurementId: "G-GZ1JKNDJZK", +}); + +// Retrieve an instance of Firebase Messaging so that it can handle background +// messages. + +messaging.onBackgroundMessage(function (payload) { + console.log("Received background message ", payload); + + const notificationTitle = payload.notification.title; + const notificationOptions = { + body: payload.notification.body, + }; + + self.registration.showNotification(notificationTitle, notificationOptions); +}); diff --git a/web/src/hooks/use-firebase.ts b/web/src/hooks/use-firebase.ts index 8fa04a614e..8930189952 100644 --- a/web/src/hooks/use-firebase.ts +++ b/web/src/hooks/use-firebase.ts @@ -1,4 +1,5 @@ import { firebaseConfig } from "@/types/notifications"; +import { getMessaging, getToken } from "firebase/messaging"; import { initializeApp } from "firebase/app"; import { useMemo } from "react"; @@ -9,3 +10,7 @@ export function useFirebaseApp() { return app; }, []); } + +export function useFirebaseMessaging() { + return useMemo(() => getMessaging(), []); +} diff --git a/web/src/views/settings/NotificationsSettingsView.tsx b/web/src/views/settings/NotificationsSettingsView.tsx index 63a32761a3..18ff2cdc4e 100644 --- a/web/src/views/settings/NotificationsSettingsView.tsx +++ b/web/src/views/settings/NotificationsSettingsView.tsx @@ -1,14 +1,23 @@ import { Button } from "@/components/ui/button"; -import { useFirebaseApp } from "@/hooks/use-firebase"; +import { useFirebaseApp, useFirebaseMessaging } from "@/hooks/use-firebase"; +import { getToken } from "firebase/messaging"; export default function NotificationView() { - const firebaseApp = useFirebaseApp(); + useFirebaseApp(); + const firebaseMessaging = useFirebaseMessaging(); 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 + } + +
+
+ )} +
+
+ ); } From afe20320852c7c4fc24061628dfd2fd134699075 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sat, 20 Jul 2024 09:14:38 -0600 Subject: [PATCH 10/30] Implement VAPID key generation --- docker/main/requirements-wheels.txt | 4 ++- frigate/api/notification.py | 25 +++++++++++++++++-- frigate/comms/webpush.py | 8 +++++- .../settings/NotificationsSettingsView.tsx | 21 ++++++++++++++-- 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/docker/main/requirements-wheels.txt b/docker/main/requirements-wheels.txt index 4b4e13850e..028a489259 100644 --- a/docker/main/requirements-wheels.txt +++ b/docker/main/requirements-wheels.txt @@ -36,4 +36,6 @@ chromadb == 0.5.0 # Generative AI google-generativeai == 0.6.* ollama == 0.2.* -openai == 1.30.* \ No newline at end of file +openai == 1.30.* +# push notifications +py-vapid == 1.9.* \ No newline at end of file diff --git a/frigate/api/notification.py b/frigate/api/notification.py index 63cff9d086..76197d436f 100644 --- a/frigate/api/notification.py +++ b/frigate/api/notification.py @@ -1,14 +1,19 @@ """Notification apis.""" import logging +import os from flask import ( Blueprint, + current_app, jsonify, + make_response, request, ) from peewee import DoesNotExist +from py_vapid import Vapid01 +from frigate.const import CONFIG_DIR from frigate.models import User logger = logging.getLogger(__name__) @@ -16,6 +21,18 @@ 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"]) def register_notifications(): 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.username == username ).execute() - return jsonify({"success": True, "message": "Successfully saved token."}), 200 + return make_response( + jsonify({"success": True, "message": "Successfully saved token."}), 200 + ) except DoesNotExist: - return jsonify({"success": False, "message": "Could not find user."}), 404 + return make_response( + jsonify({"success": False, "message": "Could not find user."}), 404 + ) diff --git a/frigate/comms/webpush.py b/frigate/comms/webpush.py index d2cf6ffd05..994881a992 100644 --- a/frigate/comms/webpush.py +++ b/frigate/comms/webpush.py @@ -2,10 +2,14 @@ import json import logging +import os from typing import Any, Callable +from py_vapid import Vapid01 + from frigate.comms.dispatcher import Communicator from frigate.config import FrigateConfig +from frigate.const import CONFIG_DIR from frigate.models import User logger = logging.getLogger(__name__) @@ -16,7 +20,9 @@ class WebPushClient(Communicator): # type: ignore[misc] def __init__(self, config: FrigateConfig) -> None: 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.invalid_tokens = [] diff --git a/web/src/views/settings/NotificationsSettingsView.tsx b/web/src/views/settings/NotificationsSettingsView.tsx index 765dbc489e..b1d6bad4a0 100644 --- a/web/src/views/settings/NotificationsSettingsView.tsx +++ b/web/src/views/settings/NotificationsSettingsView.tsx @@ -5,11 +5,27 @@ 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 useSWR from "swr"; +const NOTIFICATION_SERVICE_WORKER = "notifications-worker.ts"; + export default function NotificationView() { const { data: config } = useSWR("config"); + // notification state + + const [notificationsSubscribed, setNotificationsSubscribed] = + useState(); + + useEffect(() => { + navigator.serviceWorker + .getRegistration(NOTIFICATION_SERVICE_WORKER) + .then((worker) => { + setNotificationsSubscribed(worker != null); + }); + }, []); + return ( <>
@@ -48,12 +64,13 @@ export default function NotificationView() { // TODO make the notifications button show enable / disable depending on current state }
From 5398a661ffdf5aa5046db390be535ebeb8d8b838 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sat, 20 Jul 2024 11:25:44 -0600 Subject: [PATCH 11/30] Implement public key encoding --- frigate/api/notification.py | 8 ++++++-- web/src/views/settings/NotificationsSettingsView.tsx | 6 ++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/frigate/api/notification.py b/frigate/api/notification.py index 76197d436f..de4e5770cf 100644 --- a/frigate/api/notification.py +++ b/frigate/api/notification.py @@ -3,6 +3,7 @@ import logging import os +from cryptography.hazmat.primitives import serialization from flask import ( Blueprint, current_app, @@ -11,7 +12,7 @@ request, ) from peewee import DoesNotExist -from py_vapid import Vapid01 +from py_vapid import Vapid01, utils from frigate.const import CONFIG_DIR from frigate.models import User @@ -30,7 +31,10 @@ def get_vapid_pub_key(): ) key = Vapid01.from_file(os.path.join(CONFIG_DIR, "notifications.pem")) - return jsonify(key.public_key), 200 + raw_pub = key.public_key.public_bytes( + serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint + ) + return jsonify(utils.b64urlencode(raw_pub)), 200 @NotificationBp.route("/notifications/register", methods=["POST"]) diff --git a/web/src/views/settings/NotificationsSettingsView.tsx b/web/src/views/settings/NotificationsSettingsView.tsx index b1d6bad4a0..449a80e81e 100644 --- a/web/src/views/settings/NotificationsSettingsView.tsx +++ b/web/src/views/settings/NotificationsSettingsView.tsx @@ -13,6 +13,12 @@ const NOTIFICATION_SERVICE_WORKER = "notifications-worker.ts"; export default function NotificationView() { const { data: config } = useSWR("config"); + // notification key handling + + const { data: publicKey } = useSWR( + config?.notifications?.enabled ? "notifications/pubkey" : null, + ); + // notification state const [notificationsSubscribed, setNotificationsSubscribed] = From f9466905203a53d113a5be7c382f1660f64a9575 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sat, 20 Jul 2024 12:34:22 -0600 Subject: [PATCH 12/30] Implement webpush from server --- docker/main/requirements-wheels.txt | 3 +- frigate/api/notification.py | 8 ++-- frigate/comms/webpush.py | 40 ++++++++++++++++--- package-lock.json | 6 +++ .../settings/NotificationsSettingsView.tsx | 10 ++++- 5 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 package-lock.json diff --git a/docker/main/requirements-wheels.txt b/docker/main/requirements-wheels.txt index 028a489259..f07dc149d0 100644 --- a/docker/main/requirements-wheels.txt +++ b/docker/main/requirements-wheels.txt @@ -38,4 +38,5 @@ google-generativeai == 0.6.* ollama == 0.2.* openai == 1.30.* # push notifications -py-vapid == 1.9.* \ No newline at end of file +py-vapid == 1.9.* +pywebpush == 2.0.* \ No newline at end of file diff --git a/frigate/api/notification.py b/frigate/api/notification.py index de4e5770cf..b49f2dd5a6 100644 --- a/frigate/api/notification.py +++ b/frigate/api/notification.py @@ -41,13 +41,13 @@ def get_vapid_pub_key(): 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"] + sub = json.get("sub") - if not token: - return jsonify({"success": False, "message": "Token must be provided."}), 400 + if not sub: + return jsonify({"success": False, "message": "Subscription must be provided."}), 400 try: - User.update(notification_tokens=User.notification_tokens.append(token)).where( + User.update(notification_tokens=User.notification_tokens.append(sub)).where( User.username == username ).execute() return make_response( diff --git a/frigate/comms/webpush.py b/frigate/comms/webpush.py index 994881a992..fdebe964c5 100644 --- a/frigate/comms/webpush.py +++ b/frigate/comms/webpush.py @@ -1,11 +1,13 @@ """Handle sending notifications for Frigate via Firebase.""" +import datetime import json import logging import os from typing import Any, Callable from py_vapid import Vapid01 +from pywebpush import WebPusher from frigate.comms.dispatcher import Communicator from frigate.config import FrigateConfig @@ -20,17 +22,17 @@ class WebPushClient(Communicator): # type: ignore[misc] def __init__(self, config: FrigateConfig) -> None: self.config = config + self.claim = None + self.claim_headers = None + self.web_pushers: list[WebPusher] = [] # 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.invalid_tokens = [] + self.vapid = Vapid01.from_file(os.path.join(CONFIG_DIR, "notifications.pem")) users: list[User] = User.select(User.notification_tokens).dicts().iterator() - for user in users: - self.tokens.extend(user["notification_tokens"]) + for sub in user["notification_tokens"]: + self.web_pushers.append(WebPusher(sub)) def subscribe(self, receiver: Callable) -> None: """Wrapper for allowing dispatcher to subscribe.""" @@ -42,6 +44,20 @@ def publish(self, topic: str, payload: Any, retain: bool = False) -> None: self.send_message(json.loads(payload)) def send_message(self, payload: dict[str, any]) -> None: + # 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": "mailto:test@example.com", + "aud": "https://fcm.googleapis.com", + "exp": ( + datetime.datetime.now() + datetime.timedelta(hours=1) + ).timestamp(), + } + self.claim_headers = self.vapid.sign(self.claim) + logger.info(f"Updated claim with new headers {self.claim_headers}") + # Only notify for alerts if payload["after"]["severity"] != "alert": return @@ -72,5 +88,17 @@ def send_message(self, payload: dict[str, any]) -> None: 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", "")}' + 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, + }), + ) + def stop(self) -> None: pass diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..13ac760ada --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "frigate", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/web/src/views/settings/NotificationsSettingsView.tsx b/web/src/views/settings/NotificationsSettingsView.tsx index 449a80e81e..7247b26c22 100644 --- a/web/src/views/settings/NotificationsSettingsView.tsx +++ b/web/src/views/settings/NotificationsSettingsView.tsx @@ -70,7 +70,10 @@ export default function NotificationView() { // TODO make the notifications button show enable / disable depending on current state } From 242c254ccec5693fde77ffcdebdb0593a86274a5 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sat, 20 Jul 2024 13:24:20 -0600 Subject: [PATCH 14/30] Make notifications config only --- frigate/comms/webpush.py | 8 ++++++- frigate/config.py | 4 ++-- web/src/pages/Settings.tsx | 8 +++++-- .../settings/NotificationsSettingsView.tsx | 23 ------------------- 4 files changed, 15 insertions(+), 28 deletions(-) diff --git a/frigate/comms/webpush.py b/frigate/comms/webpush.py index 9cf7d688ba..85f4ac7d64 100644 --- a/frigate/comms/webpush.py +++ b/frigate/comms/webpush.py @@ -26,6 +26,9 @@ def __init__(self, config: FrigateConfig) -> None: self.claim_headers = None self.web_pushers: list[WebPusher] = [] + if not self.config.notifications.email: + logger.warning("Email must be provided for push notifications to be sent.") + # Pull keys from PEM or generate if they do not exist self.vapid = Vapid01.from_file(os.path.join(CONFIG_DIR, "notifications.pem")) @@ -44,12 +47,15 @@ def publish(self, topic: str, payload: Any, retain: bool = False) -> None: self.send_message(json.loads(payload)) def send_message(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": "mailto:test@example.com", + "sub": f"mailto:{self.config.notifications.email}", "aud": "https://fcm.googleapis.com", "exp": ( datetime.datetime.now() + datetime.timedelta(hours=1) diff --git a/frigate/config.py b/frigate/config.py index e06b0baa2e..0c801d67db 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -171,8 +171,8 @@ 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." + email: Optional[str] = Field( + default=None, title="Email required for push." ) diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx index 385379dfc1..bc21674842 100644 --- a/web/src/pages/Settings.tsx +++ b/web/src/pages/Settings.tsx @@ -60,13 +60,17 @@ export default function Settings() { const settingsViews = useMemo(() => { const views = [...allSettingsViews]; - if (!("Notification" in window) || !window.isSecureContext) { + if ( + !("Notification" in window) || + !window.isSecureContext || + !config?.notifications.enabled + ) { const index = views.indexOf("notifications"); views.splice(index, 1); } return views; - }, []); + }, [config]); // TODO: confirm leave page const [unsavedChanges, setUnsavedChanges] = useState(false); diff --git a/web/src/views/settings/NotificationsSettingsView.tsx b/web/src/views/settings/NotificationsSettingsView.tsx index 2b73714092..1ff6658cdb 100644 --- a/web/src/views/settings/NotificationsSettingsView.tsx +++ b/web/src/views/settings/NotificationsSettingsView.tsx @@ -1,8 +1,6 @@ import { Button } from "@/components/ui/button"; 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 { useCallback, useEffect, useState } from "react"; @@ -69,27 +67,6 @@ export default function NotificationView() { Notification Settings -
-
-
- {}} - /> - -
-
-

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

-
-
-
- {config?.notifications.enabled && (
From 104d56af25762b2e1ced3394ff6ec13e5a519b70 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sat, 20 Jul 2024 13:47:25 -0600 Subject: [PATCH 15/30] Add maskable icon --- migrations/026_add_notification_tokens.py | 1 - web/public/images/maskable-badge.png | Bin 0 -> 3084 bytes web/public/notifications-worker.ts | 9 ++------- web/site.webmanifest | 6 ++++++ 4 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 web/public/images/maskable-badge.png diff --git a/migrations/026_add_notification_tokens.py b/migrations/026_add_notification_tokens.py index 08d95cbf72..37506c4063 100644 --- a/migrations/026_add_notification_tokens.py +++ b/migrations/026_add_notification_tokens.py @@ -33,7 +33,6 @@ def migrate(migrator, database, fake=False, **kwargs): migrator.add_fields( User, notification_tokens=JSONField(default=[]), - ) diff --git a/web/public/images/maskable-badge.png b/web/public/images/maskable-badge.png new file mode 100644 index 0000000000000000000000000000000000000000..93a0d561ebf8452a4dbfcd91eed7b970b4109c60 GIT binary patch literal 3084 zcma)82|QH$7r(>U!@TmJB+E1j?}eEW$(R|&5@Q?5dq!j*vdfam)|6g|UMdw4lhB)z zDC?6kN-9Z~LS>ok+q1`5p57h&%fHWipU?l^&+ndd&pF@oJ?H+;{oI>iZDk@ZDkBO2 zfH=w2$Oe=yyd^9MUVBr+4}b#dMYJRWK+#396;}c99^r0kV+jBeN&rAV3jj-?i9QSf zVQ2stbpZfuDgbN`%J}0D4s@KQQb?YbmcUP-E({1jwgUX11_4h1A_MTUff`^2+4)ts zfv9Z6@Bu)aHvrv;IRZ-Fkqj;#W?k|#Am1`F_`XKFF!;aeJXoa|>mw*cf=um0006#+ zw;&`N#VG*bYw)(EgiaHGvZd7%ee-IA^;AmJ-^{0lqB53}80U=l#9=R5Q z1$CYog+#1{g!*uxWRV{~u>4&LFB$3jDcuqae` zc({7_KJ~z0FBC>cM+b%0Kxt^Gfe~sUCj&xVX=(u>yVsNaH;)lD#68$MDAYSJ0Kv=a z>J}Ilibo=OL|>2fIzzoZzcB@bY_tV7h~jxr7$d{e5#Inm2Y81PG&UgLnZHL-zWF!tY_@)nAP0L>!AH4X z*#^mG+opJ4e^_g88r6?tr=Kxu>g%P2}f;)Co%`O3J{5 z(FfU-s3y8T9m7}G8!Pw^DIB1MYNN{wlO84byNbR$A7$%bPf1y+C1h^@>C4D7JzMLg zIM$WnH!GrZ%H+(ZzULQ5Ix9j#m*@SDu%dLrM&FV&krGIm0cn7JoL!87nBtwPV=UVr zINzy?TxhAkx|iwOUZ5Gp4T|`(idVCLXvA*sd2ul@%EwlQ!PfSkv8(1A?qNmW-i~r= zdUwxmr1x23=y(SP8^ocrv93>`yWCc|({FRluOkhQa%Y(e{4+$8eR`)>EzoM))g2j2 zOA#MR{deMNQ|#S^u1^JTXi!DPckWNBudmOM?IE6jIv&rcCNmWouwm!wnB2-D!^PBB*N*8j^YYGlHlz}7AH0z5R28x4g+A<)8ReR!%|tS+Q3AtgwKz$Iy!#Gi zgZg$q|7`r~n%ZsB6{TiQO)S$m5x!cXHN`@9{$8sr`07*B+(egd`24uNor8l|<#a11 zY}B^Q>y7+#Pm@!~_La$goMt`S!PGKhX^yE&n_{HnBTI#<>>qg+fR$ZV&^?il^CBzX zvBbfK`mdyr1R%rHV7Vs@<0L+_7ykU%{(1a;>(%Sm&EGfP)a|-w_W)N4;O}a!5}yRv z5ZQl(jX^!;=IiUt+F8+&&kOR#+U}>PJaDS|@bm_pQ}QGsGyeDuGnrT8tuaGlwY4ejk+`DT@@r?ZCPd$)#AUD{tvGu5M8e0p;bud+@4LHK9Zi0dP3v4%M&9o85hVYZ8`(A3;l(%u&_ z*s!A)&3{1}V*saoDE2l~AVk69F(Q2w7(Qao^hxu1UDhmFh?Q1edsU0P#llJs_J(=y z-jxHA{Dau=iSdrcWn6vG6pnp;dep8_yX7**IoXv_+X#+yhRaYEKJs9kgj{y?^862Q zTaM3@aZZ&1Giv$u|(4fBDnG3-|1EB0GD{D5-}ker4_OvUh_IK;Vye z`iZ3JhO6&t5=3%76$-|;$0B4cBn3S!Sb$>ZA!uK6VXmUy{iBt?(1k>NSTj9E4e3A7 z<>L*7j`&HY>78QbkICaBQVNb%9{lq|$+oG$&hk%~(T<|qTi96XJ_x+MdG?}$-b7Dp zIwsYPRANx&Krx&j7 zIEvGspXnQipt-B&5Zhv66YPvgy5=>s`zV!HQJlR-T zzvyYNNT*p&ON-4efdXY5!!mt;b4#xIfc-VLNo=CLm`%9kzP9eFNU;|Pej#=onc&r! zVR1%532hZ`Uz^HzuU-%AQ zlvm5X1!)phY;e@_P8)m_bm1ycJ9&u%8R=kDc0L47+}dAf23Wv)u?YR=z}c+#Lnzxa ziaLv8Q6K00NNhB*F2Jo7;M~3E+%Zt-^X{k9N4{8S zx|c5xg6ug4VrY4nMNE#g8*veF56N-mF3H%+sKCJG%-_aq{d52T literal 0 HcmV?d00001 diff --git a/web/public/notifications-worker.ts b/web/public/notifications-worker.ts index 8b6067a609..95b7fe72db 100644 --- a/web/public/notifications-worker.ts +++ b/web/public/notifications-worker.ts @@ -8,16 +8,11 @@ self.addEventListener("push", function (event) { // @ts-expect-error we know this exists self.registration.showNotification(data.title, { body: data.message, - icon: data.image, + icon: "/images/maskable-icon.png", image: data.image, + badge: "/images/maskable-badge.png", tag: data.id, data: { id: data.id, link: data.direct_url }, - actions: [ - { - action: `view-${data.id}`, - title: "View", - }, - ], }); } else { // pass diff --git a/web/site.webmanifest b/web/site.webmanifest index 53a45654be..94e455ec85 100644 --- a/web/site.webmanifest +++ b/web/site.webmanifest @@ -20,6 +20,12 @@ "sizes": "180x180", "type": "image/png", "purpose": "maskable" + }, + { + "src": "/images/maskable-badge.png", + "sizes": "96x96", + "type": "image/png", + "purpose": "maskable" } ], "theme_color": "#ffffff", From fa8a0cc5fcc2054f474972b9242dcb00e01f8b3e Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sat, 20 Jul 2024 14:42:15 -0600 Subject: [PATCH 16/30] Use zod form to control notification settings in the UI --- docs/docs/configuration/notifications.md | 22 ++ docs/docs/configuration/reference.md | 12 +- docs/sidebars.js | 1 + frigate/comms/webpush.py | 3 - frigate/config.py | 4 +- web/src/pages/Settings.tsx | 12 +- web/src/types/frigateConfig.ts | 1 + .../settings/NotificationsSettingsView.tsx | 216 +++++++++++++++++- 8 files changed, 245 insertions(+), 26 deletions(-) create mode 100644 docs/docs/configuration/notifications.md diff --git a/docs/docs/configuration/notifications.md b/docs/docs/configuration/notifications.md new file mode 100644 index 0000000000..bc48824292 --- /dev/null +++ b/docs/docs/configuration/notifications.md @@ -0,0 +1,22 @@ +--- +id: notifications +title: Notifications +--- + +# 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. + +:::note + +Currently, notifications are only supported in Chrome and Firefox browsers. + +::: + +## Registration + +Once notifications are enabled, press the `Register for Notifications` button on all devices that you would like to receive notifications on. This will register the background worker. After this Frigate must be restarted and then notifications will begin to be sent. \ No newline at end of file diff --git a/docs/docs/configuration/reference.md b/docs/docs/configuration/reference.md index fa94c98aa3..8c11836b47 100644 --- a/docs/docs/configuration/reference.md +++ b/docs/docs/configuration/reference.md @@ -372,6 +372,14 @@ motion: # Optional: Delay when updating camera motion through MQTT from ON -> OFF (default: shown below). mqtt_off_delay: 30 +# Optional: Notification Configuration +notifications: + # Optional: Enable notification service (default: shown below) + enabled: False + # Optional: Email for push service to reach out to + # NOTE: This is required to use notifications + email: "admin@example.com" + # Optional: Record configuration # NOTE: Can be overridden at the camera level record: @@ -642,8 +650,8 @@ cameras: user: admin # Optional: password for login. password: admin - # Optional: Ignores time synchronization mismatches between the camera and the server during authentication. - # Using NTP on both ends is recommended and this should only be set to True in a "safe" environment due to the security risk it represents. + # Optional: Ignores time synchronization mismatches between the camera and the server during authentication. + # Using NTP on both ends is recommended and this should only be set to True in a "safe" environment due to the security risk it represents. ignore_time_mismatch: False # Optional: PTZ camera object autotracking. Keeps a moving object in # the center of the frame by automatically moving the PTZ camera. diff --git a/docs/sidebars.js b/docs/sidebars.js index 1e1a270464..9a6ba0df9d 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -54,6 +54,7 @@ module.exports = { ], "Extra Configuration": [ "configuration/authentication", + "configuration/notifications", "configuration/hardware_acceleration", "configuration/ffmpeg_presets", "configuration/tls", diff --git a/frigate/comms/webpush.py b/frigate/comms/webpush.py index 85f4ac7d64..2320692b3a 100644 --- a/frigate/comms/webpush.py +++ b/frigate/comms/webpush.py @@ -62,7 +62,6 @@ def send_message(self, payload: dict[str, any]) -> None: ).timestamp(), } self.claim_headers = self.vapid.sign(self.claim) - logger.info(f"Updated claim with new headers {self.claim_headers}") # Only notify for alerts if payload["after"]["severity"] != "alert": @@ -94,8 +93,6 @@ def send_message(self, payload: dict[str, any]) -> None: 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, diff --git a/frigate/config.py b/frigate/config.py index 0c801d67db..126d964ffb 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -171,9 +171,7 @@ class AuthConfig(FrigateBaseModel): class NotificationConfig(FrigateBaseModel): enabled: bool = Field(default=False, title="Enable notifications") - email: Optional[str] = Field( - default=None, title="Email required for push." - ) + email: Optional[str] = Field(default=None, title="Email required for push.") class StatsConfig(FrigateBaseModel): diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx index bc21674842..6d147b3327 100644 --- a/web/src/pages/Settings.tsx +++ b/web/src/pages/Settings.tsx @@ -60,17 +60,13 @@ export default function Settings() { const settingsViews = useMemo(() => { const views = [...allSettingsViews]; - if ( - !("Notification" in window) || - !window.isSecureContext || - !config?.notifications.enabled - ) { + if (!("Notification" in window) || !window.isSecureContext) { const index = views.indexOf("notifications"); views.splice(index, 1); } return views; - }, [config]); + }, []); // TODO: confirm leave page const [unsavedChanges, setUnsavedChanges] = useState(false); @@ -200,7 +196,9 @@ export default function Settings() { /> )} {page == "users" && } - {page == "notifications" && } + {page == "notifications" && ( + + )}
{confirmationDialogOpen && ( ("config", { - revalidateOnFocus: false, - }); +type NotificationSettingsValueType = { + enabled: boolean; + email?: string; +}; + +type NotificationsSettingsViewProps = { + setUnsavedChanges: React.Dispatch>; +}; +export default function NotificationView({ + setUnsavedChanges, +}: NotificationsSettingsViewProps) { + const { data: config, mutate: updateConfig } = useSWR( + "config", + { + revalidateOnFocus: false, + }, + ); + + // status bar + + const { addMessage, removeMessage } = useContext(StatusBarMessagesContext)!; // notification key handling @@ -23,6 +59,13 @@ export default function NotificationView() { const subscribeToNotifications = useCallback( (registration: ServiceWorkerRegistration) => { if (registration) { + addMessage( + "notification_settings", + "Unsaved Notification Registrations", + undefined, + "registration", + ); + registration.pushManager .subscribe({ userVisibleOnly: true, @@ -32,10 +75,16 @@ export default function NotificationView() { axios.post("notifications/register", { sub: pushSubscription, }); + toast.success( + "Successfully registered for notifications. Restart to start receiving notifications.", + { + position: "top-center", + }, + ); }); } }, - [publicKey], + [publicKey, addMessage], ); // notification state @@ -58,6 +107,78 @@ export default function NotificationView() { }); }, []); + // form + + const [isLoading, setIsLoading] = useState(false); + const formSchema = z.object({ + enabled: z.boolean(), + email: z.string(), + }); + + const form = useForm>({ + resolver: zodResolver(formSchema), + mode: "onChange", + defaultValues: { + enabled: config?.notifications.enabled, + email: config?.notifications.email, + }, + }); + + const onCancel = useCallback(() => { + if (!config) { + return; + } + + setUnsavedChanges(false); + form.reset({ + enabled: config.notifications.enabled, + email: config.notifications.email || "", + }); + // we know that these deps are correct + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [config, removeMessage, setUnsavedChanges]); + + const saveToConfig = useCallback( + async ( + { enabled, email }: NotificationSettingsValueType, // values submitted via the form + ) => { + axios + .put( + `config/set?notifications.enabled=${enabled}¬ifications.email=${email}`, + { + requires_restart: 0, + }, + ) + .then((res) => { + if (res.status === 200) { + toast.success("Notification settings have been saved.", { + position: "top-center", + }); + updateConfig(); + } else { + toast.error(`Failed to save config changes: ${res.statusText}`, { + position: "top-center", + }); + } + }) + .catch((error) => { + toast.error( + `Failed to save config changes: ${error.response.data.message}`, + { position: "top-center" }, + ); + }) + .finally(() => { + setIsLoading(false); + }); + }, + [updateConfig, setIsLoading], + ); + + function onSubmit(values: z.infer) { + setIsLoading(true); + saveToConfig(values as NotificationSettingsValueType); + } + return ( <>
@@ -67,13 +188,85 @@ export default function NotificationView() { Notification Settings +
+ + ( + + +
+ + { + return field.onChange(checked); + }} + /> +
+
+
+ )} + /> + ( + + Loitering Time + + + + + Entering a valid email is required, as this is used by the + push server in case problems occur. + + + + )} + /> +
+ + +
+ + + {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 - } +
From d0e46b3ce85e8c9dba94279d93c530f1dd40f45d Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 22 Jul 2024 12:28:25 -0600 Subject: [PATCH 27/30] Improve wording --- .../settings/NotificationsSettingsView.tsx | 87 +++++++++---------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/web/src/views/settings/NotificationsSettingsView.tsx b/web/src/views/settings/NotificationsSettingsView.tsx index 97e479d4d8..d3087b1746 100644 --- a/web/src/views/settings/NotificationsSettingsView.tsx +++ b/web/src/views/settings/NotificationsSettingsView.tsx @@ -193,8 +193,8 @@ export default function NotificationView({

- Frigate can natively send push notifications to Frigate when it - is running in the browser or installed as a PWA. + Frigate can natively send push notifications to your device when + it is running in the browser or installed as a PWA.

- {config?.notifications.enabled && ( -
-
- - -
+ } + }} + > + {`${registration != null ? "Unregister" : "Register"} for notifications on this device`} +
- )} +
From 258983db6f49bf947acb4cfeb8943f3b1112c1ea Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 22 Jul 2024 14:06:21 -0600 Subject: [PATCH 28/30] Fix docstring Co-authored-by: Blake Blackshear --- frigate/comms/webpush.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frigate/comms/webpush.py b/frigate/comms/webpush.py index 9ffd6c636b..084f91059b 100644 --- a/frigate/comms/webpush.py +++ b/frigate/comms/webpush.py @@ -18,7 +18,7 @@ class WebPushClient(Communicator): # type: ignore[misc] - """Frigate wrapper for firebase client.""" + """Frigate wrapper for webpush client.""" def __init__(self, config: FrigateConfig) -> None: self.config = config From 7e55af7d87dabd817c1efa37e42736b286f6e8bd Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 22 Jul 2024 14:17:30 -0600 Subject: [PATCH 29/30] Handle case where native auth is not enabled --- frigate/api/notification.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frigate/api/notification.py b/frigate/api/notification.py index d35ab5588d..2fe61882f6 100644 --- a/frigate/api/notification.py +++ b/frigate/api/notification.py @@ -39,7 +39,11 @@ def get_vapid_pub_key(): @NotificationBp.route("/notifications/register", methods=["POST"]) def register_notifications(): - username = request.headers.get("remote-user", type=str) or "admin" + if current_app.frigate_config.auth.enabled: + username = request.headers.get("remote-user", type=str) or "admin" + else: + username = "admin" + json: dict[str, any] = request.get_json(silent=True) or {} sub = json.get("sub") From 273e7c07d131a765fcd08139fc98ccf77ac81d15 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 22 Jul 2024 14:25:36 -0600 Subject: [PATCH 30/30] Show errors in UI --- .../views/settings/NotificationsSettingsView.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/web/src/views/settings/NotificationsSettingsView.tsx b/web/src/views/settings/NotificationsSettingsView.tsx index d3087b1746..5e592b7a2d 100644 --- a/web/src/views/settings/NotificationsSettingsView.tsx +++ b/web/src/views/settings/NotificationsSettingsView.tsx @@ -74,9 +74,18 @@ export default function NotificationView({ applicationServerKey: publicKey, }) .then((pushSubscription) => { - axios.post("notifications/register", { - sub: pushSubscription, - }); + axios + .post("notifications/register", { + sub: pushSubscription, + }) + .catch(() => { + toast.error("Failed to save notification registration.", { + position: "top-center", + }); + pushSubscription.unsubscribe(); + registration.unregister(); + setRegistration(null); + }); toast.success( "Successfully registered for notifications. Restart to start receiving notifications.", {